From c1f7f9599551157f7cb65dbef47304e92f4e2bb6 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Mon, 20 Apr 2026 13:52:39 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Stage=201=20is=20done.=20The=20widgets=20?= =?UTF-8?q?=E2=86=94=20forms=20circular=20dependency=20is=20broken:=20=20?= =?UTF-8?q?=20-=20autocompleteBar.ts=20now=20imports=20directly=20from=20.?= =?UTF-8?q?./../buttons=20and=20../../dragAndDrop=20=20=20-=20autocomplete?= =?UTF-8?q?Field.ts=20imports=20from=20../../error=20and=20../basic=20=20?= =?UTF-8?q?=20-=20autocompletePicker.ts=20imports=20from=20../../error=20?= =?UTF-8?q?=20=20-=20forms.js=20no=20longer=20imports=20../widgets=20at=20?= =?UTF-8?q?all?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 60 ++++++++++++++++--- src/widgets/forms.js | 5 +- .../forms/autocomplete/autocompleteBar.ts | 19 +++--- .../forms/autocomplete/autocompleteField.ts | 25 ++++---- .../forms/autocomplete/autocompletePicker.ts | 4 +- 5 files changed, 78 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62e48124..fdfc6be1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2194,9 +2194,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -2206,9 +2206,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -4862,6 +4862,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4879,6 +4882,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4896,6 +4902,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4913,6 +4922,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4930,6 +4942,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4947,6 +4962,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5062,9 +5080,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -5907,6 +5925,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5921,6 +5942,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5935,6 +5959,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5949,6 +5976,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5963,6 +5993,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5977,6 +6010,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5991,6 +6027,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6005,6 +6044,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ diff --git a/src/widgets/forms.js b/src/widgets/forms.js index 670baccd..ea624e68 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -23,7 +23,6 @@ import * as $rdf from 'rdflib' import { store } from 'solid-logic' import * as utils from '../utils' import { IconicMultiSelect } from './multiSelect' -import * as widgets from '../widgets' export { basicField, fieldLabel, fieldStore, renderNameValuePair } from './forms/basic' // Note default export export { field, mostSpecificClassURI, fieldFunction } from './forms/fieldFunction' @@ -1329,7 +1328,7 @@ export function makeDescription ( const editable = kb.updater.editable(dataDoc.uri) let submit if (editable) { - submit = widgets.continueButton(dom, saveChange) + submit = buttons.continueButton(dom, saveChange) submit.disabled = true // until the filled has been modified submit.style.visibility = 'hidden' submit.style.float = 'right' @@ -1879,7 +1878,7 @@ export function buildCheckboxForm (dom, kb, lab, del, ins, form, dataDoc, trista const negation = holdsAll(del) if (state && negation) { box.appendChild( - widgets.errorMessageBlock( + errorMessageBlock( dom, 'Inconsistent data in dataDoc!\n' + ins + ' and\n' + del ) diff --git a/src/widgets/forms/autocomplete/autocompleteBar.ts b/src/widgets/forms/autocomplete/autocompleteBar.ts index c58fa88a..9da16c9a 100644 --- a/src/widgets/forms/autocomplete/autocompleteBar.ts +++ b/src/widgets/forms/autocomplete/autocompleteBar.ts @@ -7,7 +7,8 @@ and so on. See the state diagram in the documentation. The AUtocomplete Picker import ns from '../../../ns' import { icons } from '../../../iconBase' import { store } from 'solid-logic' -import * as widgets from '../../../widgets' +import { askName, button, cancelButton as makeCancelButton, continueButton, deleteButtonWithCheck } from '../../buttons' +import { makeDropTarget } from '../../dragAndDrop' import * as utils from '../../../utils' import { renderAutoComplete, AutocompleteDecoration, setVisible } from './autocompletePicker' // dbpediaParameters @@ -40,7 +41,7 @@ export async function renderAutocompleteControl (dom:HTMLDocument, } async function greenButtonHandler (_event) { - const webid = await widgets.askName(dom, store, creationArea, ns.vcard('url'), undefined, WEBID_NOUN) + const webid = await askName(dom, store, creationArea, ns.vcard('url'), undefined, WEBID_NOUN) if (!webid) { return // cancelled by user } @@ -87,16 +88,16 @@ export async function renderAutocompleteControl (dom:HTMLDocument, } } - const acceptButton = widgets.continueButton(dom) + const acceptButton = continueButton(dom) acceptButton.setAttribute('data-testid', 'accept-button') - const cancelButton = widgets.cancelButton(dom) + const cancelButton = makeCancelButton(dom) cancelButton.setAttribute('data-testid', 'cancel-button') const deleteButtonContainer = dom.createElement('div') const noun = acOptions.targetClass ? utils.label(acOptions.targetClass) : 'item' - const deleteButton = widgets.deleteButtonWithCheck(dom, deleteButtonContainer, noun, deleteOne) // need to knock out this UI or caller does that + const deleteButton = deleteButtonWithCheck(dom, deleteButtonContainer, noun, deleteOne) // need to knock out this UI or caller does that deleteButton.setAttribute('data-testid', 'delete-button') - const editButton = widgets.button(dom, EDIT_ICON, 'Edit', _event => { + const editButton = button(dom, EDIT_ICON, 'Edit', _event => { editing = !editing syncEditingStatus() }) @@ -132,11 +133,11 @@ export async function renderAutocompleteControl (dom:HTMLDocument, // creationArea.appendChild(await renderAutoComplete(dom, barOptions, autoCompleteDone)) wait for searchButton creationArea.style.width = '100%' if (barOptions.manualURIEntry) { - const plus = creationArea.appendChild(widgets.button(dom, GREEN_PLUS, barOptions.idNoun, greenButtonHandler)) - widgets.makeDropTarget(plus, droppedURIHandler, undefined) + const plus = creationArea.appendChild(button(dom, GREEN_PLUS, barOptions.idNoun, greenButtonHandler)) + makeDropTarget(plus, droppedURIHandler, undefined) } if (barOptions.dbLookup && !acOptions.currentObject && !acOptions.permanent) { - creationArea.appendChild(widgets.button(dom, SEARCH_ICON, barOptions.idNoun, searchButtonHandler)) + creationArea.appendChild(button(dom, SEARCH_ICON, barOptions.idNoun, searchButtonHandler)) } } syncEditingStatus() diff --git a/src/widgets/forms/autocomplete/autocompleteField.ts b/src/widgets/forms/autocomplete/autocompleteField.ts index ccb3a8df..22fb8809 100644 --- a/src/widgets/forms/autocomplete/autocompleteField.ts +++ b/src/widgets/forms/autocomplete/autocompleteField.ts @@ -2,8 +2,9 @@ */ import ns from '../../../ns' import { store } from 'solid-logic' -import * as widgets from '../../../widgets' -import { style } from '../../../style' +import { errorMessageBlock } from '../../error' +import { fieldLabel } from '../basic' +import * as style from '../../../style' import { renderAutocompleteControl } from './autocompleteBar' import { QueryParameters } from './publicData' import { NamedNode, BlankNode, Literal, Variable, st } from 'rdflib' @@ -67,7 +68,7 @@ export function autocompleteField ( await kb.updater?.updateMany(deletables, insertables) } catch (err) { callbackFunction(false, err) - box.appendChild(widgets.errorMessageBlock(dom, 'Autocomplete form data update error:' + err, null, err)) + box.appendChild(errorMessageBlock(dom, 'Autocomplete form data update error:' + err, err)) return } callbackFunction(true, '') @@ -77,7 +78,7 @@ export function autocompleteField ( const oldValue = kb.the(subject, property as any, null, doc) if (!oldValue) { callbackFunction(false, 'NO data to elete') - box.appendChild(widgets.errorMessageBlock(dom, 'Autocomplete delete: no old data!')) + box.appendChild(errorMessageBlock(dom, 'Autocomplete delete: no old data!')) return } // const oldName = kb.any(oldValue as any, labelProperty as any, null, doc) @@ -92,7 +93,7 @@ export function autocompleteField ( } catch (err) { const e2 = new Error('Autocomplete form data delete error:' + err) callbackFunction(false, err) - box.appendChild(widgets.errorMessageBlock(dom, e2, null, err)) + box.appendChild(errorMessageBlock(dom, e2, err)) return } callbackFunction(true, '') // changed @@ -117,7 +118,7 @@ export function autocompleteField ( const property = kb.any(form, ns.ui('property')) if (!property) { return box.appendChild( - widgets.errorMessageBlock(dom, 'Error: No property given for autocomplete field: ' + form) + errorMessageBlock(dom, 'Error: No property given for autocomplete field: ' + form) ) } const labelProperty = kb.any(form, ns.ui('labelProperty')) || ns.schema('name') @@ -128,7 +129,7 @@ export function autocompleteField ( if (!dataSource) { // console.log('@@ connectedStatements ACF ', kb.connectedStatements(form).map(x => x.toNT()).join('\n')) return box.appendChild( - widgets.errorMessageBlock(dom, 'Error: No data source given for autocomplete field: ' + form) + errorMessageBlock(dom, 'Error: No data source given for autocomplete field: ' + form) ) } const queryParams:QueryParameters = { @@ -163,16 +164,16 @@ export function autocompleteField ( queryParams.searchByNameQuery = kb.anyJS(dataSource, ns.ui('searchByNameQuery'), null, dataSource.doc()) if (!queryParams.searchByNameQuery) { return box.appendChild( - widgets.errorMessageBlock(dom, 'Error: No searchByNameQuery given for endpoint data Source: ' + form)) + errorMessageBlock(dom, 'Error: No searchByNameQuery given for endpoint data Source: ' + form)) } queryParams.insitituteDetailsQuery = kb.anyJS(dataSource, ns.ui('insitituteDetailsQuery'), null, dataSource.doc()) } else { // return box.appendChild( - // widgets.errorMessageBlock(dom, 'Error: No SPARQL endpoint given for autocomplete field: ' + form)) + // errorMessageBlock(dom, 'Error: No SPARQL endpoint given for autocomplete field: ' + form)) const searchByNameURI = kb.anyJS(dataSource, ns.ui('searchByNameURI')) if (!searchByNameURI) { return box.appendChild( - widgets.errorMessageBlock(dom, 'Error: No searchByNameURI OR sparql endpoint given for dataSource: ' + dataSource) + errorMessageBlock(dom, 'Error: No searchByNameURI OR sparql endpoint given for dataSource: ' + dataSource) ) } queryParams.searchByNameURI = searchByNameURI @@ -206,7 +207,7 @@ export function autocompleteField ( autocompleteOptions.currentName = kb.any(autocompleteOptions.currentObject, labelProperty, null, doc) as Literal } - lhs.appendChild(widgets.fieldLabel(dom, property as any, form)) + lhs.appendChild(fieldLabel(dom, property as any, form)) const barOptions = { editable, @@ -216,7 +217,7 @@ export function autocompleteField ( renderAutocompleteControl(dom, subject as NamedNode, barOptions, autocompleteOptions, addOneIdAndRefresh, deleteOne).then((control) => { rhs.appendChild(control) }, (err) => { - rhs.appendChild(widgets.errorMessageBlock(dom, `Error rendering autocomplete ${form}: ${err}`, '#fee', err)) // + rhs.appendChild(errorMessageBlock(dom, `Error rendering autocomplete ${form}: ${err}`, '#fee', err)) // }) return box } diff --git a/src/widgets/forms/autocomplete/autocompletePicker.ts b/src/widgets/forms/autocomplete/autocompletePicker.ts index fc7f865d..a56caadc 100644 --- a/src/widgets/forms/autocomplete/autocompletePicker.ts +++ b/src/widgets/forms/autocomplete/autocompletePicker.ts @@ -6,7 +6,7 @@ import * as debug from '../../../debug' import { style } from '../../../style' import styleConstants from '../../../styleConstants' -import * as widgets from '../../../widgets' +import { errorMessageBlock } from '../../error' import { store } from 'solid-logic' import { NamedNode, Literal } from 'rdflib' import { queryPublicDataByName, bindingToTerm, AUTOCOMPLETE_LIMIT, QueryParameters } from './publicData' @@ -61,7 +61,7 @@ export async function renderAutoComplete (dom: HTMLDocument, const errorRow = table.appendChild(dom.createElement('tr')) debug.log(message) const err = new Error(message) - errorRow.appendChild(widgets.errorMessageBlock(dom, err, 'pink')) + errorRow.appendChild(errorMessageBlock(dom, err, 'pink')) // errorMessageBlock will log the stack to the console style.setStyle(errorRow, 'autocompleteRowStyle') errorRow.style.padding = '1em' From 879e309ba4801677aa09e730850c40185452c6f1 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Mon, 20 Apr 2026 14:15:21 +0100 Subject: [PATCH 2/4] =?UTF-8?q?-=20error.ts=20=E2=86=94=20widgets=20index?= =?UTF-8?q?=20cycle:=20broke=20#778?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/error.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/widgets/error.ts b/src/widgets/error.ts index 3ab1c578..83238a32 100644 --- a/src/widgets/error.ts +++ b/src/widgets/error.ts @@ -10,9 +10,8 @@ * Meanwhile the stack is dumped to the console for the developer, so you actually know * where it happened! */ - -import { cancelButton } from '../widgets' -import { style } from '../style' +import { cancelButton } from './buttons' +import * as style from '../style' import styleConstants from '../styleConstants' export function errorMessageBlock (dom: HTMLDocument, err: string | Error, backgroundColor?: string, err2?: Error): HTMLDivElement { From 62961620059666999a274158b9c5755155e7e391 Mon Sep 17 00:00:00 2001 From: timea-solid <4144203+timea-solid@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:37:38 +0200 Subject: [PATCH 3/4] cancel and continue button handlers #778 --- src/widgets/buttons.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/buttons.ts b/src/widgets/buttons.ts index 51c459e0..ed94c584 100644 --- a/src/widgets/buttons.ts +++ b/src/widgets/buttons.ts @@ -636,7 +636,7 @@ export function button (dom: HTMLDocument, iconURI: string | undefined, text: st * * @returns - the button */ -export function cancelButton (dom: HTMLDocument, handler: (_event?: any) => void) { +export function cancelButton (dom: HTMLDocument, handler?: (_event?: any) => void) { const b = button(dom, cancelIconURI, 'Cancel', handler) if (b.firstChild) { // sigh for tsc (b.firstChild as HTMLElement).style.opacity = '0.3' // Black X is too harsh: current language is grey X @@ -651,7 +651,7 @@ export function cancelButton (dom: HTMLDocument, handler: (_event?: any) => void * * @returns - the button */ -export function continueButton (dom: HTMLDocument, handler: (_event: any) => void) { +export function continueButton (dom: HTMLDocument, handler?: (_event: any) => void) { return button(dom, checkIconURI, 'Continue', handler) } From 18d02c5d31bac449c26283deeacf046be2c4e4d0 Mon Sep 17 00:00:00 2001 From: timea-solid <4144203+timea-solid@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:38:25 +0200 Subject: [PATCH 4/4] update snapshots for tests #778 --- test/unit/widgets/__snapshots__/error.test.ts.snap | 12 ++++++------ .../widgets/forms/__snapshots__/basic.test.ts.snap | 4 ++-- .../__snapshots__/autocomplete.test.ts.snap | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/unit/widgets/__snapshots__/error.test.ts.snap b/test/unit/widgets/__snapshots__/error.test.ts.snap index 3565142d..841fccaa 100644 --- a/test/unit/widgets/__snapshots__/error.test.ts.snap +++ b/test/unit/widgets/__snapshots__/error.test.ts.snap @@ -2,11 +2,11 @@ exports[`errorMessageBlock creates an error message div 1`] = `
my error message