@lo-tek/interfaces 0.0.0-stage → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3254 -0
- package/LICENSE +21 -0
- package/README.md +129 -2
- package/agent/astro.md +400 -0
- package/agent/bundles.md +429 -0
- package/agent/datastar.md +409 -0
- package/agent/html.md +388 -0
- package/agent/react.md +440 -0
- package/agent/templates.md +472 -0
- package/core/lotek-core.wasm +0 -0
- package/core/lotek.wasm +0 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -0
- package/dist/components/css/accordion/accordion.d.ts +37 -0
- package/dist/components/css/accordion/accordion.d.ts.map +1 -0
- package/dist/components/css/accordion/accordion.js +31 -0
- package/dist/components/css/accordion/accordion.js.map +1 -0
- package/dist/components/css/alert/alert.d.ts +35 -0
- package/dist/components/css/alert/alert.d.ts.map +1 -0
- package/dist/components/css/alert/alert.js +29 -0
- package/dist/components/css/alert/alert.js.map +1 -0
- package/dist/components/css/avatar/avatar.d.ts +40 -0
- package/dist/components/css/avatar/avatar.d.ts.map +1 -0
- package/dist/components/css/avatar/avatar.js +30 -0
- package/dist/components/css/avatar/avatar.js.map +1 -0
- package/dist/components/css/badge/badge.d.ts +24 -0
- package/dist/components/css/badge/badge.d.ts.map +1 -0
- package/dist/components/css/badge/badge.js +24 -0
- package/dist/components/css/badge/badge.js.map +1 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +26 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.d.ts.map +1 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.js +24 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/components/css/button/button.d.ts +26 -0
- package/dist/components/css/button/button.d.ts.map +1 -0
- package/dist/components/css/button/button.js +25 -0
- package/dist/components/css/button/button.js.map +1 -0
- package/dist/components/css/card/card.d.ts +36 -0
- package/dist/components/css/card/card.d.ts.map +1 -0
- package/dist/components/css/card/card.js +28 -0
- package/dist/components/css/card/card.js.map +1 -0
- package/dist/components/css/checkbox/checkbox.d.ts +32 -0
- package/dist/components/css/checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/css/checkbox/checkbox.js +27 -0
- package/dist/components/css/checkbox/checkbox.js.map +1 -0
- package/dist/components/css/divider/divider.d.ts +29 -0
- package/dist/components/css/divider/divider.d.ts.map +1 -0
- package/dist/components/css/divider/divider.js +23 -0
- package/dist/components/css/divider/divider.js.map +1 -0
- package/dist/components/css/error-text/error-text.d.ts +24 -0
- package/dist/components/css/error-text/error-text.d.ts.map +1 -0
- package/dist/components/css/error-text/error-text.js +18 -0
- package/dist/components/css/error-text/error-text.js.map +1 -0
- package/dist/components/css/fieldset/fieldset.d.ts +55 -0
- package/dist/components/css/fieldset/fieldset.d.ts.map +1 -0
- package/dist/components/css/fieldset/fieldset.js +39 -0
- package/dist/components/css/fieldset/fieldset.js.map +1 -0
- package/dist/components/css/file-upload/file-upload.d.ts +41 -0
- package/dist/components/css/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/css/file-upload/file-upload.js +32 -0
- package/dist/components/css/file-upload/file-upload.js.map +1 -0
- package/dist/components/css/heading/heading.d.ts +29 -0
- package/dist/components/css/heading/heading.d.ts.map +1 -0
- package/dist/components/css/heading/heading.js +23 -0
- package/dist/components/css/heading/heading.js.map +1 -0
- package/dist/components/css/help-text/help-text.d.ts +24 -0
- package/dist/components/css/help-text/help-text.d.ts.map +1 -0
- package/dist/components/css/help-text/help-text.js +23 -0
- package/dist/components/css/help-text/help-text.js.map +1 -0
- package/dist/components/css/icon/icon.d.ts +34 -0
- package/dist/components/css/icon/icon.d.ts.map +1 -0
- package/dist/components/css/icon/icon.js +25 -0
- package/dist/components/css/icon/icon.js.map +1 -0
- package/dist/components/css/input/input.d.ts +40 -0
- package/dist/components/css/input/input.d.ts.map +1 -0
- package/dist/components/css/input/input.js +45 -0
- package/dist/components/css/input/input.js.map +1 -0
- package/dist/components/css/label/label.d.ts +33 -0
- package/dist/components/css/label/label.d.ts.map +1 -0
- package/dist/components/css/label/label.js +23 -0
- package/dist/components/css/label/label.js.map +1 -0
- package/dist/components/css/link/link.d.ts +28 -0
- package/dist/components/css/link/link.d.ts.map +1 -0
- package/dist/components/css/link/link.js +20 -0
- package/dist/components/css/link/link.js.map +1 -0
- package/dist/components/css/list/list.d.ts +31 -0
- package/dist/components/css/list/list.d.ts.map +1 -0
- package/dist/components/css/list/list.js +25 -0
- package/dist/components/css/list/list.js.map +1 -0
- package/dist/components/css/pagination/pagination.d.ts +27 -0
- package/dist/components/css/pagination/pagination.d.ts.map +1 -0
- package/dist/components/css/pagination/pagination.js +25 -0
- package/dist/components/css/pagination/pagination.js.map +1 -0
- package/dist/components/css/paragraph/paragraph.d.ts +36 -0
- package/dist/components/css/paragraph/paragraph.d.ts.map +1 -0
- package/dist/components/css/paragraph/paragraph.js +26 -0
- package/dist/components/css/paragraph/paragraph.js.map +1 -0
- package/dist/components/css/progress/progress.d.ts +53 -0
- package/dist/components/css/progress/progress.d.ts.map +1 -0
- package/dist/components/css/progress/progress.js +38 -0
- package/dist/components/css/progress/progress.js.map +1 -0
- package/dist/components/css/radio/radio.d.ts +32 -0
- package/dist/components/css/radio/radio.d.ts.map +1 -0
- package/dist/components/css/radio/radio.js +27 -0
- package/dist/components/css/radio/radio.js.map +1 -0
- package/dist/components/css/search/search.d.ts +34 -0
- package/dist/components/css/search/search.d.ts.map +1 -0
- package/dist/components/css/search/search.js +32 -0
- package/dist/components/css/search/search.js.map +1 -0
- package/dist/components/css/select/select.d.ts +40 -0
- package/dist/components/css/select/select.d.ts.map +1 -0
- package/dist/components/css/select/select.js +31 -0
- package/dist/components/css/select/select.js.map +1 -0
- package/dist/components/css/shared.d.ts +90 -0
- package/dist/components/css/shared.d.ts.map +1 -0
- package/dist/components/css/shared.js +167 -0
- package/dist/components/css/shared.js.map +1 -0
- package/dist/components/css/skeleton/skeleton.d.ts +29 -0
- package/dist/components/css/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/css/skeleton/skeleton.js +23 -0
- package/dist/components/css/skeleton/skeleton.js.map +1 -0
- package/dist/components/css/skip-link/skip-link.d.ts +18 -0
- package/dist/components/css/skip-link/skip-link.d.ts.map +1 -0
- package/dist/components/css/skip-link/skip-link.js +16 -0
- package/dist/components/css/skip-link/skip-link.js.map +1 -0
- package/dist/components/css/spinner/spinner.d.ts +33 -0
- package/dist/components/css/spinner/spinner.d.ts.map +1 -0
- package/dist/components/css/spinner/spinner.js +25 -0
- package/dist/components/css/spinner/spinner.js.map +1 -0
- package/dist/components/css/sr-only/sr-only.d.ts +17 -0
- package/dist/components/css/sr-only/sr-only.d.ts.map +1 -0
- package/dist/components/css/sr-only/sr-only.js +15 -0
- package/dist/components/css/sr-only/sr-only.js.map +1 -0
- package/dist/components/css/switch/switch.d.ts +37 -0
- package/dist/components/css/switch/switch.d.ts.map +1 -0
- package/dist/components/css/switch/switch.js +29 -0
- package/dist/components/css/switch/switch.js.map +1 -0
- package/dist/components/css/table/table.d.ts +76 -0
- package/dist/components/css/table/table.d.ts.map +1 -0
- package/dist/components/css/table/table.js +59 -0
- package/dist/components/css/table/table.js.map +1 -0
- package/dist/components/css/tag/tag.d.ts +35 -0
- package/dist/components/css/tag/tag.d.ts.map +1 -0
- package/dist/components/css/tag/tag.js +27 -0
- package/dist/components/css/tag/tag.js.map +1 -0
- package/dist/components/css/textarea/textarea.d.ts +23 -0
- package/dist/components/css/textarea/textarea.d.ts.map +1 -0
- package/dist/components/css/textarea/textarea.js +18 -0
- package/dist/components/css/textarea/textarea.js.map +1 -0
- package/dist/components/css/toggle-group/toggle-group.d.ts +31 -0
- package/dist/components/css/toggle-group/toggle-group.d.ts.map +1 -0
- package/dist/components/css/toggle-group/toggle-group.js +29 -0
- package/dist/components/css/toggle-group/toggle-group.js.map +1 -0
- package/dist/components/css/tooltip/tooltip.d.ts +31 -0
- package/dist/components/css/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/css/tooltip/tooltip.js +29 -0
- package/dist/components/css/tooltip/tooltip.js.map +1 -0
- package/dist/components/default-texts.d.ts +35 -0
- package/dist/components/default-texts.d.ts.map +1 -0
- package/dist/components/default-texts.js +35 -0
- package/dist/components/default-texts.js.map +1 -0
- package/dist/components/events.d.ts +77 -0
- package/dist/components/events.d.ts.map +1 -0
- package/dist/components/events.js +3 -0
- package/dist/components/events.js.map +1 -0
- package/dist/components/frame/dialog/dialog.d.ts +122 -0
- package/dist/components/frame/dialog/dialog.d.ts.map +1 -0
- package/dist/components/frame/dialog/dialog.js +112 -0
- package/dist/components/frame/dialog/dialog.js.map +1 -0
- package/dist/components/frame/dialog/lt-dialog.d.ts +104 -0
- package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -0
- package/dist/components/frame/dialog/lt-dialog.js +431 -0
- package/dist/components/frame/dialog/lt-dialog.js.map +1 -0
- package/dist/components/frame/error-summary/error-summary.d.ts +40 -0
- package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -0
- package/dist/components/frame/error-summary/error-summary.js +32 -0
- package/dist/components/frame/error-summary/error-summary.js.map +1 -0
- package/dist/components/frame/error-summary/lt-error-summary.d.ts +82 -0
- package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -0
- package/dist/components/frame/error-summary/lt-error-summary.js +302 -0
- package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -0
- package/dist/components/frame/field/field-core.d.ts +92 -0
- package/dist/components/frame/field/field-core.d.ts.map +1 -0
- package/dist/components/frame/field/field-core.js +63 -0
- package/dist/components/frame/field/field-core.js.map +1 -0
- package/dist/components/frame/field/lt-field.d.ts +173 -0
- package/dist/components/frame/field/lt-field.d.ts.map +1 -0
- package/dist/components/frame/field/lt-field.js +580 -0
- package/dist/components/frame/field/lt-field.js.map +1 -0
- package/dist/components/frame/popover/lt-popover.d.ts +132 -0
- package/dist/components/frame/popover/lt-popover.d.ts.map +1 -0
- package/dist/components/frame/popover/lt-popover.js +490 -0
- package/dist/components/frame/popover/lt-popover.js.map +1 -0
- package/dist/components/frame/popover/popover.d.ts +49 -0
- package/dist/components/frame/popover/popover.d.ts.map +1 -0
- package/dist/components/frame/popover/popover.js +64 -0
- package/dist/components/frame/popover/popover.js.map +1 -0
- package/dist/components/frame/session-timeout/lt-session-timeout.d.ts +198 -0
- package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -0
- package/dist/components/frame/session-timeout/lt-session-timeout.js +573 -0
- package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -0
- package/dist/components/frame/session-timeout/session-timeout.d.ts +113 -0
- package/dist/components/frame/session-timeout/session-timeout.d.ts.map +1 -0
- package/dist/components/frame/session-timeout/session-timeout.js +101 -0
- package/dist/components/frame/session-timeout/session-timeout.js.map +1 -0
- package/dist/components/frame/suggestion/lt-suggestion.d.ts +361 -0
- package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -0
- package/dist/components/frame/suggestion/lt-suggestion.js +1152 -0
- package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -0
- package/dist/components/frame/suggestion/suggestion.d.ts +143 -0
- package/dist/components/frame/suggestion/suggestion.d.ts.map +1 -0
- package/dist/components/frame/suggestion/suggestion.js +127 -0
- package/dist/components/frame/suggestion/suggestion.js.map +1 -0
- package/dist/components/frame/tabs/lt-tabs.d.ts +404 -0
- package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -0
- package/dist/components/frame/tabs/lt-tabs.js +1201 -0
- package/dist/components/frame/tabs/lt-tabs.js.map +1 -0
- package/dist/components/frame/tabs/tabs.d.ts +112 -0
- package/dist/components/frame/tabs/tabs.d.ts.map +1 -0
- package/dist/components/frame/tabs/tabs.js +107 -0
- package/dist/components/frame/tabs/tabs.js.map +1 -0
- package/dist/components/host-element.d.ts +135 -0
- package/dist/components/host-element.d.ts.map +1 -0
- package/dist/components/host-element.js +366 -0
- package/dist/components/host-element.js.map +1 -0
- package/dist/components/standalone/connection-status/connection-status.d.ts +28 -0
- package/dist/components/standalone/connection-status/connection-status.d.ts.map +1 -0
- package/dist/components/standalone/connection-status/connection-status.js +22 -0
- package/dist/components/standalone/connection-status/connection-status.js.map +1 -0
- package/dist/components/standalone/connection-status/lt-connection-status.d.ts +81 -0
- package/dist/components/standalone/connection-status/lt-connection-status.d.ts.map +1 -0
- package/dist/components/standalone/connection-status/lt-connection-status.js +216 -0
- package/dist/components/standalone/connection-status/lt-connection-status.js.map +1 -0
- package/dist/components/standalone/toast/lt-toast.d.ts +109 -0
- package/dist/components/standalone/toast/lt-toast.d.ts.map +1 -0
- package/dist/components/standalone/toast/lt-toast.js +256 -0
- package/dist/components/standalone/toast/lt-toast.js.map +1 -0
- package/dist/components/standalone/toast/toast.d.ts +52 -0
- package/dist/components/standalone/toast/toast.d.ts.map +1 -0
- package/dist/components/standalone/toast/toast.js +42 -0
- package/dist/components/standalone/toast/toast.js.map +1 -0
- package/dist/diagnostics/core.d.ts +137 -0
- package/dist/diagnostics/core.d.ts.map +1 -0
- package/dist/diagnostics/core.js +72 -0
- package/dist/diagnostics/core.js.map +1 -0
- package/dist/diagnostics/default-core.d.ts +10 -0
- package/dist/diagnostics/default-core.d.ts.map +1 -0
- package/dist/diagnostics/default-core.js +72 -0
- package/dist/diagnostics/default-core.js.map +1 -0
- package/dist/diagnostics/index.d.ts +84 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +106 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/dom.d.ts +55 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +162 -0
- package/dist/dom.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx/react.d.ts +180 -0
- package/dist/jsx/react.d.ts.map +1 -0
- package/dist/jsx/react.js +2 -0
- package/dist/jsx/react.js.map +1 -0
- package/dist/lotek.css +3617 -0
- package/dist/lt.d.ts +325 -0
- package/dist/lt.d.ts.map +1 -0
- package/dist/lt.js +183 -0
- package/dist/lt.js.map +1 -0
- package/dist/react.d.ts +492 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +296 -0
- package/dist/react.js.map +1 -0
- package/dist/register.d.ts +17 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +35 -0
- package/dist/register.js.map +1 -0
- package/dist/tokens/color.d.ts +43 -0
- package/dist/tokens/color.d.ts.map +1 -0
- package/dist/tokens/color.js +123 -0
- package/dist/tokens/color.js.map +1 -0
- package/dist/tokens/contract.d.ts +18 -0
- package/dist/tokens/contract.d.ts.map +1 -0
- package/dist/tokens/contract.js +18 -0
- package/dist/tokens/contract.js.map +1 -0
- package/dist/tokens/matrix-cells.d.ts +30 -0
- package/dist/tokens/matrix-cells.d.ts.map +1 -0
- package/dist/tokens/matrix-cells.js +273 -0
- package/dist/tokens/matrix-cells.js.map +1 -0
- package/dist/tokens/matrix.d.ts +18 -0
- package/dist/tokens/matrix.d.ts.map +1 -0
- package/dist/tokens/matrix.js +21 -0
- package/dist/tokens/matrix.js.map +1 -0
- package/dist/tokens/theme-check.d.ts +42 -0
- package/dist/tokens/theme-check.d.ts.map +1 -0
- package/dist/tokens/theme-check.js +10 -0
- package/dist/tokens/theme-check.js.map +1 -0
- package/dist/tokens/theme.d.ts +94 -0
- package/dist/tokens/theme.d.ts.map +1 -0
- package/dist/tokens/theme.js +14 -0
- package/dist/tokens/theme.js.map +1 -0
- package/dist/tokens/tokens.d.ts +122 -0
- package/dist/tokens/tokens.d.ts.map +1 -0
- package/dist/tokens/tokens.js +158 -0
- package/dist/tokens/tokens.js.map +1 -0
- package/dist/vocabulary/classes.d.ts +3 -0
- package/dist/vocabulary/classes.d.ts.map +1 -0
- package/dist/vocabulary/classes.js +1320 -0
- package/dist/vocabulary/classes.js.map +1 -0
- package/dist/vocabulary/elements.d.ts +3 -0
- package/dist/vocabulary/elements.d.ts.map +1 -0
- package/dist/vocabulary/elements.js +410 -0
- package/dist/vocabulary/elements.js.map +1 -0
- package/dist/vocabulary/types.d.ts +175 -0
- package/dist/vocabulary/types.d.ts.map +1 -0
- package/dist/vocabulary/types.js +11 -0
- package/dist/vocabulary/types.js.map +1 -0
- package/dist/wasi-host.d.ts +24 -0
- package/dist/wasi-host.d.ts.map +1 -0
- package/dist/wasi-host.js +140 -0
- package/dist/wasi-host.js.map +1 -0
- package/live-templates.xml +62 -0
- package/manifest/files.json +373 -0
- package/manifest/manifest.json +4965 -0
- package/manifest/manifest.schema.json +513 -0
- package/package.json +404 -4
- package/src/cli.ts +21 -0
- package/src/components/css/accordion/accordion.css +102 -0
- package/src/components/css/accordion/accordion.ts +50 -0
- package/src/components/css/alert/alert.css +53 -0
- package/src/components/css/alert/alert.ts +48 -0
- package/src/components/css/avatar/avatar.css +96 -0
- package/src/components/css/avatar/avatar.ts +57 -0
- package/src/components/css/badge/badge.css +42 -0
- package/src/components/css/badge/badge.ts +43 -0
- package/src/components/css/breadcrumbs/breadcrumbs.css +62 -0
- package/src/components/css/breadcrumbs/breadcrumbs.ts +30 -0
- package/src/components/css/button/button.css +128 -0
- package/src/components/css/button/button.ts +44 -0
- package/src/components/css/card/card.css +65 -0
- package/src/components/css/card/card.ts +50 -0
- package/src/components/css/checkbox/checkbox.css +114 -0
- package/src/components/css/checkbox/checkbox.ts +50 -0
- package/src/components/css/divider/divider.css +38 -0
- package/src/components/css/divider/divider.ts +42 -0
- package/src/components/css/error-text/error-text.css +21 -0
- package/src/components/css/error-text/error-text.ts +37 -0
- package/src/components/css/fieldset/fieldset.css +72 -0
- package/src/components/css/fieldset/fieldset.ts +87 -0
- package/src/components/css/file-upload/file-upload.css +102 -0
- package/src/components/css/file-upload/file-upload.ts +65 -0
- package/src/components/css/heading/heading.css +33 -0
- package/src/components/css/heading/heading.ts +42 -0
- package/src/components/css/help-text/help-text.css +24 -0
- package/src/components/css/help-text/help-text.ts +42 -0
- package/src/components/css/icon/icon.css +20 -0
- package/src/components/css/icon/icon.ts +44 -0
- package/src/components/css/input/input.css +145 -0
- package/src/components/css/input/input.ts +77 -0
- package/src/components/css/label/label.css +42 -0
- package/src/components/css/label/label.ts +51 -0
- package/src/components/css/link/link.css +38 -0
- package/src/components/css/link/link.ts +38 -0
- package/src/components/css/list/list.css +53 -0
- package/src/components/css/list/list.ts +44 -0
- package/src/components/css/pagination/pagination.css +95 -0
- package/src/components/css/pagination/pagination.ts +33 -0
- package/src/components/css/paragraph/paragraph.css +31 -0
- package/src/components/css/paragraph/paragraph.ts +53 -0
- package/src/components/css/progress/progress.css +161 -0
- package/src/components/css/progress/progress.ts +78 -0
- package/src/components/css/radio/radio.css +94 -0
- package/src/components/css/radio/radio.ts +50 -0
- package/src/components/css/search/search.css +50 -0
- package/src/components/css/search/search.ts +56 -0
- package/src/components/css/select/select.css +321 -0
- package/src/components/css/select/select.ts +63 -0
- package/src/components/css/shared.ts +199 -0
- package/src/components/css/skeleton/skeleton.css +62 -0
- package/src/components/css/skeleton/skeleton.ts +43 -0
- package/src/components/css/skip-link/skip-link.css +41 -0
- package/src/components/css/skip-link/skip-link.ts +22 -0
- package/src/components/css/spinner/spinner.css +48 -0
- package/src/components/css/spinner/spinner.ts +48 -0
- package/src/components/css/sr-only/sr-only.css +16 -0
- package/src/components/css/sr-only/sr-only.ts +21 -0
- package/src/components/css/switch/switch.css +105 -0
- package/src/components/css/switch/switch.ts +50 -0
- package/src/components/css/table/table.css +159 -0
- package/src/components/css/table/table.ts +107 -0
- package/src/components/css/tag/tag.css +58 -0
- package/src/components/css/tag/tag.ts +49 -0
- package/src/components/css/textarea/textarea.css +83 -0
- package/src/components/css/textarea/textarea.ts +40 -0
- package/src/components/css/toggle-group/toggle-group.css +92 -0
- package/src/components/css/toggle-group/toggle-group.ts +37 -0
- package/src/components/css/tooltip/tooltip.css +89 -0
- package/src/components/css/tooltip/tooltip.ts +37 -0
- package/src/components/default-texts.ts +34 -0
- package/src/components/events.ts +68 -0
- package/src/components/frame/dialog/dialog.css +306 -0
- package/src/components/frame/dialog/dialog.ts +166 -0
- package/src/components/frame/dialog/lt-dialog.ts +510 -0
- package/src/components/frame/error-summary/error-summary.css +47 -0
- package/src/components/frame/error-summary/error-summary.ts +56 -0
- package/src/components/frame/error-summary/lt-error-summary.ts +344 -0
- package/src/components/frame/field/field-core.ts +152 -0
- package/src/components/frame/field/field.css +17 -0
- package/src/components/frame/field/lt-field.ts +753 -0
- package/src/components/frame/popover/lt-popover.ts +555 -0
- package/src/components/frame/popover/popover.css +50 -0
- package/src/components/frame/popover/popover.ts +102 -0
- package/src/components/frame/session-timeout/lt-session-timeout.ts +681 -0
- package/src/components/frame/session-timeout/session-timeout.css +50 -0
- package/src/components/frame/session-timeout/session-timeout.ts +156 -0
- package/src/components/frame/suggestion/lt-suggestion.ts +1358 -0
- package/src/components/frame/suggestion/suggestion.css +132 -0
- package/src/components/frame/suggestion/suggestion.ts +172 -0
- package/src/components/frame/tabs/lt-tabs.ts +1319 -0
- package/src/components/frame/tabs/tabs.css +128 -0
- package/src/components/frame/tabs/tabs.ts +188 -0
- package/src/components/host-element.ts +400 -0
- package/src/components/standalone/connection-status/connection-status.css +41 -0
- package/src/components/standalone/connection-status/connection-status.ts +41 -0
- package/src/components/standalone/connection-status/lt-connection-status.ts +242 -0
- package/src/components/standalone/toast/lt-toast.ts +300 -0
- package/src/components/standalone/toast/toast.css +78 -0
- package/src/components/standalone/toast/toast.ts +70 -0
- package/src/diagnostics/core.ts +210 -0
- package/src/diagnostics/default-core.ts +81 -0
- package/src/diagnostics/index.ts +150 -0
- package/src/dom.ts +185 -0
- package/src/index.ts +218 -0
- package/src/jsx/react.ts +212 -0
- package/src/lt.ts +192 -0
- package/src/react.ts +411 -0
- package/src/register.ts +43 -0
- package/src/tailwind/tailwind.css +169 -0
- package/src/tokens/boundary.css +104 -0
- package/src/tokens/color-contract.json +231 -0
- package/src/tokens/color.ts +150 -0
- package/src/tokens/contract.ts +24 -0
- package/src/tokens/matrix-cells.ts +375 -0
- package/src/tokens/matrix.ts +32 -0
- package/src/tokens/theme-check.ts +51 -0
- package/src/tokens/theme.ts +104 -0
- package/src/tokens/tokens.css +569 -0
- package/src/tokens/tokens.ts +223 -0
- package/src/vocabulary/classes.ts +1322 -0
- package/src/vocabulary/elements.ts +412 -0
- package/src/vocabulary/types.ts +178 -0
- package/src/wasi-host.ts +155 -0
- package/web-types.json +800 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import { ERROR_TEXT_CLASS } from "../../css/error-text/error-text.js"
|
|
2
|
+
import { HELP_TEXT_CLASS } from "../../css/help-text/help-text.js"
|
|
3
|
+
import {
|
|
4
|
+
addClass,
|
|
5
|
+
defineElement,
|
|
6
|
+
HostElement,
|
|
7
|
+
setAttr,
|
|
8
|
+
setFlag,
|
|
9
|
+
uniqueId,
|
|
10
|
+
warnAboutMarkup,
|
|
11
|
+
} from "../../host-element.js"
|
|
12
|
+
import { computeFieldAttributes } from "./field-core.js"
|
|
13
|
+
|
|
14
|
+
export const LT_FIELD_TAG = "lt-field" as const
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The parts the field looks for, as selectors relative to the element. The
|
|
18
|
+
* component finds its parts with these, and the manifest describes them for
|
|
19
|
+
* whoever assembles the markup by hand, so the two cannot say different
|
|
20
|
+
* things.
|
|
21
|
+
*/
|
|
22
|
+
export const LT_FIELD_PARTS = {
|
|
23
|
+
label: "label",
|
|
24
|
+
control: "input:not([type='hidden']), textarea, select",
|
|
25
|
+
help: `.${HELP_TEXT_CLASS}`,
|
|
26
|
+
error: `.${ERROR_TEXT_CLASS}`,
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
const CONTROL_SELECTOR = LT_FIELD_PARTS.control
|
|
30
|
+
|
|
31
|
+
type Marker = "none" | "symbol" | "text"
|
|
32
|
+
|
|
33
|
+
function readMarker(value: string | null | undefined): Marker | undefined {
|
|
34
|
+
return value === "none" || value === "symbol" || value === "text"
|
|
35
|
+
? value
|
|
36
|
+
: undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The attributes `<lt-field>` observes. It computes most of them itself, and
|
|
41
|
+
* sets them back if they disappear. `id` on the control, and `hidden` and
|
|
42
|
+
* `style` on the error text, it only reads: they come from the template.
|
|
43
|
+
*
|
|
44
|
+
* The same list once lived in `FIELD_PRESERVED_ATTRIBUTES`, which the
|
|
45
|
+
* template had to copy into `data-preserve-attr`. That list no longer
|
|
46
|
+
* exists: the component sees that the attributes are gone and sets them
|
|
47
|
+
* back.
|
|
48
|
+
*/
|
|
49
|
+
const DERIVED_ATTRIBUTES = [
|
|
50
|
+
"class",
|
|
51
|
+
"for",
|
|
52
|
+
"id",
|
|
53
|
+
"aria-describedby",
|
|
54
|
+
"aria-invalid",
|
|
55
|
+
"aria-disabled",
|
|
56
|
+
"data-state",
|
|
57
|
+
"data-required",
|
|
58
|
+
"data-optional",
|
|
59
|
+
"disabled",
|
|
60
|
+
"hidden",
|
|
61
|
+
// `data-show` in Datastar hides the error text with `display: none`.
|
|
62
|
+
"style",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* What the component last wrote on a control, and what the server last said.
|
|
67
|
+
*
|
|
68
|
+
* Three attributes are both read from the control and written back to it:
|
|
69
|
+
* `aria-invalid`, `aria-describedby` and `disabled`. They are read because
|
|
70
|
+
* the server may have written the field with `lt.field()`, and then the
|
|
71
|
+
* answer is already there. But a naive read is the component's echo from the
|
|
72
|
+
* previous round. For `aria-invalid` that meant `field.invalid = false`
|
|
73
|
+
* removed the flag on the host while the red border stayed for good. For
|
|
74
|
+
* `aria-describedby` the list never shrank: the error text's id stayed after
|
|
75
|
+
* the error was gone. For `disabled` the field could not be enabled again
|
|
76
|
+
* from the host.
|
|
77
|
+
*
|
|
78
|
+
* The fix is not to remember what the host said last. That was tried, and
|
|
79
|
+
* made the component depend on its history: the same markup gave two
|
|
80
|
+
* different answers depending on whether the host had had `invalid` at some
|
|
81
|
+
* point.
|
|
82
|
+
*
|
|
83
|
+
* Instead the component notes the value it wrote, and on which control. If
|
|
84
|
+
* something else is there next time, someone else has touched the
|
|
85
|
+
* attribute, and that is the server's word. The read is therefore always
|
|
86
|
+
* derived from a change that actually happened, and the same document always
|
|
87
|
+
* gives the same answer.
|
|
88
|
+
*/
|
|
89
|
+
type ControlMemory = {
|
|
90
|
+
written: ControlWord
|
|
91
|
+
server: ControlWord
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The same memory for the markers on the label. The component both reads and
|
|
96
|
+
* writes `data-required` and `data-optional`, and without knowing what it
|
|
97
|
+
* wrote itself, it read its own answer back: `optional` and the required
|
|
98
|
+
* marker could not be turned off again.
|
|
99
|
+
*/
|
|
100
|
+
type LabelWord = { required: string | null; optional: boolean }
|
|
101
|
+
type LabelMemory = { written: LabelWord; server: LabelWord }
|
|
102
|
+
|
|
103
|
+
type ControlWord = {
|
|
104
|
+
invalid: string | null
|
|
105
|
+
describedBy: string | null
|
|
106
|
+
disabled: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readControlWord(control: Element): ControlWord {
|
|
110
|
+
return {
|
|
111
|
+
invalid: control.getAttribute("aria-invalid"),
|
|
112
|
+
describedBy: control.getAttribute("aria-describedby"),
|
|
113
|
+
disabled: control.hasAttribute("disabled"),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Connects label, control, help text and error text in plain DOM.
|
|
119
|
+
*
|
|
120
|
+
* The component is for markup produced without JavaScript: a Go template, a
|
|
121
|
+
* PHP file, a Razor view or handwritten HTML. When the markup is written with
|
|
122
|
+
* JavaScript, wherever the code runs, `lt.field()` should write the
|
|
123
|
+
* attributes instead, and this element is not needed.
|
|
124
|
+
*
|
|
125
|
+
* The component renders nothing. It used to insert a `<slot>` element into
|
|
126
|
+
* plain DOM, and since the server did not know about it, Datastar's morph
|
|
127
|
+
* removed it on every patch.
|
|
128
|
+
*
|
|
129
|
+
* The template writes what HTML requires for the field to work without
|
|
130
|
+
* JavaScript: `for` on the label and `id` on the control, or the label
|
|
131
|
+
* wrapping the control. The component writes the rest: the ids of the help
|
|
132
|
+
* text and the error text, `aria-describedby`, `aria-invalid` and the
|
|
133
|
+
* classes. If a morph removes them, the component sees it and sets them back.
|
|
134
|
+
*
|
|
135
|
+
* The component never writes an id on the control. Datastar and idiomorph
|
|
136
|
+
* recognise a node by its id, and a control with an id the server did not
|
|
137
|
+
* send is replaced on the next update, along with focus and what the user
|
|
138
|
+
* has typed.
|
|
139
|
+
*
|
|
140
|
+
* The field is invalid when the error text is visible, that is when it has
|
|
141
|
+
* no `hidden`, or when the server has written `aria-invalid` on the control.
|
|
142
|
+
* There is no separate state on the host: a template renders the error text
|
|
143
|
+
* when the field is invalid, and client code toggles `hidden`.
|
|
144
|
+
*/
|
|
145
|
+
export class LtField extends HostElement {
|
|
146
|
+
static observedAttributes = ["disabled", "optional", "described-by"] as const
|
|
147
|
+
|
|
148
|
+
private observer?: MutationObserver
|
|
149
|
+
/**
|
|
150
|
+
* The ids the help text and the error text last had, whether they came
|
|
151
|
+
* from the markup or from here, so a patch that removes them gets the same
|
|
152
|
+
* one back and not a new one. `managedIds` is every id the component has
|
|
153
|
+
* ever managed. Together with whether the id still points to an element,
|
|
154
|
+
* that tells them apart from the ids the server put in `aria-describedby`.
|
|
155
|
+
*/
|
|
156
|
+
private lastHelpId?: string
|
|
157
|
+
private lastErrorId?: string
|
|
158
|
+
private readonly managedIds = new Set<string>()
|
|
159
|
+
/**
|
|
160
|
+
* What the component wrote on each control, keyed on the control itself.
|
|
161
|
+
*
|
|
162
|
+
* A `WeakMap` and not a "previous control" property. The memory has to
|
|
163
|
+
* survive the host being disconnected and connected again, as when React
|
|
164
|
+
* moves a field, and the control disappearing and coming back in a patch.
|
|
165
|
+
* A property that was reset in `disconnectedCallback` made the component
|
|
166
|
+
* read its own `aria-invalid="true"` as the server's word after a move, and
|
|
167
|
+
* the field could never become valid again. A `WeakMap` also keeps no
|
|
168
|
+
* detached node alive: when the control is gone for good, so is the memory.
|
|
169
|
+
*/
|
|
170
|
+
private readonly memory = new WeakMap<Element, ControlMemory>()
|
|
171
|
+
private readonly labelMemory = new WeakMap<Element, LabelMemory>()
|
|
172
|
+
/** The `for` the component itself wrote on the label. */
|
|
173
|
+
private readonly forByMe = new WeakMap<Element, string>()
|
|
174
|
+
/**
|
|
175
|
+
* Labels where the server wrote a `for` pointing to a different id than
|
|
176
|
+
* the control's. The component rewrites it, but without JavaScript the
|
|
177
|
+
* field had no label, so it must be reported, once. The memory is not
|
|
178
|
+
* reset: after the rewrite the right value is there, and it cannot be told
|
|
179
|
+
* apart from a server that corrected itself.
|
|
180
|
+
*/
|
|
181
|
+
private readonly wrongFor = new WeakSet<Element>()
|
|
182
|
+
/**
|
|
183
|
+
* The control last time.
|
|
184
|
+
*
|
|
185
|
+
* Enough to see that a server update replaced it because the id changed:
|
|
186
|
+
* Datastar and idiomorph recognise a node by its id, and a new id from the
|
|
187
|
+
* server is a new node to them. That happens when the template builds the
|
|
188
|
+
* id from something that changes between renders, such as a random number
|
|
189
|
+
* or a counter.
|
|
190
|
+
*
|
|
191
|
+
* A reference, not just an entry in `memory`: the replacement is detected
|
|
192
|
+
* by comparing it with the control that is there now, and what the old one
|
|
193
|
+
* contained must be readable after it has left the tree. It keeps one
|
|
194
|
+
* detached node alive until the next `sync()`, on purpose.
|
|
195
|
+
*/
|
|
196
|
+
private lastControl?: HTMLElement
|
|
197
|
+
/** The controls where `data-state` was written by the component. */
|
|
198
|
+
private readonly stateByMe = new WeakSet<Element>()
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The properties mirror the attributes.
|
|
202
|
+
*
|
|
203
|
+
* The state lives in the attribute and not in a field on the class, so what
|
|
204
|
+
* the server sent and what the component thinks are always the same.
|
|
205
|
+
*/
|
|
206
|
+
get disabled(): boolean {
|
|
207
|
+
return this.hasAttribute("disabled")
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
set disabled(value: boolean) {
|
|
211
|
+
setFlag(this, "disabled", value)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get optional(): boolean {
|
|
215
|
+
return this.hasAttribute("optional")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
set optional(value: boolean) {
|
|
219
|
+
setFlag(this, "optional", value)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
get describedBy(): string | undefined {
|
|
223
|
+
return this.getAttribute("described-by") ?? undefined
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
set describedBy(value: string | undefined) {
|
|
227
|
+
setAttr(this, "described-by", value ?? null)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
connectedCallback(): void {
|
|
231
|
+
/*
|
|
232
|
+
* `slotchange` does not fire in plain DOM, and the content is replaced
|
|
233
|
+
* while the user fills in the form.
|
|
234
|
+
*
|
|
235
|
+
* The attributes are observed, not just the children. A morph removes
|
|
236
|
+
* whatever is not in the server's HTML, and the wiring between label,
|
|
237
|
+
* field and help text is exactly that: something the component computed,
|
|
238
|
+
* not something the server sent. The template used to have to list the
|
|
239
|
+
* attributes in `data-preserve-attr` for them to survive. Now the
|
|
240
|
+
* component sees that they are gone, and sets them back.
|
|
241
|
+
*
|
|
242
|
+
* The list is limited to what the component derives itself. The other
|
|
243
|
+
* components do the same with their state, each with its own list.
|
|
244
|
+
*
|
|
245
|
+
* Every write in `sync()` compares first. Without that the observer
|
|
246
|
+
* would trigger itself forever.
|
|
247
|
+
*/
|
|
248
|
+
this.observer = new MutationObserver((records) => {
|
|
249
|
+
// `style` is observed for `display: none` on the error text. A style
|
|
250
|
+
// write elsewhere in the field, such as a popover positioning its
|
|
251
|
+
// panel, does not concern the wiring.
|
|
252
|
+
const onlyForeignStyle = records.every(
|
|
253
|
+
(r) =>
|
|
254
|
+
r.type === "attributes" &&
|
|
255
|
+
r.attributeName === "style" &&
|
|
256
|
+
!(r.target as Element).matches?.(LT_FIELD_PARTS.error),
|
|
257
|
+
)
|
|
258
|
+
if (!onlyForeignStyle) this.sync()
|
|
259
|
+
})
|
|
260
|
+
this.observer.observe(this, {
|
|
261
|
+
childList: true,
|
|
262
|
+
subtree: true,
|
|
263
|
+
attributes: true,
|
|
264
|
+
attributeFilter: DERIVED_ATTRIBUTES,
|
|
265
|
+
// A morph can change the text of the error text without replacing the node.
|
|
266
|
+
characterData: true,
|
|
267
|
+
})
|
|
268
|
+
this.sync()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
disconnectedCallback(): void {
|
|
272
|
+
this.observer?.disconnect()
|
|
273
|
+
this.observer = undefined
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
attributeChangedCallback(): void {
|
|
277
|
+
if (this.isConnected) this.sync()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The label the field belongs with.
|
|
282
|
+
*
|
|
283
|
+
* Usually it is inside the element. If it is outside, with `for` pointing
|
|
284
|
+
* to the control, the field is just as well named, and the component
|
|
285
|
+
* connects it the same way. One difference is worth knowing: a label
|
|
286
|
+
* outside is not in what the component observes, so if a patch removes its
|
|
287
|
+
* class, the class does not come back by itself.
|
|
288
|
+
*/
|
|
289
|
+
/**
|
|
290
|
+
* Remembers whether the server wrote a `for` that was not the control's
|
|
291
|
+
* id. What the component itself wrote last does not count.
|
|
292
|
+
*/
|
|
293
|
+
private noteFor(label: Element, id: string): void {
|
|
294
|
+
const current = label.getAttribute("for")
|
|
295
|
+
if (current === null || current === id) return
|
|
296
|
+
if (current !== this.forByMe.get(label)) this.wrongFor.add(label)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private resolveLabel(control: HTMLElement | null): HTMLLabelElement | null {
|
|
300
|
+
const inside = this.ownPart<HTMLLabelElement>(LT_FIELD_PARTS.label)
|
|
301
|
+
if (inside || !control?.id) return inside
|
|
302
|
+
|
|
303
|
+
const root = this.getRootNode() as Document | ShadowRoot
|
|
304
|
+
return (
|
|
305
|
+
root.querySelector?.<HTMLLabelElement>(
|
|
306
|
+
`label[for="${CSS.escape(control.id)}"]`,
|
|
307
|
+
) ?? null
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The server's word about the control, derived from what is there now
|
|
313
|
+
* compared with what the component itself wrote last.
|
|
314
|
+
*
|
|
315
|
+
* A control the component has never written on holds only the server's
|
|
316
|
+
* word. Otherwise each attribute is the server's if it differs from what
|
|
317
|
+
* the component wrote, and unchanged if it is the same.
|
|
318
|
+
*/
|
|
319
|
+
private serverLabelWord(label: Element | null): LabelWord {
|
|
320
|
+
if (!label) return { required: null, optional: false }
|
|
321
|
+
const now: LabelWord = {
|
|
322
|
+
required: label.getAttribute("data-required"),
|
|
323
|
+
optional: label.hasAttribute("data-optional"),
|
|
324
|
+
}
|
|
325
|
+
const known = this.labelMemory.get(label)
|
|
326
|
+
if (!known) return now
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
required:
|
|
330
|
+
now.required !== known.written.required
|
|
331
|
+
? now.required
|
|
332
|
+
: known.server.required,
|
|
333
|
+
optional:
|
|
334
|
+
now.optional !== known.written.optional
|
|
335
|
+
? now.optional
|
|
336
|
+
: known.server.optional,
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private serverWord(control: Element, now: ControlWord): ControlWord {
|
|
341
|
+
const known = this.memory.get(control)
|
|
342
|
+
if (!known) return now
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
invalid:
|
|
346
|
+
now.invalid !== known.written.invalid
|
|
347
|
+
? now.invalid
|
|
348
|
+
: known.server.invalid,
|
|
349
|
+
describedBy:
|
|
350
|
+
now.describedBy !== known.written.describedBy
|
|
351
|
+
? now.describedBy
|
|
352
|
+
: known.server.describedBy,
|
|
353
|
+
disabled:
|
|
354
|
+
now.disabled !== known.written.disabled
|
|
355
|
+
? now.disabled
|
|
356
|
+
: known.server.disabled,
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The parts that belong to this field, and not to a field further down.
|
|
362
|
+
*
|
|
363
|
+
* A field can be inside another: a search field in a popover inside the
|
|
364
|
+
* field, or an `<lt-field>` in an `<lt-field>`. `querySelector` over the
|
|
365
|
+
* whole subtree then found the innermost field's control first, and the
|
|
366
|
+
* outer field wrote its wiring on it. The scoping is the same as the one
|
|
367
|
+
* `derivedParts()` in `host-element.ts` uses for the other components.
|
|
368
|
+
*/
|
|
369
|
+
private ownParts<T extends Element>(selector: string): T[] {
|
|
370
|
+
return [...this.querySelectorAll<T>(selector)].filter(
|
|
371
|
+
(part) => part.closest(this.localName) === this,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private ownPart<T extends Element>(selector: string): T | null {
|
|
376
|
+
return this.ownParts<T>(selector)[0] ?? null
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Reports when an update replaced the control because the id from the
|
|
381
|
+
* server changed.
|
|
382
|
+
*
|
|
383
|
+
* The message only comes when the user had something to lose: the old
|
|
384
|
+
* control has left the tree, both had an id from the server, the ids
|
|
385
|
+
* differ, and the old one contained something other than the new one. A
|
|
386
|
+
* server that sends a different field on purpose usually also has a
|
|
387
|
+
* different label or a different `name`; then it stays silent. The
|
|
388
|
+
* comparison is with the new control, not with the old one's
|
|
389
|
+
* `defaultValue`: a `<select>` always has a selected option without any
|
|
390
|
+
* being `defaultSelected`, and a server that sends back the text the user
|
|
391
|
+
* typed has lost nothing.
|
|
392
|
+
*/
|
|
393
|
+
private warnAboutReplacedControl(
|
|
394
|
+
lost: HTMLElement,
|
|
395
|
+
control: HTMLElement,
|
|
396
|
+
): void {
|
|
397
|
+
if (lost.isConnected || !lost.id || !control.id || lost.id === control.id)
|
|
398
|
+
return
|
|
399
|
+
if (lost.getAttribute("name") !== control.getAttribute("name")) return
|
|
400
|
+
if (!this.differs(lost, control)) return
|
|
401
|
+
|
|
402
|
+
warnAboutMarkup(
|
|
403
|
+
this,
|
|
404
|
+
"a server update replaced the control and lost what the user " +
|
|
405
|
+
"had typed in it, because the control's id was different from " +
|
|
406
|
+
"last time and the update did not recognise it. Build the id " +
|
|
407
|
+
"from something that is the same every time, such as the field " +
|
|
408
|
+
"name or a key in the data, not from a random number or a " +
|
|
409
|
+
"counter.",
|
|
410
|
+
// The replacement has happened and cannot undo itself, unless a new
|
|
411
|
+
// update has already replaced the control again.
|
|
412
|
+
() =>
|
|
413
|
+
!lost.isConnected &&
|
|
414
|
+
this.ownPart<HTMLElement>(CONTROL_SELECTOR) === control,
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Did the old control contain something other than the new one?
|
|
420
|
+
*
|
|
421
|
+
* A checkbox and a radio button have the same `value` either way, so there
|
|
422
|
+
* the checked state is what counts. A `<select>` is compared on the
|
|
423
|
+
* selected options, since `value` is only the first of them.
|
|
424
|
+
*/
|
|
425
|
+
private differs(lost: HTMLElement, control: HTMLElement): boolean {
|
|
426
|
+
if (
|
|
427
|
+
lost instanceof HTMLSelectElement &&
|
|
428
|
+
control instanceof HTMLSelectElement
|
|
429
|
+
) {
|
|
430
|
+
const chosen = (select: HTMLSelectElement) =>
|
|
431
|
+
[...select.selectedOptions].map((o) => o.value).join("\n")
|
|
432
|
+
return chosen(lost) !== chosen(control)
|
|
433
|
+
}
|
|
434
|
+
if (
|
|
435
|
+
lost instanceof HTMLInputElement &&
|
|
436
|
+
control instanceof HTMLInputElement &&
|
|
437
|
+
(lost.type === "checkbox" || lost.type === "radio")
|
|
438
|
+
) {
|
|
439
|
+
return lost.checked !== control.checked
|
|
440
|
+
}
|
|
441
|
+
return (
|
|
442
|
+
(lost as HTMLInputElement).value !== (control as HTMLInputElement).value
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private sync(): void {
|
|
447
|
+
const control = this.ownPart<
|
|
448
|
+
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
449
|
+
>(CONTROL_SELECTOR)
|
|
450
|
+
const help = this.ownPart<HTMLElement>(LT_FIELD_PARTS.help)
|
|
451
|
+
const error = this.ownPart<HTMLElement>(LT_FIELD_PARTS.error)
|
|
452
|
+
|
|
453
|
+
if (!control) {
|
|
454
|
+
warnAboutMarkup(
|
|
455
|
+
this,
|
|
456
|
+
"found no control to connect. Add an <input>, <textarea> or " +
|
|
457
|
+
"<select>, or the label, help text and error text have no field " +
|
|
458
|
+
"to be connected to.",
|
|
459
|
+
// An empty element is a region the server has not filled yet, and
|
|
460
|
+
// that is not an error in the markup.
|
|
461
|
+
() =>
|
|
462
|
+
this.childElementCount > 0 &&
|
|
463
|
+
this.ownParts(CONTROL_SELECTOR).length === 0,
|
|
464
|
+
)
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// The wiring applies to one control, and that is the first. Another
|
|
469
|
+
// control in the same field has no label, help text or error text, and
|
|
470
|
+
// that must be reported rather than left silently unconnected.
|
|
471
|
+
warnAboutMarkup(
|
|
472
|
+
this,
|
|
473
|
+
"has more than one control, and only the first gets the label, " +
|
|
474
|
+
"help text and error text connected to it. Give each control " +
|
|
475
|
+
"its own <lt-field>, or put a group in a <fieldset> with a " +
|
|
476
|
+
"<legend>.",
|
|
477
|
+
() => this.ownParts(CONTROL_SELECTOR).length > 1,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
// The label can be outside the element. The lookup has to happen after
|
|
481
|
+
// the control is found, since it goes through the control's id.
|
|
482
|
+
const label = this.resolveLabel(control)
|
|
483
|
+
|
|
484
|
+
warnAboutMarkup(
|
|
485
|
+
this,
|
|
486
|
+
"<label> is not connected to the field: the field has no id, " +
|
|
487
|
+
"and the label does not wrap it, so without JavaScript a screen " +
|
|
488
|
+
"reader announces the field without a name. Write an id on the " +
|
|
489
|
+
"field and the same value in for on the label, or wrap the " +
|
|
490
|
+
"field in the <label>; the component does not write the id " +
|
|
491
|
+
"itself, because an id the server did not send makes a server " +
|
|
492
|
+
"update replace the field.",
|
|
493
|
+
() => {
|
|
494
|
+
const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
|
|
495
|
+
const labelNow = this.resolveLabel(now)
|
|
496
|
+
return (
|
|
497
|
+
now !== null &&
|
|
498
|
+
labelNow !== null &&
|
|
499
|
+
!now.id &&
|
|
500
|
+
!labelNow.contains(now) &&
|
|
501
|
+
!now.hasAttribute("aria-label") &&
|
|
502
|
+
!now.hasAttribute("aria-labelledby")
|
|
503
|
+
)
|
|
504
|
+
},
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
// `for` wins over the label wrapping the field. If it points to another
|
|
508
|
+
// id, the field has no label, with JavaScript too.
|
|
509
|
+
warnAboutMarkup(
|
|
510
|
+
this,
|
|
511
|
+
"<label> wraps the field, but its for points to a different id " +
|
|
512
|
+
"than the field's, and for wins, so a screen reader announces " +
|
|
513
|
+
"the field without a name. Remove for, or give the field the " +
|
|
514
|
+
"same id.",
|
|
515
|
+
() => {
|
|
516
|
+
const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
|
|
517
|
+
const labelNow = this.ownPart<HTMLLabelElement>(LT_FIELD_PARTS.label)
|
|
518
|
+
if (!now || !labelNow?.contains(now)) return false
|
|
519
|
+
const target = labelNow.getAttribute("for")
|
|
520
|
+
return target !== null && target !== now.id
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
warnAboutMarkup(
|
|
525
|
+
this,
|
|
526
|
+
"for on the label pointed to a different id than the field's. " +
|
|
527
|
+
"The component rewrote it, but without JavaScript the field has " +
|
|
528
|
+
"no label and a screen reader announces it without a name, so " +
|
|
529
|
+
"write the field's id in for in the template.",
|
|
530
|
+
() => {
|
|
531
|
+
const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
|
|
532
|
+
const labelNow = this.resolveLabel(now)
|
|
533
|
+
return now !== null && labelNow !== null && this.wrongFor.has(labelNow)
|
|
534
|
+
},
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
warnAboutMarkup(
|
|
538
|
+
this,
|
|
539
|
+
"found no <label>, so the field has no label and a screen " +
|
|
540
|
+
"reader announces it without a name. If the label is outside " +
|
|
541
|
+
"<lt-field>, its for must point to the field's id.",
|
|
542
|
+
/*
|
|
543
|
+
* Three valid ways to give the field a name, and none of them should
|
|
544
|
+
* give a warning: a `<label>` inside, a `<label for>` outside, or
|
|
545
|
+
* `aria-label` and `aria-labelledby` on the control, as in a search
|
|
546
|
+
* field with only an icon.
|
|
547
|
+
*/
|
|
548
|
+
() => {
|
|
549
|
+
const named = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
|
|
550
|
+
if (!named || this.resolveLabel(named)) return false
|
|
551
|
+
// A <label> around the whole <lt-field> also gives the control a name.
|
|
552
|
+
if (((named as HTMLInputElement).labels?.length ?? 0) > 0) return false
|
|
553
|
+
return (
|
|
554
|
+
!named.hasAttribute("aria-label") &&
|
|
555
|
+
!named.hasAttribute("aria-labelledby")
|
|
556
|
+
)
|
|
557
|
+
},
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
/*
|
|
561
|
+
* The ids the help text and the error text last had, remembered between
|
|
562
|
+
* rounds.
|
|
563
|
+
*
|
|
564
|
+
* The markup is the source as long as it has them. If a morph removes
|
|
565
|
+
* them, a new id would be created for every single patch, and a screen
|
|
566
|
+
* reader in the middle of announcing would follow a reference that
|
|
567
|
+
* changed under it. The memory is not a parallel copy of the state: if
|
|
568
|
+
* the id is in the markup, that is the one that applies.
|
|
569
|
+
*/
|
|
570
|
+
if (help) {
|
|
571
|
+
if (help.id) this.lastHelpId = help.id
|
|
572
|
+
else {
|
|
573
|
+
this.lastHelpId ??= uniqueId("lt-field-help")
|
|
574
|
+
setAttr(help, "id", this.lastHelpId)
|
|
575
|
+
}
|
|
576
|
+
this.managedIds.add(help.id)
|
|
577
|
+
}
|
|
578
|
+
if (error) {
|
|
579
|
+
if (error.id) this.lastErrorId = error.id
|
|
580
|
+
else {
|
|
581
|
+
this.lastErrorId ??= uniqueId("lt-field-error")
|
|
582
|
+
setAttr(error, "id", this.lastErrorId)
|
|
583
|
+
}
|
|
584
|
+
this.managedIds.add(error.id)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// The markers are also read from the markup. If the server wrote them
|
|
588
|
+
// with `lt.field()`, they are on the label, and a component that only
|
|
589
|
+
// looked at its own attributes would remove them again. In React that
|
|
590
|
+
// gave a hydration error: the server sent `data-required="symbol"`, the
|
|
591
|
+
// component removed it, and then React decided the HTML did not match.
|
|
592
|
+
const serverLabel = this.serverLabelWord(label)
|
|
593
|
+
const marker = readMarker(serverLabel.required)
|
|
594
|
+
|
|
595
|
+
// The state is read from the markup, not just from an attribute on the
|
|
596
|
+
// host. If the server wrote the field with `lt.field()`, the answer is
|
|
597
|
+
// already on the control, and a component that computed its own would
|
|
598
|
+
// remove it again. The two halves of the API then fought each other. See
|
|
599
|
+
// `ControlMemory` for how the server's word is told apart from what the
|
|
600
|
+
// component wrote.
|
|
601
|
+
const server = this.serverWord(control, readControlWord(control))
|
|
602
|
+
|
|
603
|
+
// `aria-invalid` has four valid values, and both `grammar` and
|
|
604
|
+
// `spelling` mean invalid. Absence, `false` and the empty string mean
|
|
605
|
+
// valid; ARIA says the empty string is read as `false`, and assistive
|
|
606
|
+
// technology does so, so the component cannot show a red border for it.
|
|
607
|
+
const serverInvalid =
|
|
608
|
+
server.invalid !== null &&
|
|
609
|
+
server.invalid !== "false" &&
|
|
610
|
+
server.invalid !== ""
|
|
611
|
+
// A visible error text means the field is invalid. It is not the
|
|
612
|
+
// component's to hide: the template renders it when the field is
|
|
613
|
+
// invalid, and client code toggles `hidden`, or `display: none` as
|
|
614
|
+
// `data-show` in Datastar does. An empty error text says nothing, such as
|
|
615
|
+
// a container that `data-text` fills.
|
|
616
|
+
const errorShown =
|
|
617
|
+
error !== null &&
|
|
618
|
+
!error.hasAttribute("hidden") &&
|
|
619
|
+
(error.textContent ?? "").trim() !== "" &&
|
|
620
|
+
// Last, since it forces a style computation.
|
|
621
|
+
getComputedStyle(error).display !== "none"
|
|
622
|
+
const invalid = serverInvalid || errorShown
|
|
623
|
+
const disabled = this.hasAttribute("disabled") || server.disabled
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
* The ids the server put in `aria-describedby`, apart from those the
|
|
627
|
+
* component manages. `lt.field({ describedBy })` writes them straight on
|
|
628
|
+
* the control, and they must be kept. The managed ones are removed here
|
|
629
|
+
* and added again from the current state, or the error text's id stayed
|
|
630
|
+
* after the error was gone.
|
|
631
|
+
*
|
|
632
|
+
* Managed means the current help text and error text, and an id the
|
|
633
|
+
* component managed before that no longer points to anything. The latter
|
|
634
|
+
* is for a script that removes the help text without touching the
|
|
635
|
+
* control. Who an id belongs to is decided by the DOM and not by the
|
|
636
|
+
* history alone: if the id still points to an element, as when the
|
|
637
|
+
* server moves the help text out of the field and keeps the id, it is the
|
|
638
|
+
* server's.
|
|
639
|
+
*/
|
|
640
|
+
const root = this.getRootNode() as Document | ShadowRoot
|
|
641
|
+
const managedNow = (id: string) =>
|
|
642
|
+
id === help?.id ||
|
|
643
|
+
id === error?.id ||
|
|
644
|
+
(this.managedIds.has(id) && !root.getElementById?.(id))
|
|
645
|
+
const serverExtras = (server.describedBy ?? "")
|
|
646
|
+
.split(/\s+/)
|
|
647
|
+
.filter((id) => id && !managedNow(id))
|
|
648
|
+
|
|
649
|
+
const computed = computeFieldAttributes({
|
|
650
|
+
// `computeFieldAttributes` requires an id. The control's id is used
|
|
651
|
+
// when it exists; otherwise a stem that is never written, since the
|
|
652
|
+
// component writes neither an id on the control nor `for` from it.
|
|
653
|
+
id: control.id || "lt-field",
|
|
654
|
+
help: Boolean(help),
|
|
655
|
+
// Only a visible error text describes the field. If the server says
|
|
656
|
+
// invalid while the message is hidden, the reference must not point to
|
|
657
|
+
// something hidden.
|
|
658
|
+
error: errorShown,
|
|
659
|
+
helpId: help?.id,
|
|
660
|
+
errorId: error?.id,
|
|
661
|
+
required: marker === "symbol" || marker === "text" ? marker : undefined,
|
|
662
|
+
optional: this.hasAttribute("optional") || serverLabel.optional,
|
|
663
|
+
invalid,
|
|
664
|
+
disabled,
|
|
665
|
+
describedBy: [...serverExtras, this.getAttribute("described-by") ?? ""],
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
if (this.lastControl && this.lastControl !== control) {
|
|
669
|
+
this.warnAboutReplacedControl(this.lastControl, control)
|
|
670
|
+
}
|
|
671
|
+
this.lastControl = control
|
|
672
|
+
|
|
673
|
+
if (label) {
|
|
674
|
+
addClass(label, computed.label.class)
|
|
675
|
+
// `for` and `id` are the same piece of information, and the two must
|
|
676
|
+
// not say different things. It is written from the control's id, never
|
|
677
|
+
// the other way round, and not when the label wraps the control, where
|
|
678
|
+
// the wiring is implicit.
|
|
679
|
+
if (control.id && !label.contains(control)) {
|
|
680
|
+
this.noteFor(label, control.id)
|
|
681
|
+
setAttr(label, "for", control.id)
|
|
682
|
+
this.forByMe.set(label, control.id)
|
|
683
|
+
}
|
|
684
|
+
setAttr(label, "data-required", computed.label["data-required"])
|
|
685
|
+
setAttr(label, "data-optional", computed.label["data-optional"])
|
|
686
|
+
this.labelMemory.set(label, {
|
|
687
|
+
written: {
|
|
688
|
+
required: computed.label["data-required"] ?? null,
|
|
689
|
+
optional: computed.label["data-optional"] !== undefined,
|
|
690
|
+
},
|
|
691
|
+
server: serverLabel,
|
|
692
|
+
})
|
|
693
|
+
setAttr(label, "aria-disabled", computed.label["aria-disabled"])
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// The server's word stays verbatim when it is the server that says the
|
|
697
|
+
// field is invalid, `spelling` and `grammar` included. Otherwise the
|
|
698
|
+
// component writes `true` when the host says so, and leaves a `false`
|
|
699
|
+
// the server wrote: it is valid and common in handwritten HTML, and used
|
|
700
|
+
// to be removed on every patch.
|
|
701
|
+
const ariaInvalid = serverInvalid
|
|
702
|
+
? server.invalid
|
|
703
|
+
: invalid
|
|
704
|
+
? "true"
|
|
705
|
+
: server.invalid
|
|
706
|
+
|
|
707
|
+
setAttr(control, "aria-describedby", computed.control["aria-describedby"])
|
|
708
|
+
setAttr(control, "aria-invalid", ariaInvalid)
|
|
709
|
+
// `setFlag` and not `setAttr`: a template may have written
|
|
710
|
+
// `disabled="disabled"`, and it must stay as it is. `setAttr` would
|
|
711
|
+
// normalise the value to the empty string, and since `disabled` is among
|
|
712
|
+
// the attributes the component observes, the server and the component
|
|
713
|
+
// would each write their own value on every patch. No `aria-disabled`
|
|
714
|
+
// alongside it: a real `disabled` is already visible to assistive
|
|
715
|
+
// technology, and `computeFieldAttributes` only writes it on the label.
|
|
716
|
+
setFlag(control, "disabled", disabled)
|
|
717
|
+
|
|
718
|
+
this.memory.set(control, {
|
|
719
|
+
written: {
|
|
720
|
+
invalid: ariaInvalid,
|
|
721
|
+
describedBy: computed.control["aria-describedby"] ?? null,
|
|
722
|
+
disabled,
|
|
723
|
+
},
|
|
724
|
+
server,
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
// `data-state` is only set on the system's controls, and only when the
|
|
728
|
+
// consumer has not set it. It is only removed when the component set it
|
|
729
|
+
// and the field is no longer invalid. A `data-state` a template wrote by
|
|
730
|
+
// hand stays as the template wrote it.
|
|
731
|
+
const isSystemField =
|
|
732
|
+
control.classList.contains("lt-input") ||
|
|
733
|
+
control.classList.contains("lt-textarea") ||
|
|
734
|
+
control.classList.contains("lt-select")
|
|
735
|
+
|
|
736
|
+
const state = computed.control["data-state"]
|
|
737
|
+
if (state && isSystemField && !control.hasAttribute("data-state")) {
|
|
738
|
+
setAttr(control, "data-state", state)
|
|
739
|
+
this.stateByMe.add(control)
|
|
740
|
+
} else if (
|
|
741
|
+
!state &&
|
|
742
|
+
this.stateByMe.has(control) &&
|
|
743
|
+
control.getAttribute("data-state") === "invalid"
|
|
744
|
+
) {
|
|
745
|
+
control.removeAttribute("data-state")
|
|
746
|
+
this.stateByMe.delete(control)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function defineLtField(tagName: string = LT_FIELD_TAG): void {
|
|
752
|
+
defineElement(tagName, LtField)
|
|
753
|
+
}
|