@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,510 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addClass,
|
|
3
|
+
defineElement,
|
|
4
|
+
HostElement,
|
|
5
|
+
isServerControlled,
|
|
6
|
+
SERVER_CONTROLLED,
|
|
7
|
+
setAttr,
|
|
8
|
+
setFlag,
|
|
9
|
+
uniqueId,
|
|
10
|
+
warnAboutMarkup,
|
|
11
|
+
} from "../../host-element.js"
|
|
12
|
+
import {
|
|
13
|
+
DIALOG_BODY_CLASS,
|
|
14
|
+
DIALOG_CLASS,
|
|
15
|
+
DIALOG_TITLE_CLASS,
|
|
16
|
+
} from "./dialog.js"
|
|
17
|
+
|
|
18
|
+
export const LT_DIALOG_TAG = "lt-dialog" as const
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The parts the dialog finds, as selectors relative to the element. The
|
|
22
|
+
* component finds the parts with these, and the manifest describes them for
|
|
23
|
+
* whoever assembles the markup by hand, so the two cannot say different things.
|
|
24
|
+
*/
|
|
25
|
+
export const LT_DIALOG_PARTS = {
|
|
26
|
+
dialog: ":scope > dialog",
|
|
27
|
+
title: "h1, h2, h3, h4, h5, h6",
|
|
28
|
+
body: `.${DIALOG_BODY_CLASS}`,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A modal dialog a server can open.
|
|
33
|
+
*
|
|
34
|
+
* Opening a `<dialog>` is a call and not an attribute. `showModal()` moves
|
|
35
|
+
* focus in, keeps focus inside the dialog, closes on Escape and makes the rest
|
|
36
|
+
* of the page inert. `<dialog open>` does none of that: it is a box on the
|
|
37
|
+
* page.
|
|
38
|
+
*
|
|
39
|
+
* A server that only sends HTML cannot call anything. Without this component
|
|
40
|
+
* an app in Datastar, htmx or a Go template could not open a dialog at all
|
|
41
|
+
* without writing a script of its own alongside. Here the server says instead
|
|
42
|
+
* that the dialog is open, and the component makes the call.
|
|
43
|
+
*
|
|
44
|
+
* When the user closes the dialog, with Escape or with a button in a
|
|
45
|
+
* `<form method="dialog">`, `open` is removed from the host again, so the
|
|
46
|
+
* markup says the same as the screen. The component reports it with
|
|
47
|
+
* `dialog-toggle`, which carries `open` and `returnValue`. A click on the
|
|
48
|
+
* backdrop does not close it: neither does it on a plain `<dialog>`, and the
|
|
49
|
+
* component adds nothing.
|
|
50
|
+
*
|
|
51
|
+
* `open` on the **host** comes from the server. If the server sends the
|
|
52
|
+
* region again with `open` still set, the dialog opens again. `open` on the
|
|
53
|
+
* `<dialog>` itself comes from the browser, set by `showModal()`, and the
|
|
54
|
+
* component puts it back when a patch removes it. The template needs no
|
|
55
|
+
* `data-preserve-attr`.
|
|
56
|
+
*
|
|
57
|
+
* Write `open` on the `<dialog>` itself too when the dialog should be shown.
|
|
58
|
+
* Without JavaScript a `<dialog>` without `open` is hidden, and then the
|
|
59
|
+
* content does not exist at all. `lt.dialog({ open: true })` gives both.
|
|
60
|
+
*
|
|
61
|
+
* `server-controlled` on the host turns off the repair of `open` on the
|
|
62
|
+
* `<dialog>`. Then every patch decides whether the dialog is shown, also when
|
|
63
|
+
* it is in the top layer.
|
|
64
|
+
*
|
|
65
|
+
* ```html
|
|
66
|
+
* <lt-dialog open>
|
|
67
|
+
* <dialog class="lt-dialog" aria-labelledby="title" open>
|
|
68
|
+
* <h2 class="lt-dialog__title" id="title">The decision is registered</h2>
|
|
69
|
+
* <div class="lt-dialog__body">The case has been processed.</div>
|
|
70
|
+
* <form method="dialog" class="lt-dialog__footer">
|
|
71
|
+
* <button class="lt-button" value="close">Close</button>
|
|
72
|
+
* </form>
|
|
73
|
+
* </dialog>
|
|
74
|
+
* </lt-dialog>
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* The dialogs already checked for a close before upgrade.
|
|
79
|
+
*
|
|
80
|
+
* The check applies only the first time a dialog is seen, not every time a
|
|
81
|
+
* host is connected. A morph that moves the host disconnects and reconnects
|
|
82
|
+
* it, and then `returnValue` was left over from a close the user made long
|
|
83
|
+
* ago. The component thought it had been closed before the script arrived,
|
|
84
|
+
* and took `open` from the host the server had just opened. The set lives
|
|
85
|
+
* outside the class, so the same applies to a dialog moved into a new host.
|
|
86
|
+
*/
|
|
87
|
+
const upgradeChecked = new WeakSet<HTMLDialogElement>()
|
|
88
|
+
|
|
89
|
+
export class LtDialog extends HostElement {
|
|
90
|
+
static observedAttributes = ["open", SERVER_CONTROLLED] as const
|
|
91
|
+
|
|
92
|
+
private observer?: MutationObserver
|
|
93
|
+
/**
|
|
94
|
+
* A separate observer for `open` on the `<dialog>` itself.
|
|
95
|
+
*
|
|
96
|
+
* It cannot be merged with the other one. When the user closes the dialog,
|
|
97
|
+
* the browser removes the attribute first and sends `close` afterwards, and
|
|
98
|
+
* an observer running all of `sync()` there would have seen a host still
|
|
99
|
+
* saying "open" and a dialog that was not modal, and opened it again before
|
|
100
|
+
* `close` was handled. This one does only one thing: it puts the attribute
|
|
101
|
+
* back on a dialog that is still in the top layer.
|
|
102
|
+
*/
|
|
103
|
+
private openObserver?: MutationObserver
|
|
104
|
+
private dialogElement?: HTMLDialogElement
|
|
105
|
+
/**
|
|
106
|
+
* What the component last reported, so it reports only when the state
|
|
107
|
+
* changes.
|
|
108
|
+
*
|
|
109
|
+
* A morph that moves the host or the dialog takes the dialog out of the top
|
|
110
|
+
* layer, and the component opens it again. Before, it then reported
|
|
111
|
+
* `open: true` for a dialog that had been open the whole time. The field
|
|
112
|
+
* survives the host being disconnected and reconnected.
|
|
113
|
+
*/
|
|
114
|
+
private lastNotified?: boolean
|
|
115
|
+
|
|
116
|
+
/** Whether the dialog is open. Mirrors the `open` attribute. */
|
|
117
|
+
get open(): boolean {
|
|
118
|
+
return this.hasAttribute("open")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set open(value: boolean) {
|
|
122
|
+
setFlag(this, "open", value)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
connectedCallback(): void {
|
|
126
|
+
/*
|
|
127
|
+
* The server can send the dialog into a region already on the page, and
|
|
128
|
+
* then the `<dialog>` does not exist yet when the component connects. Only
|
|
129
|
+
* a change among the host's direct children runs all of `sync()`: the
|
|
130
|
+
* dialog is always a direct child, and the content inside it changes with
|
|
131
|
+
* every patch without saying anything about which dialog is the
|
|
132
|
+
* component's.
|
|
133
|
+
*
|
|
134
|
+
* `open` on the `<dialog>` itself is included. The browser sets it when
|
|
135
|
+
* `showModal()` is called, and a morph removes it again, since the server
|
|
136
|
+
* did not send it. Without this an open dialog disappeared as soon as
|
|
137
|
+
* something in the region around it was patched, and the template had to
|
|
138
|
+
* write `data-preserve-attr="open"` to prevent it.
|
|
139
|
+
*/
|
|
140
|
+
/*
|
|
141
|
+
* The attributes are included for the wiring the component fills in when
|
|
142
|
+
* the markup came without it: the class, the heading's id and
|
|
143
|
+
* `aria-labelledby`. But an attribute record runs only the wiring, not all
|
|
144
|
+
* of `sync()`. `close()` removes `open` synchronously and queues the
|
|
145
|
+
* `close` event as a separate task, and the observer runs before it. When
|
|
146
|
+
* a click changed a class on the button in the same task as it closed the
|
|
147
|
+
* dialog, `sync()` saw a host with `open` and a dialog that was not modal,
|
|
148
|
+
* and opened it again before the close was handled. Every write compares
|
|
149
|
+
* first, or the observer would trigger itself.
|
|
150
|
+
*/
|
|
151
|
+
this.observer = new MutationObserver((records) => {
|
|
152
|
+
const swapped = records.some(
|
|
153
|
+
(record) => record.type === "childList" && record.target === this,
|
|
154
|
+
)
|
|
155
|
+
if (swapped) this.sync()
|
|
156
|
+
else this.rewire()
|
|
157
|
+
})
|
|
158
|
+
this.observer.observe(this, {
|
|
159
|
+
childList: true,
|
|
160
|
+
subtree: true,
|
|
161
|
+
attributes: true,
|
|
162
|
+
attributeFilter: ["class", "id", "aria-labelledby"],
|
|
163
|
+
})
|
|
164
|
+
this.sync()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
disconnectedCallback(): void {
|
|
168
|
+
this.observer?.disconnect()
|
|
169
|
+
this.observer = undefined
|
|
170
|
+
this.openObserver?.disconnect()
|
|
171
|
+
this.openObserver = undefined
|
|
172
|
+
this.dialogElement?.removeEventListener("close", this.handleClose)
|
|
173
|
+
this.dialogElement = undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
attributeChangedCallback(): void {
|
|
177
|
+
this.sync()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The dialog the component controls.
|
|
182
|
+
*
|
|
183
|
+
* Only a direct child. A `<dialog>` further down the tree can belong to
|
|
184
|
+
* something else, and must not be opened by this component.
|
|
185
|
+
*/
|
|
186
|
+
private get dialog(): HTMLDialogElement | null {
|
|
187
|
+
return this.querySelector(LT_DIALOG_PARTS.dialog)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The id the component gave the heading, so a patch gets the same one back. */
|
|
191
|
+
private titleId?: string
|
|
192
|
+
|
|
193
|
+
/** The wiring alone, for an attribute record. See the observer. */
|
|
194
|
+
private rewire(): void {
|
|
195
|
+
const dialog = this.dialog
|
|
196
|
+
if (dialog?.isConnected) this.wire(dialog)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The heading that is the dialog's title when the server did not point to
|
|
201
|
+
* one.
|
|
202
|
+
*
|
|
203
|
+
* The first heading in this dialog that is not in the body. A heading in
|
|
204
|
+
* `.lt-dialog__body` is a heading in the content, not the dialog's name: a
|
|
205
|
+
* settings dialog in three sections got the first section's heading as its
|
|
206
|
+
* title, with the class and the styling that come with it. A heading in a
|
|
207
|
+
* nested dialog belongs to the nested one, and the body that counts is only
|
|
208
|
+
* a body in this dialog: if this dialog is itself in the body of another,
|
|
209
|
+
* the outer body is not its body.
|
|
210
|
+
*/
|
|
211
|
+
private headingOf(dialog: HTMLDialogElement): HTMLElement | null {
|
|
212
|
+
return (
|
|
213
|
+
[...dialog.querySelectorAll<HTMLElement>(LT_DIALOG_PARTS.title)].find(
|
|
214
|
+
(heading) => {
|
|
215
|
+
if (heading.closest("dialog") !== dialog) return false
|
|
216
|
+
const body = heading.closest(LT_DIALOG_PARTS.body)
|
|
217
|
+
return body === null || !dialog.contains(body)
|
|
218
|
+
},
|
|
219
|
+
) ?? null
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Whether the dialog has no name: no reference, no `aria-label` and no
|
|
225
|
+
* title. An empty `<dialog>` is a region the server has not filled yet, and
|
|
226
|
+
* does not count.
|
|
227
|
+
*/
|
|
228
|
+
private nameless(dialog: HTMLDialogElement): boolean {
|
|
229
|
+
return (
|
|
230
|
+
dialog.childElementCount > 0 &&
|
|
231
|
+
!dialog.hasAttribute("aria-labelledby") &&
|
|
232
|
+
!dialog.hasAttribute("aria-label") &&
|
|
233
|
+
this.headingOf(dialog) === null
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Fills in what a template without JavaScript did not write: the class on
|
|
239
|
+
* the dialog, and the title. The title is what `aria-labelledby` points to
|
|
240
|
+
* when the server wrote it, otherwise the first heading outside the body,
|
|
241
|
+
* and only when the dialog does not already have a name from `aria-label`.
|
|
242
|
+
* The class and the id are written only on the title: a heading in the body
|
|
243
|
+
* of a dialog with `aria-label` is not the title, and must not be styled as
|
|
244
|
+
* one.
|
|
245
|
+
*/
|
|
246
|
+
private wire(dialog: HTMLDialogElement): void {
|
|
247
|
+
addClass(dialog, DIALOG_CLASS)
|
|
248
|
+
|
|
249
|
+
const named = dialog.getAttribute("aria-labelledby")
|
|
250
|
+
if (named) {
|
|
251
|
+
// The attribute can list several ids. The title is the first one that
|
|
252
|
+
// is inside the dialog.
|
|
253
|
+
const root = this.getRootNode() as Document | ShadowRoot
|
|
254
|
+
const title = named
|
|
255
|
+
.split(/\s+/)
|
|
256
|
+
.map((id) => root.getElementById?.(id) ?? null)
|
|
257
|
+
.find((element) => element !== null && dialog.contains(element))
|
|
258
|
+
if (title) addClass(title, DIALOG_TITLE_CLASS)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (dialog.hasAttribute("aria-label")) return
|
|
262
|
+
|
|
263
|
+
const title = this.headingOf(dialog)
|
|
264
|
+
if (!title) {
|
|
265
|
+
warnAboutMarkup(
|
|
266
|
+
this,
|
|
267
|
+
"the dialog has no name: it has no aria-labelledby, no " +
|
|
268
|
+
"aria-label and no heading outside the body, so a screen reader " +
|
|
269
|
+
'announces it as "dialog" without saying what it is about. Give ' +
|
|
270
|
+
"it a heading, or aria-label on the <dialog>.",
|
|
271
|
+
() => {
|
|
272
|
+
const now = this.dialog
|
|
273
|
+
return now !== null && this.nameless(now)
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
addClass(title, DIALOG_TITLE_CLASS)
|
|
279
|
+
if (!title.id) {
|
|
280
|
+
this.titleId ??= uniqueId("lt-dialog-title")
|
|
281
|
+
setAttr(title, "id", this.titleId)
|
|
282
|
+
}
|
|
283
|
+
setAttr(dialog, "aria-labelledby", title.id)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private notify(open: boolean, returnValue = ""): void {
|
|
287
|
+
if (open === this.lastNotified) return
|
|
288
|
+
this.lastNotified = open
|
|
289
|
+
this.dispatchEvent(
|
|
290
|
+
new CustomEvent("dialog-toggle", {
|
|
291
|
+
bubbles: true,
|
|
292
|
+
// Without `composed` the event stops at a shadow root, and a listener
|
|
293
|
+
// outside never learns that the dialog was closed.
|
|
294
|
+
composed: true,
|
|
295
|
+
detail: { open, returnValue },
|
|
296
|
+
}),
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Puts `open` back on a dialog that is still in the top layer.
|
|
302
|
+
*
|
|
303
|
+
* `showModal()` sets the attribute itself, and the server did not send it,
|
|
304
|
+
* so the morph removes it. The browser's own stylesheet then hides a
|
|
305
|
+
* `<dialog>` without `open`, and the dialog disappeared for the user in the
|
|
306
|
+
* middle of what she was doing. Before, the template had to write
|
|
307
|
+
* `data-preserve-attr="open"` to prevent it.
|
|
308
|
+
*
|
|
309
|
+
* The condition is `:modal` and not `this.open`, and that is precise: when
|
|
310
|
+
* the user closes the dialog, it leaves the top layer, and then the missing
|
|
311
|
+
* attribute is real. Only a dialog that is still modal without saying so is
|
|
312
|
+
* a dialog someone has taken the attribute from.
|
|
313
|
+
*/
|
|
314
|
+
private repairOpen(): void {
|
|
315
|
+
const dialog = this.dialogElement
|
|
316
|
+
if (!dialog?.isConnected || !dialog.matches(":modal") || dialog.open) {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The attribute must come back either way: without it `close()` does
|
|
321
|
+
// nothing, and the dialog stayed in the top layer, invisible, with the
|
|
322
|
+
// rest of the page inert.
|
|
323
|
+
setAttr(dialog, "open", "")
|
|
324
|
+
if (!isServerControlled(this)) return
|
|
325
|
+
|
|
326
|
+
/*
|
|
327
|
+
* The server decides the state, and the patch took `open` from a modal
|
|
328
|
+
* dialog. Then the message is that it should close, not be put back.
|
|
329
|
+
* Without this the dialog stood with `:modal` and `display: none`, and
|
|
330
|
+
* nothing on the page could be clicked. The host follows along here, so
|
|
331
|
+
* `handleClose` has nothing left to do and the event comes once.
|
|
332
|
+
*/
|
|
333
|
+
dialog.close()
|
|
334
|
+
// The event comes whoever removed `open` from the host. If React has
|
|
335
|
+
// already taken it, in its order with children before parent, this is
|
|
336
|
+
// the one time anyone can report it: `sync()` and `handleClose` then see
|
|
337
|
+
// a dialog that is already closed, and stay quiet.
|
|
338
|
+
if (this.open) this.removeAttribute("open")
|
|
339
|
+
this.notify(false, dialog.returnValue)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private handleClose = (): void => {
|
|
343
|
+
// `close` is queued, not synchronous. If the server manages to close and
|
|
344
|
+
// open again in the same task, this event no longer applies, and without
|
|
345
|
+
// the guard the component closed the dialog it had just opened.
|
|
346
|
+
if (this.dialog?.matches(":modal")) return
|
|
347
|
+
|
|
348
|
+
// If the server closed it, the host is already in step, and `sync()` has
|
|
349
|
+
// reported it.
|
|
350
|
+
if (!this.open) return
|
|
351
|
+
|
|
352
|
+
this.removeAttribute("open")
|
|
353
|
+
this.notify(false, this.dialog?.returnValue ?? "")
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private sync(): void {
|
|
357
|
+
const dialog = this.dialog
|
|
358
|
+
|
|
359
|
+
// `showModal()` throws "The element is not in a Document" if the dialog
|
|
360
|
+
// is not on the page. That is not theoretical: a morph builds the
|
|
361
|
+
// server's version in a detached tree before comparing, and a web
|
|
362
|
+
// component is used there too. Without this line the component threw on
|
|
363
|
+
// every single patch that touched the dialog. If it is detached now,
|
|
364
|
+
// `sync()` runs again anyway when it is connected.
|
|
365
|
+
if (!dialog?.isConnected) {
|
|
366
|
+
// Let go of a dialog that is gone. Otherwise the observer and the
|
|
367
|
+
// listener stayed attached to a detached node for as long as the host
|
|
368
|
+
// lived.
|
|
369
|
+
if (!dialog) {
|
|
370
|
+
this.openObserver?.disconnect()
|
|
371
|
+
this.openObserver = undefined
|
|
372
|
+
this.dialogElement?.removeEventListener("close", this.handleClose)
|
|
373
|
+
this.dialogElement = undefined
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
warnAboutMarkup(
|
|
377
|
+
this,
|
|
378
|
+
"found no <dialog> as a direct child. Without it nothing can " +
|
|
379
|
+
"open as a modal, and the content stays on the page as a plain " +
|
|
380
|
+
"box.",
|
|
381
|
+
// If the component is detached, a morph is building the server's
|
|
382
|
+
// version in a separate tree, and there is nothing to report. An
|
|
383
|
+
// empty element is a region the server has not filled yet.
|
|
384
|
+
() => this.childElementCount > 0 && this.dialog === null,
|
|
385
|
+
)
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
this.wire(dialog)
|
|
390
|
+
|
|
391
|
+
const first = dialog !== this.dialogElement
|
|
392
|
+
if (first) {
|
|
393
|
+
this.dialogElement?.removeEventListener("close", this.handleClose)
|
|
394
|
+
this.dialogElement = dialog
|
|
395
|
+
dialog.addEventListener("close", this.handleClose)
|
|
396
|
+
|
|
397
|
+
this.openObserver?.disconnect()
|
|
398
|
+
this.openObserver = new MutationObserver(() => this.repairOpen())
|
|
399
|
+
this.openObserver.observe(dialog, { attributeFilter: ["open"] })
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.repairOpen()
|
|
403
|
+
|
|
404
|
+
/*
|
|
405
|
+
* Did the user close the dialog before the component got to run?
|
|
406
|
+
*
|
|
407
|
+
* The server writes `open` in both places, so the content exists without
|
|
408
|
+
* JavaScript, and `<form method="dialog">` closes the box without
|
|
409
|
+
* JavaScript too. If that happens before the script has loaded, there is
|
|
410
|
+
* no listener, and the first `sync()` would see a host saying "open" and a
|
|
411
|
+
* dialog that is closed, and open it again. So the dialog popped up again
|
|
412
|
+
* right after the user had closed it, and on mobile the window was large
|
|
413
|
+
* enough for it to happen every time.
|
|
414
|
+
*
|
|
415
|
+
* `returnValue` tells the two cases apart. The browser sets it to the
|
|
416
|
+
* value of the button that closed the dialog, so a dialog that has never
|
|
417
|
+
* been open has it empty. A template that only wrote `open` on the host,
|
|
418
|
+
* as the documentation showed for a long time, also has it empty, and is
|
|
419
|
+
* opened as before.
|
|
420
|
+
*
|
|
421
|
+
* This works only when the button has a `value`. If the dialog is closed
|
|
422
|
+
* with a button without a value, or with `close()` without an argument,
|
|
423
|
+
* the browser leaves nothing to look for, and the dialog opens again as it
|
|
424
|
+
* did before. That is why every `<form method="dialog">` in the
|
|
425
|
+
* documentation has a `value`, and it is worth keeping.
|
|
426
|
+
*/
|
|
427
|
+
if (!upgradeChecked.has(dialog)) {
|
|
428
|
+
upgradeChecked.add(dialog)
|
|
429
|
+
if (this.open && !dialog.open && dialog.returnValue !== "") {
|
|
430
|
+
this.removeAttribute("open")
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// `:modal` and not `open`. The two are not the same: a `<dialog open>`
|
|
436
|
+
// in the markup, and a dialog that was moved in the DOM while it was
|
|
437
|
+
// open, have `open` without being modal. Then it is a box on the page,
|
|
438
|
+
// without a focus trap and without Escape, which is exactly what the
|
|
439
|
+
// component exists to prevent.
|
|
440
|
+
const modal = dialog.matches(":modal")
|
|
441
|
+
|
|
442
|
+
/*
|
|
443
|
+
* The user has just closed the dialog with a button, and `close` has not
|
|
444
|
+
* arrived yet. The browser removes `open` at once and sends the event in a
|
|
445
|
+
* later task, and the host still says "open" until `handleClose` gets to
|
|
446
|
+
* run. When something wrote `open` on the host in the meantime, such as a
|
|
447
|
+
* framework setting the same value again, the component opened the dialog
|
|
448
|
+
* again and cleared `returnValue`, and a listener on `close` read an empty
|
|
449
|
+
* value instead of "delete". `returnValue` is cleared every time the
|
|
450
|
+
* dialog opens, so a value here comes from a button in this round.
|
|
451
|
+
*/
|
|
452
|
+
if (
|
|
453
|
+
this.open &&
|
|
454
|
+
!modal &&
|
|
455
|
+
this.lastNotified === true &&
|
|
456
|
+
dialog.returnValue !== ""
|
|
457
|
+
) {
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (this.open && !modal) {
|
|
462
|
+
/*
|
|
463
|
+
* `showModal()` throws `InvalidStateError` when `open` is already there
|
|
464
|
+
* without the dialog being modal. So the attribute must go first.
|
|
465
|
+
*
|
|
466
|
+
* But not with `close()`. It sends a real `close` event, and since the
|
|
467
|
+
* server now writes `open` on the `<dialog>` itself, that event would
|
|
468
|
+
* have come on every single load of a dialog that is open. An app
|
|
469
|
+
* listening for `close` directly on the `<dialog>`, as the complete
|
|
470
|
+
* example in the documentation does, then got a phantom close before the
|
|
471
|
+
* user had seen the dialog. `removeAttribute` makes room without the
|
|
472
|
+
* event.
|
|
473
|
+
*/
|
|
474
|
+
dialog.removeAttribute("open")
|
|
475
|
+
/*
|
|
476
|
+
* Escape and `close()` without an argument leave the previous
|
|
477
|
+
* `returnValue` in place. A dialog the server opened again then
|
|
478
|
+
* reported "delete" from last time when it was closed from the server,
|
|
479
|
+
* and the app that tells "Cancel" from "Delete" by the value deleted
|
|
480
|
+
* something the user never asked for.
|
|
481
|
+
*/
|
|
482
|
+
dialog.returnValue = ""
|
|
483
|
+
dialog.showModal()
|
|
484
|
+
this.notify(true)
|
|
485
|
+
} else if (!this.open && (modal || dialog.open)) {
|
|
486
|
+
/*
|
|
487
|
+
* `modal` and not only `dialog.open`: the attribute can be gone while
|
|
488
|
+
* the dialog is still in the top layer.
|
|
489
|
+
*
|
|
490
|
+
* React is the reason. The server writes `open` on the `<dialog>`, so
|
|
491
|
+
* React manages the attribute, and React updates children before the
|
|
492
|
+
* parent. When the app closes the dialog, React first removes `open`
|
|
493
|
+
* from the `<dialog>` and then from `<lt-dialog>`. So we get here with
|
|
494
|
+
* `dialog.open` already false.
|
|
495
|
+
*
|
|
496
|
+
* `close()` does nothing without the attribute, so without this the
|
|
497
|
+
* dialog stayed in the top layer, invisible, with the rest of the page
|
|
498
|
+
* inert. The user was left with a page where nothing could be clicked,
|
|
499
|
+
* and nothing visible to explain why.
|
|
500
|
+
*/
|
|
501
|
+
setAttr(dialog, "open", "")
|
|
502
|
+
dialog.close()
|
|
503
|
+
this.notify(false, dialog.returnValue)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function defineLtDialog(tagName: string = LT_DIALOG_TAG): void {
|
|
509
|
+
defineElement(tagName, LtDialog)
|
|
510
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@import "../../css/list/list.css";
|
|
2
|
+
|
|
3
|
+
@layer lotek {
|
|
4
|
+
lt-error-summary[hidden] {
|
|
5
|
+
display: none;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* The component sets the class on the element itself. The consumer's
|
|
9
|
+
markup stays where it is. */
|
|
10
|
+
.lt-error-summary {
|
|
11
|
+
display: block;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
padding: var(--lt-error-summary-padding, var(--lt-spacing-4));
|
|
14
|
+
font-family: inherit;
|
|
15
|
+
font-size: var(--lt-font-size-m);
|
|
16
|
+
line-height: var(--lt-line-height-default);
|
|
17
|
+
color: var(--lt-color-neutral-text-strong);
|
|
18
|
+
background: var(--lt-color-danger-surface);
|
|
19
|
+
border-inline-start: var(--lt-spacing-1) solid var(--lt-color-danger-text);
|
|
20
|
+
border-radius: var(--lt-error-summary-radius, var(--lt-spacing-1));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.lt-error-summary:focus-visible {
|
|
24
|
+
outline: var(--lt-focus-ring);
|
|
25
|
+
outline-color: var(--lt-color-danger-text);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.lt-error-summary__title {
|
|
30
|
+
margin: 0 0 var(--lt-spacing-2);
|
|
31
|
+
font-size: var(--lt-font-size-l);
|
|
32
|
+
font-weight: var(--lt-font-weight-semibold);
|
|
33
|
+
line-height: var(--lt-line-height-compact);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.lt-error-summary a {
|
|
37
|
+
color: var(--lt-color-danger-text);
|
|
38
|
+
text-decoration: underline;
|
|
39
|
+
text-underline-offset: 0.14em;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.lt-error-summary a:focus-visible {
|
|
43
|
+
outline: var(--lt-focus-ring);
|
|
44
|
+
outline-color: var(--lt-color-danger-text);
|
|
45
|
+
outline-offset: 2px;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { attributes } from "../../css/shared.js"
|
|
2
|
+
|
|
3
|
+
export const ERROR_SUMMARY_CLASS = "lt-error-summary" as const
|
|
4
|
+
export const ERROR_SUMMARY_TITLE_CLASS = "lt-error-summary__title" as const
|
|
5
|
+
|
|
6
|
+
export type ErrorSummaryOptions = {
|
|
7
|
+
/** The number of errors the server found. When it is zero, the box is hidden. */
|
|
8
|
+
count?: number
|
|
9
|
+
/** The id of the box, so the form can move focus here after submission. */
|
|
10
|
+
id?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ErrorSummaryAttributes = {
|
|
14
|
+
host: {
|
|
15
|
+
class: typeof ERROR_SUMMARY_CLASS
|
|
16
|
+
role: "alert"
|
|
17
|
+
tabindex: "-1"
|
|
18
|
+
id?: string
|
|
19
|
+
hidden?: true
|
|
20
|
+
}
|
|
21
|
+
title: {
|
|
22
|
+
class: typeof ERROR_SUMMARY_TITLE_CLASS
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The attributes for an error summary.
|
|
28
|
+
*
|
|
29
|
+
* The server writes the whole box, including the heading and the list.
|
|
30
|
+
* `<lt-error-summary>` only moves focus and handles clicks on the links. The
|
|
31
|
+
* component used to create the heading itself, and then it disappeared on
|
|
32
|
+
* every morph in Datastar.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const summary = lt.errorSummary({ count: errors.length, id: "errors" })
|
|
36
|
+
* ```
|
|
37
|
+
* ```html
|
|
38
|
+
* <lt-error-summary {...summary.host}>
|
|
39
|
+
* <h2 {...summary.title}>You need to fix 2 errors</h2>
|
|
40
|
+
* <ul><li><a href="#email">The email address is missing an @</a></li></ul>
|
|
41
|
+
* </lt-error-summary>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const errorSummary = ({
|
|
45
|
+
count = 0,
|
|
46
|
+
id,
|
|
47
|
+
}: ErrorSummaryOptions = {}): ErrorSummaryAttributes => ({
|
|
48
|
+
host: attributes({
|
|
49
|
+
class: ERROR_SUMMARY_CLASS,
|
|
50
|
+
role: "alert" as const,
|
|
51
|
+
tabindex: "-1" as const,
|
|
52
|
+
id,
|
|
53
|
+
hidden: count === 0 ? (true as const) : undefined,
|
|
54
|
+
}),
|
|
55
|
+
title: attributes({ class: ERROR_SUMMARY_TITLE_CLASS }),
|
|
56
|
+
})
|