From e834008075eead04c142a57275dcaa3f08d44d38 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 19 Jun 2026 22:32:37 -0700 Subject: [PATCH] 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. --- datasette/static/app.css | 3 +- datasette/static/edit-tools.js | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/datasette/static/app.css b/datasette/static/app.css index 49f070b1..06919444 100644 --- a/datasette/static/app.css +++ b/datasette/static/app.css @@ -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; diff --git a/datasette/static/edit-tools.js b/datasette/static/edit-tools.js index 5a9d0962..2a0a29b2 100644 --- a/datasette/static/edit-tools.js +++ b/datasette/static/edit-tools.js @@ -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); }