Make custom type and foreign key mutually exclusive

In the create table dialog a column can now have either a custom display
type or a foreign key target, but not both - a foreign key column's type
is determined by the referenced primary key, so a custom type doesn't
apply. Setting one clears and disables the other, and the foreign key
select stays disabled on the primary key column and when no targets exist.

Also add "Controls how Datasette displays and edits this column" help
text (with aria-describedby) under the custom type selector in both the
create and alter dialogs, and style the alter dialog help text.
This commit is contained in:
Simon Willison 2026-06-19 22:32:37 -07:00
commit e834008075
2 changed files with 73 additions and 1 deletions

View file

@ -1953,7 +1953,8 @@ dialog.table-create-dialog::backdrop {
font-size: 0.72rem;
}
.table-create-detail-help {
.table-create-detail-help,
.table-alter-detail-help {
color: var(--muted);
font-size: 0.82rem;
line-height: 1.35;

View file

@ -422,11 +422,42 @@ function syncTableCreateForeignKeyOptions(row, state) {
);
}
function syncTableCreateCustomTypeAndForeignKey(row, state, isFirstColumn) {
var customTypeSelect = row.querySelector(".table-create-custom-column-type");
var foreignKeySelect = row.querySelector(".table-create-foreign-key-target");
if (!foreignKeySelect) {
return;
}
var hasCustomType = customTypeSelect && !!customTypeSelect.value;
var hasForeignKey = !!foreignKeySelect.value;
if (customTypeSelect && hasForeignKey) {
customTypeSelect.value = "";
updateTableCreateCustomColumnTypePlaceholder(customTypeSelect);
hasCustomType = false;
}
if (isFirstColumn || hasCustomType) {
foreignKeySelect.value = "";
foreignKeySelect.dataset.selectedKey = "";
updateTableCreateCustomColumnTypePlaceholder(foreignKeySelect);
hasForeignKey = false;
}
if (customTypeSelect) {
customTypeSelect.disabled = state.isSaving;
}
foreignKeySelect.disabled =
state.isSaving || isFirstColumn || foreignKeySelect.options.length <= 1;
}
function refreshTableCreateForeignKeyControls(state) {
tableCreateDialogRows(state).forEach(function (row, index) {
if (index > 0) {
syncTableCreateForeignKeyOptions(row, state);
}
syncTableCreateCustomTypeAndForeignKey(row, state, index === 0);
});
}
@ -455,6 +486,7 @@ function updateTableCreateColumnRules(state) {
} else {
syncTableCreateForeignKeyOptions(row, state);
}
syncTableCreateCustomTypeAndForeignKey(row, state, isFirstColumn);
}
});
updateTableCreateMoveButtons(state);
@ -639,11 +671,19 @@ function createTableColumnRow(state, column) {
customTypeLabel.className = "table-create-detail-label";
customTypeLabel.setAttribute("for", customTypeId);
customTypeLabel.textContent = "Custom type";
var customTypeHelpId = "table-create-column-custom-type-help-" + index;
var customTypeHelp = document.createElement("p");
customTypeHelp.id = customTypeHelpId;
customTypeHelp.className = "table-create-detail-help";
customTypeHelp.textContent =
"Controls how Datasette displays and edits this column";
customTypeSelect = createTableCustomColumnTypeSelect();
customTypeSelect.id = customTypeId;
customTypeSelect.setAttribute("aria-describedby", customTypeHelpId);
customTypeSelect.value = column && column.customType ? column.customType : "";
updateTableCreateCustomColumnTypePlaceholder(customTypeSelect);
customTypeField.appendChild(customTypeLabel);
customTypeField.appendChild(customTypeHelp);
customTypeField.appendChild(customTypeSelect);
}
@ -733,11 +773,20 @@ function createTableColumnRow(state, column) {
clearTableCreateDialogError(state);
syncTableCreateCustomTypeForSqliteType(row);
syncTableCreateForeignKeyOptions(row, state);
syncTableCreateCustomTypeAndForeignKey(
row,
state,
row === state.columnList.firstElementChild,
);
});
if (customTypeSelect) {
customTypeSelect.addEventListener("change", function () {
clearTableCreateDialogError(state);
updateTableCreateCustomColumnTypePlaceholder(customTypeSelect);
if (customTypeSelect.value) {
foreignKeySelect.value = "";
foreignKeySelect.dataset.selectedKey = "";
}
var option = tableCreateCustomColumnType(customTypeSelect.value);
if (
option &&
@ -747,6 +796,11 @@ function createTableColumnRow(state, column) {
typeSelect.value = option.fixedSqliteType;
syncTableCreateForeignKeyOptions(row, state);
}
syncTableCreateCustomTypeAndForeignKey(
row,
state,
row === state.columnList.firstElementChild,
);
});
}
pkInput.addEventListener("change", function () {
@ -757,6 +811,15 @@ function createTableColumnRow(state, column) {
foreignKeySelect.dataset.selectedKey = foreignKeySelect.value;
clearTableCreateDialogError(state);
updateTableCreateCustomColumnTypePlaceholder(foreignKeySelect);
if (customTypeSelect && foreignKeySelect.value) {
customTypeSelect.value = "";
updateTableCreateCustomColumnTypePlaceholder(customTypeSelect);
}
syncTableCreateCustomTypeAndForeignKey(
row,
state,
row === state.columnList.firstElementChild,
);
});
expandButton.addEventListener("click", function () {
@ -1664,11 +1727,19 @@ function createTableAlterColumnRow(state, column) {
customTypeLabel.className = "table-alter-detail-label";
customTypeLabel.setAttribute("for", customTypeId);
customTypeLabel.textContent = "Custom type";
var customTypeHelpId = "table-alter-column-custom-type-help-" + index;
var customTypeHelp = document.createElement("p");
customTypeHelp.id = customTypeHelpId;
customTypeHelp.className = "table-alter-detail-help";
customTypeHelp.textContent =
"Controls how Datasette displays and edits this column";
customTypeSelect = createTableAlterCustomColumnTypeSelect();
customTypeSelect.id = customTypeId;
customTypeSelect.setAttribute("aria-describedby", customTypeHelpId);
customTypeSelect.value = originalCustomType;
updateTableAlterCustomColumnTypePlaceholder(customTypeSelect);
customTypeField.appendChild(customTypeLabel);
customTypeField.appendChild(customTypeHelp);
customTypeField.appendChild(customTypeSelect);
}