@threadlabs/looma 0.14.7 → 0.15.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/components/shared/icons.js +5 -0
- package/components/shared/menu-navigation.js +49 -0
- package/components/shared/overlay.js +33 -60
- package/components/ui-avatar/ui-avatar.html +19 -2
- package/components/ui-avatar/ui-avatar.js +7 -1
- package/components/ui-avatar-group/ui-avatar-group.html +8 -3
- package/components/ui-avatar-group/ui-avatar-group.js +6 -2
- package/components/ui-badge/ui-badge.html +70 -4
- package/components/ui-button/ui-button.html +21 -5
- package/components/ui-button/ui-button.js +10 -0
- package/components/ui-callout/ui-callout.html +13 -5
- package/components/ui-checkbox/ui-checkbox.html +11 -0
- package/components/ui-combobox/ui-combobox.html +50 -10
- package/components/ui-combobox/ui-combobox.js +11 -8
- package/components/ui-context-menu/ui-context-menu.html +33 -5
- package/components/ui-context-menu/ui-context-menu.js +26 -17
- package/components/ui-dialog/ui-dialog.html +51 -8
- package/components/ui-dialog/ui-dialog.js +28 -7
- package/components/ui-disclosure/ui-disclosure.html +14 -5
- package/components/ui-disclosure/ui-disclosure.js +51 -4
- package/components/ui-editable/ui-editable.html +17 -3
- package/components/ui-editable/ui-editable.js +7 -0
- package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +48 -14
- package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.js +40 -6
- package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +22 -9
- package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +33 -11
- package/components/ui-editor-mention-menu-item/ui-editor-mention-menu-item.html +67 -0
- package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +33 -30
- package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +42 -8
- package/components/ui-editor-slash-menu-group/ui-editor-slash-menu-group.html +31 -0
- package/components/ui-editor-slash-menu-item/ui-editor-slash-menu-item.html +75 -0
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +16 -5
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +10 -2
- package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +5 -5
- package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +5 -2
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +14 -4
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +2 -2
- package/components/ui-editor-toolbar/ui-editor-toolbar.html +1 -1
- package/components/ui-editor-toolbar/ui-editor-toolbar.js +46 -0
- package/components/ui-floating-action-button/ui-floating-action-button.html +1 -1
- package/components/ui-form-field/ui-form-field.html +16 -3
- package/components/ui-form-field/ui-form-field.js +1 -1
- package/components/ui-grid/ui-grid.html +17 -2
- package/components/ui-icon/ui-icon.html +5 -0
- package/components/ui-icon-button/ui-icon-button.html +25 -6
- package/components/ui-icon-button/ui-icon-button.js +11 -1
- package/components/ui-input/ui-input.html +3 -3
- package/components/ui-input-group/ui-input-group.html +18 -4
- package/components/ui-listbox/ui-listbox.html +81 -0
- package/components/ui-listbox/ui-listbox.js +27 -0
- package/components/ui-menu/ui-menu.html +33 -5
- package/components/ui-menu/ui-menu.js +18 -12
- package/components/ui-menu-group/ui-menu-group.html +27 -0
- package/components/ui-menu-item/ui-menu-item.html +80 -8
- package/components/ui-menu-item/ui-menu-item.js +47 -0
- package/components/ui-popover/ui-popover.html +33 -5
- package/components/ui-popover/ui-popover.js +9 -1
- package/components/ui-radio-group/ui-radio-group.html +13 -1
- package/components/ui-search-shell/ui-search-shell.html +6 -6
- package/components/ui-search-shell/ui-search-shell.js +21 -5
- package/components/ui-select/ui-select.html +14 -4
- package/components/ui-switch/ui-switch.html +2 -2
- package/components/ui-switcher/ui-switcher.html +8 -1
- package/components/ui-switcher/ui-switcher.js +15 -0
- package/components/ui-tabs/ui-tabs.html +17 -12
- package/components/ui-tabs/ui-tabs.js +43 -7
- package/components/ui-textarea/ui-textarea.html +28 -5
- package/components/ui-toast/ui-toast.html +88 -0
- package/components/ui-toast/ui-toast.js +68 -0
- package/components/ui-toast-region/ui-toast-region.html +102 -19
- package/components/ui-toast-region/ui-toast-region.js +10 -18
- package/components/ui-tooltip/ui-tooltip.html +51 -7
- package/components/ui-tooltip/ui-tooltip.js +29 -3
- package/components/ui-tree/ui-tree.html +12 -0
- package/components/ui-tree/ui-tree.js +191 -12
- package/components/ui-tree-item/ui-tree-item.html +87 -8
- package/components/ui-tree-item/ui-tree-item.js +3 -1
- package/dist/index.js +38 -32
- package/editor/chunks/extensions.js +82 -2
- package/editor/extensions/table-commands.d.ts +8 -0
- package/editor/icons.d.ts +5 -0
- package/editor/index.js +7 -2
- package/editor/insert-table-grid.d.ts +1 -1
- package/editor/mention-menu.d.ts +2 -0
- package/editor/slash-menu.d.ts +3 -0
- package/editor/table-context-menu.d.ts +1 -1
- package/editor/table-overlay.d.ts +2 -2
- package/package.json +1 -1
- package/styles/ui-avatar-group.css +1 -2
- package/styles/ui-avatar.css +11 -1
- package/styles/ui-badge.css +73 -1
- package/styles/ui-button.css +9 -1
- package/styles/ui-callout.css +1 -1
- package/styles/ui-checkbox.css +9 -0
- package/styles/ui-combobox.css +38 -7
- package/styles/ui-context-menu.css +32 -4
- package/styles/ui-dialog.css +42 -5
- package/styles/ui-editor-insert-table-grid.css +48 -11
- package/styles/ui-editor-mention-menu-item.css +57 -0
- package/styles/ui-editor-mention-menu.css +11 -1
- package/styles/ui-editor-slash-menu-group.css +21 -0
- package/styles/ui-editor-slash-menu-item.css +62 -0
- package/styles/ui-editor-slash-menu.css +10 -21
- package/styles/ui-editor-table-context-menu.css +1 -1
- package/styles/ui-floating-action-button.css +1 -1
- package/styles/ui-form-field.css +14 -1
- package/styles/ui-grid.css +14 -1
- package/styles/ui-icon-button.css +17 -5
- package/styles/ui-input-group.css +18 -4
- package/styles/ui-input.css +3 -3
- package/styles/ui-listbox.css +69 -0
- package/styles/ui-menu-group.css +19 -0
- package/styles/ui-menu-item.css +48 -9
- package/styles/ui-menu.css +32 -4
- package/styles/ui-popover.css +28 -4
- package/styles/ui-radio-group.css +9 -0
- package/styles/ui-search-shell.css +5 -5
- package/styles/ui-select.css +14 -4
- package/styles/ui-switch.css +2 -2
- package/styles/ui-switcher.css +4 -0
- package/styles/ui-textarea.css +27 -4
- package/styles/ui-toast-region.css +88 -4
- package/styles/ui-toast.css +74 -0
- package/styles/ui-tooltip.css +46 -6
- package/styles/ui-tree-item.css +72 -4
- package/styles/ui-tree.css +8 -0
- package/tokens.css +3 -0
- package/vanilla/UiAvatar.d.ts +2 -0
- package/vanilla/UiAvatar.js +8 -1
- package/vanilla/UiAvatarGroup.d.ts +2 -0
- package/vanilla/UiAvatarGroup.js +1 -1
- package/vanilla/UiBadge.d.ts +1 -1
- package/vanilla/UiBadge.js +1 -1
- package/vanilla/UiButton.d.ts +1 -0
- package/vanilla/UiButton.js +22 -6
- package/vanilla/UiCallout.d.ts +1 -1
- package/vanilla/UiCallout.js +1 -1
- package/vanilla/UiCheckbox.d.ts +1 -0
- package/vanilla/UiCheckbox.js +12 -9
- package/vanilla/UiCombobox.d.ts +4 -0
- package/vanilla/UiCombobox.js +30 -15
- package/vanilla/UiContextMenu.d.ts +1 -1
- package/vanilla/UiContextMenu.js +1 -1
- package/vanilla/UiDialog.d.ts +5 -2
- package/vanilla/UiDialog.js +1 -1
- package/vanilla/UiDisclosure.d.ts +2 -0
- package/vanilla/UiDisclosure.js +32 -18
- package/vanilla/UiEditable.d.ts +5 -0
- package/vanilla/UiEditable.js +7 -3
- package/vanilla/UiEditorInsertTableGrid.js +1 -6
- package/vanilla/UiEditorMentionMenu.d.ts +4 -2
- package/vanilla/UiEditorMentionMenu.js +49 -31
- package/vanilla/UiEditorMentionMenuItem.d.ts +16 -0
- package/vanilla/UiEditorMentionMenuItem.js +56 -0
- package/vanilla/UiEditorSlashMenu.d.ts +4 -3
- package/vanilla/UiEditorSlashMenu.js +59 -46
- package/vanilla/UiEditorSlashMenuGroup.d.ts +14 -0
- package/vanilla/UiEditorSlashMenuGroup.js +48 -0
- package/vanilla/UiEditorSlashMenuItem.d.ts +18 -0
- package/vanilla/UiEditorSlashMenuItem.js +57 -0
- package/vanilla/UiEditorTableContextMenu.d.ts +5 -2
- package/vanilla/UiEditorTableContextMenu.js +1 -2
- package/vanilla/UiEditorTableOverlay.d.ts +1 -1
- package/vanilla/UiEditorTableOverlay.js +5 -5
- package/vanilla/UiEditorTableToolbar.d.ts +4 -2
- package/vanilla/UiEditorTableToolbar.js +1 -2
- package/vanilla/UiEditorToolbar.js +2 -0
- package/vanilla/UiGrid.d.ts +1 -0
- package/vanilla/UiGrid.js +8 -7
- package/vanilla/UiIcon.js +1 -1
- package/vanilla/UiIconButton.d.ts +1 -0
- package/vanilla/UiIconButton.js +11 -2
- package/vanilla/UiListbox.d.ts +20 -0
- package/vanilla/UiListbox.js +45 -0
- package/vanilla/UiMenu.d.ts +1 -1
- package/vanilla/UiMenu.js +1 -1
- package/vanilla/UiMenuGroup.d.ts +14 -0
- package/vanilla/UiMenuGroup.js +35 -0
- package/vanilla/UiMenuItem.d.ts +5 -1
- package/vanilla/UiMenuItem.js +91 -30
- package/vanilla/UiPopover.d.ts +1 -1
- package/vanilla/UiPopover.js +1 -1
- package/vanilla/UiRadioGroup.d.ts +1 -0
- package/vanilla/UiRadioGroup.js +6 -3
- package/vanilla/UiSwitcher.d.ts +1 -0
- package/vanilla/UiSwitcher.js +7 -9
- package/vanilla/UiTabs.d.ts +1 -0
- package/vanilla/UiTabs.js +9 -1
- package/vanilla/UiTextarea.d.ts +1 -0
- package/vanilla/UiTextarea.js +4 -1
- package/vanilla/UiToast.d.ts +16 -0
- package/vanilla/UiToast.js +53 -0
- package/vanilla/UiToastRegion.d.ts +1 -4
- package/vanilla/UiToastRegion.js +3 -11
- package/vanilla/UiTooltip.d.ts +1 -1
- package/vanilla/UiTooltip.js +1 -1
- package/vanilla/UiTree.d.ts +2 -0
- package/vanilla/UiTree.js +1 -1
- package/vanilla/UiTreeItem.d.ts +1 -0
- package/vanilla/UiTreeItem.js +45 -31
- package/vanilla/index.js +6 -0
- package/vue/UiAvatar.d.ts +4 -0
- package/vue/UiAvatar.vue +23 -3
- package/vue/UiAvatarGroup.d.ts +3 -0
- package/vue/UiAvatarGroup.vue +7 -4
- package/vue/UiBadge.d.ts +2 -2
- package/vue/UiBadge.vue +74 -2
- package/vue/UiButton.d.ts +5 -3
- package/vue/UiButton.vue +41 -7
- package/vue/UiCallout.d.ts +2 -2
- package/vue/UiCallout.vue +3 -2
- package/vue/UiCheckbox.d.ts +2 -0
- package/vue/UiCheckbox.vue +16 -0
- package/vue/UiCombobox.d.ts +13 -3
- package/vue/UiCombobox.vue +62 -11
- package/vue/UiContextMenu.d.ts +2 -0
- package/vue/UiContextMenu.vue +47 -6
- package/vue/UiDialog.d.ts +18 -4
- package/vue/UiDialog.vue +86 -11
- package/vue/UiDisclosure.d.ts +8 -2
- package/vue/UiDisclosure.vue +38 -14
- package/vue/UiEditable.d.ts +10 -0
- package/vue/UiEditable.vue +14 -2
- package/vue/UiEditorInsertTableGrid.vue +52 -15
- package/vue/UiEditorMentionMenu.d.ts +24 -2
- package/vue/UiEditorMentionMenu.vue +27 -11
- package/vue/UiEditorMentionMenuItem.d.ts +24 -0
- package/vue/UiEditorMentionMenuItem.js +2 -0
- package/vue/UiEditorMentionMenuItem.vue +88 -0
- package/vue/UiEditorSlashMenu.d.ts +24 -1
- package/vue/UiEditorSlashMenu.vue +33 -33
- package/vue/UiEditorSlashMenuGroup.d.ts +18 -0
- package/vue/UiEditorSlashMenuGroup.js +2 -0
- package/vue/UiEditorSlashMenuGroup.vue +43 -0
- package/vue/UiEditorSlashMenuItem.d.ts +26 -0
- package/vue/UiEditorSlashMenuItem.js +2 -0
- package/vue/UiEditorSlashMenuItem.vue +101 -0
- package/vue/UiEditorTableContextMenu.d.ts +9 -3
- package/vue/UiEditorTableContextMenu.vue +53 -3
- package/vue/UiEditorTableOverlay.d.ts +2 -2
- package/vue/UiEditorTableOverlay.vue +8 -6
- package/vue/UiEditorTableToolbar.d.ts +7 -3
- package/vue/UiEditorTableToolbar.vue +50 -2
- package/vue/UiEditorToolbar.vue +14 -1
- package/vue/UiFloatingActionButton.vue +1 -1
- package/vue/UiFormField.vue +28 -3
- package/vue/UiGrid.d.ts +1 -0
- package/vue/UiGrid.vue +26 -2
- package/vue/UiIcon.vue +16 -0
- package/vue/UiIconButton.d.ts +5 -3
- package/vue/UiIconButton.vue +40 -6
- package/vue/UiInput.vue +10 -3
- package/vue/UiInputGroup.vue +32 -11
- package/vue/UiListbox.d.ts +33 -0
- package/vue/UiListbox.js +2 -0
- package/vue/UiListbox.vue +131 -0
- package/vue/UiMenu.d.ts +2 -0
- package/vue/UiMenu.vue +43 -6
- package/vue/UiMenuGroup.d.ts +18 -0
- package/vue/UiMenuGroup.js +2 -0
- package/vue/UiMenuGroup.vue +39 -0
- package/vue/UiMenuItem.d.ts +34 -2
- package/vue/UiMenuItem.vue +145 -9
- package/vue/UiPopover.d.ts +2 -2
- package/vue/UiPopover.vue +44 -5
- package/vue/UiRadioGroup.d.ts +2 -0
- package/vue/UiRadioGroup.vue +17 -0
- package/vue/UiSearchShell.vue +12 -5
- package/vue/UiSelect.vue +18 -4
- package/vue/UiSwitch.vue +2 -2
- package/vue/UiSwitcher.d.ts +1 -0
- package/vue/UiSwitcher.vue +22 -1
- package/vue/UiTabs.d.ts +6 -2
- package/vue/UiTabs.vue +14 -12
- package/vue/UiTextarea.d.ts +2 -0
- package/vue/UiTextarea.vue +32 -4
- package/vue/UiToast.d.ts +34 -0
- package/vue/UiToast.js +2 -0
- package/vue/UiToast.vue +173 -0
- package/vue/UiToastRegion.d.ts +2 -18
- package/vue/UiToastRegion.vue +123 -48
- package/vue/UiTooltip.d.ts +2 -2
- package/vue/UiTooltip.vue +67 -12
- package/vue/UiTree.d.ts +10 -0
- package/vue/UiTree.vue +27 -0
- package/vue/UiTreeItem.d.ts +2 -0
- package/vue/UiTreeItem.vue +96 -4
- package/vue/chunks/UiAvatar.js +19 -5
- package/vue/chunks/UiAvatarGroup.js +10 -4
- package/vue/chunks/UiBadge.js +1 -1
- package/vue/chunks/UiButton.js +58 -11
- package/vue/chunks/UiCallout.js +2 -2
- package/vue/chunks/UiCheckbox.js +12 -2
- package/vue/chunks/UiCombobox.js +30 -13
- package/vue/chunks/UiContextMenu.js +6 -2
- package/vue/chunks/UiDialog.js +25 -8
- package/vue/chunks/UiDisclosure.js +36 -12
- package/vue/chunks/UiEditable.js +22 -8
- package/vue/chunks/UiEditorInsertTableGrid.js +11 -14
- package/vue/chunks/UiEditorMentionMenu.js +30 -19
- package/vue/chunks/UiEditorMentionMenuItem.js +36 -0
- package/vue/chunks/UiEditorSlashMenu.js +33 -35
- package/vue/chunks/UiEditorSlashMenuGroup.js +26 -0
- package/vue/chunks/UiEditorSlashMenuItem.js +51 -0
- package/vue/chunks/UiEditorTableContextMenu.js +39 -5
- package/vue/chunks/UiEditorTableOverlay.js +6 -6
- package/vue/chunks/UiEditorTableToolbar.js +37 -4
- package/vue/chunks/UiEditorToolbar.js +18 -4
- package/vue/chunks/UiFloatingActionButton.js +1 -1
- package/vue/chunks/UiFormField.js +13 -9
- package/vue/chunks/UiGrid.js +6 -2
- package/vue/chunks/UiIcon.js +40 -2
- package/vue/chunks/UiIconButton.js +35 -4
- package/vue/chunks/UiInput.js +1 -1
- package/vue/chunks/UiInputGroup.js +1 -1
- package/vue/chunks/UiListbox.js +70 -0
- package/vue/chunks/UiMenu.js +6 -2
- package/vue/chunks/UiMenuGroup.js +25 -0
- package/vue/chunks/UiMenuItem.js +128 -7
- package/vue/chunks/UiPopover.js +1 -1
- package/vue/chunks/UiRadioGroup.js +8 -2
- package/vue/chunks/UiSearchShell.js +1 -1
- package/vue/chunks/UiSelect.js +1 -1
- package/vue/chunks/UiSwitch.js +1 -1
- package/vue/chunks/UiSwitcher.js +20 -3
- package/vue/chunks/UiTabs.js +4 -3
- package/vue/chunks/UiTextarea.js +7 -2
- package/vue/chunks/UiToast.js +81 -0
- package/vue/chunks/UiToastRegion.js +25 -49
- package/vue/chunks/UiTooltip.js +3 -7
- package/vue/chunks/UiTree.js +8 -4
- package/vue/chunks/UiTreeItem.js +28 -10
- package/vue/components.css +1840 -851
- package/vue/editor/index.js +263 -2
- package/vue/index.d.ts +6 -0
- package/vue/index.js +9 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<prop name="checked" type="boolean" default="false"
|
|
4
4
|
>Whether the checkbox is checked initially or when set externally. A form reset returns it to this.</prop>
|
|
5
5
|
<prop name="disabled" type="boolean" default="false">Disables the native checkbox, so it cannot be toggled or focused.</prop>
|
|
6
|
+
<prop name="invalid" type="boolean" default="false">Exposes an externally validated error and styles the checkbox with the field danger colour.</prop>
|
|
6
7
|
<prop name="indeterminate" type="boolean" default="false"
|
|
7
8
|
>Shows the mixed state, for a checkbox that summarises a group whose items are partly checked. It sets the native indeterminate property, which is
|
|
8
9
|
independent of checked, so update it as the group changes.</prop>
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
.checked="internalChecked"
|
|
29
30
|
.indeterminate="indeterminate"
|
|
30
31
|
:disabled="disabled"
|
|
32
|
+
:aria-invalid="invalid"
|
|
31
33
|
:required="required"
|
|
32
34
|
:name="name"
|
|
33
35
|
:value="value"
|
|
@@ -43,6 +45,10 @@
|
|
|
43
45
|
syntax: "*";
|
|
44
46
|
inherits: false;
|
|
45
47
|
}
|
|
48
|
+
@property --ui-checkbox-invalid-border {
|
|
49
|
+
syntax: "*";
|
|
50
|
+
inherits: false;
|
|
51
|
+
}
|
|
46
52
|
@property --ui-checkbox-size {
|
|
47
53
|
syntax: "*";
|
|
48
54
|
inherits: false;
|
|
@@ -51,6 +57,7 @@
|
|
|
51
57
|
:host {
|
|
52
58
|
/* Hooks do not inherit, so the parts below read them from the root. */
|
|
53
59
|
--_ui-checkbox-size: var(--ui-checkbox-size);
|
|
60
|
+
--_ui-checkbox-invalid-border: var(--ui-checkbox-invalid-border, var(--ui-field-danger, var(--ui-danger-solid)));
|
|
54
61
|
|
|
55
62
|
display: inline-flex;
|
|
56
63
|
align-items: flex-start;
|
|
@@ -148,6 +155,10 @@
|
|
|
148
155
|
outline-offset: 2px;
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
:host-state([invalid]) input {
|
|
159
|
+
border-color: var(--_ui-checkbox-invalid-border);
|
|
160
|
+
}
|
|
161
|
+
|
|
151
162
|
:host-state([disabled]) {
|
|
152
163
|
color: var(--ui-disabled-text);
|
|
153
164
|
cursor: not-allowed;
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
<prop name="query" type="string"
|
|
34
34
|
>The input's text, when the consumer controls it. Once set, the consumer owns the text: typing reports query-change, and the input returns to query unless
|
|
35
35
|
query is updated to match. Leave it unset to let the combobox manage its own text, showing the chosen option's label.</prop>
|
|
36
|
+
<prop name="filter" type="label | none" default="label"
|
|
37
|
+
>How the list narrows as the user types. label lists the options whose label contains the typed text, ignoring case. none lists every authored option, for
|
|
38
|
+
options already narrowed elsewhere, such as a server search, so a result that matched on data other than its label still shows. Leaving the field still
|
|
39
|
+
commits an option whose label matches the text, or the only one listed.</prop>
|
|
36
40
|
<prop name="allowFreeText" type="boolean" default="false">Allows values that do not match an authored option.</prop>
|
|
37
41
|
<prop name="allowCreate" type="boolean" default="false"
|
|
38
42
|
>Offers the current query as a new option, reported as create-item. When nothing else matches, the offer is highlighted, so Enter creates it; with nothing
|
|
@@ -46,6 +50,11 @@
|
|
|
46
50
|
<prop name="required" type="boolean" default="false"
|
|
47
51
|
>Requires a value: adds an asterisk to the label, marks the input required for native form validation, and makes validation report "A value is required."
|
|
48
52
|
when the field is left empty. In multiple mode a chosen item satisfies it, whatever is left in the input.</prop>
|
|
53
|
+
<prop name="invalid" type="boolean" default="false"
|
|
54
|
+
>Exposes an externally validated error on the input and field. It remains until the consumer clears it, independently of native validation.</prop>
|
|
55
|
+
<prop name="openOnFocus" type="boolean" default="false">Opens suggestions when the input receives focus, before typing.</prop>
|
|
56
|
+
<prop name="loading" type="boolean" default="false"
|
|
57
|
+
>Marks the suggestion list busy while the consumer loads options. It announces loading and suppresses the empty state and create offer until cleared.</prop>
|
|
49
58
|
<prop name="size" type="sm | md | lg" default="md"
|
|
50
59
|
>Field height, padding, and type size, matching Button's sizes so a field and a button of one size share a row. sm is the small control height (2rem by
|
|
51
60
|
default) with smaller text, for compact rows such as a table row or a filter bar; md is the standard control height; lg is taller with roomier padding.
|
|
@@ -77,7 +86,6 @@
|
|
|
77
86
|
:value="[]"
|
|
78
87
|
></state>
|
|
79
88
|
<state name="active" :value="-1"></state>
|
|
80
|
-
<state name="loading" :value="false"></state>
|
|
81
89
|
<state name="helpOpen" :value="false"></state>
|
|
82
90
|
<state name="lookupError" :value="''"></state>
|
|
83
91
|
<state
|
|
@@ -159,8 +167,8 @@
|
|
|
159
167
|
autocomplete="off"
|
|
160
168
|
:aria-controls="format('%s-listbox', uid)"
|
|
161
169
|
:aria-expanded="expanded"
|
|
162
|
-
:aria-invalid="validationStatus = 'error'"
|
|
163
|
-
:aria-busy="validationStatus = 'pending'"
|
|
170
|
+
:aria-invalid="invalid or validationStatus = 'error'"
|
|
171
|
+
:aria-busy="loading or validationStatus = 'pending'"
|
|
164
172
|
.value="display"
|
|
165
173
|
:placeholder="placeholder"
|
|
166
174
|
:disabled="disabled"
|
|
@@ -258,6 +266,7 @@
|
|
|
258
266
|
>
|
|
259
267
|
</div>
|
|
260
268
|
<div $if="message" class="message" $value="message"></div>
|
|
269
|
+
<div $if="not loading and not rows[0] and not creatable and not lookupError" class="message"><slot name="empty">No suggestions.</slot></div>
|
|
261
270
|
</div>
|
|
262
271
|
<slot name="footer"></slot>
|
|
263
272
|
</div>
|
|
@@ -309,6 +318,30 @@
|
|
|
309
318
|
syntax: "*";
|
|
310
319
|
inherits: false;
|
|
311
320
|
}
|
|
321
|
+
@property --ui-combobox-radius {
|
|
322
|
+
syntax: "*";
|
|
323
|
+
inherits: false;
|
|
324
|
+
}
|
|
325
|
+
@property --ui-combobox-invalid-border {
|
|
326
|
+
syntax: "*";
|
|
327
|
+
inherits: false;
|
|
328
|
+
}
|
|
329
|
+
@property --ui-combobox-popup-surface {
|
|
330
|
+
syntax: "*";
|
|
331
|
+
inherits: false;
|
|
332
|
+
}
|
|
333
|
+
@property --ui-combobox-popup-border {
|
|
334
|
+
syntax: "*";
|
|
335
|
+
inherits: false;
|
|
336
|
+
}
|
|
337
|
+
@property --ui-combobox-popup-radius {
|
|
338
|
+
syntax: "*";
|
|
339
|
+
inherits: false;
|
|
340
|
+
}
|
|
341
|
+
@property --ui-combobox-popup-shadow {
|
|
342
|
+
syntax: "*";
|
|
343
|
+
inherits: false;
|
|
344
|
+
}
|
|
312
345
|
|
|
313
346
|
:host {
|
|
314
347
|
/* Hooks do not inherit, so the parts below read them from the root. */
|
|
@@ -322,6 +355,12 @@
|
|
|
322
355
|
--_ui-combobox-item-min-block-size: var(--ui-combobox-item-min-block-size);
|
|
323
356
|
--_ui-combobox-item-input-min-inline-size: var(--ui-combobox-item-input-min-inline-size);
|
|
324
357
|
--_ui-combobox-item-item-max-inline-size: var(--ui-combobox-item-item-max-inline-size);
|
|
358
|
+
--_ui-combobox-radius: var(--ui-combobox-radius);
|
|
359
|
+
--_ui-combobox-invalid-border: var(--ui-combobox-invalid-border);
|
|
360
|
+
--_ui-combobox-popup-surface: var(--ui-combobox-popup-surface);
|
|
361
|
+
--_ui-combobox-popup-border: var(--ui-combobox-popup-border);
|
|
362
|
+
--_ui-combobox-popup-radius: var(--ui-combobox-popup-radius);
|
|
363
|
+
--_ui-combobox-popup-shadow: var(--ui-combobox-popup-shadow);
|
|
325
364
|
|
|
326
365
|
display: grid;
|
|
327
366
|
gap: var(--ui-space-1);
|
|
@@ -348,7 +387,7 @@
|
|
|
348
387
|
display: flex;
|
|
349
388
|
min-inline-size: 0;
|
|
350
389
|
border: 1px solid var(--ui-control-border, var(--ui-border-default));
|
|
351
|
-
border-radius: var(--ui-radius-md);
|
|
390
|
+
border-radius: var(--_ui-combobox-radius, var(--ui-field-radius, var(--ui-radius-md)));
|
|
352
391
|
background: var(--ui-control-surface, var(--ui-surface-default));
|
|
353
392
|
box-shadow: var(--ui-control-inset);
|
|
354
393
|
transition: border-color var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
|
|
@@ -466,8 +505,9 @@
|
|
|
466
505
|
.field:has(input:disabled) {
|
|
467
506
|
background: var(--ui-control-surface-disabled, var(--ui-surface-muted));
|
|
468
507
|
}
|
|
469
|
-
:host-state([validationStatus="error"]) .field
|
|
470
|
-
|
|
508
|
+
:host-state([validationStatus="error"]) .field,
|
|
509
|
+
:host-state([invalid]) .field {
|
|
510
|
+
border-color: var(--_ui-combobox-invalid-border, var(--ui-field-danger, var(--ui-danger-solid, var(--ui-accent-active))));
|
|
471
511
|
}
|
|
472
512
|
.popup {
|
|
473
513
|
box-sizing: border-box;
|
|
@@ -477,11 +517,11 @@
|
|
|
477
517
|
overflow: auto;
|
|
478
518
|
margin: 0;
|
|
479
519
|
padding: var(--ui-space-1);
|
|
480
|
-
border: 1px solid var(--ui-border-default);
|
|
481
|
-
border-radius: var(--ui-radius-md);
|
|
482
|
-
background: var(--ui-surface-elevated);
|
|
520
|
+
border: 1px solid var(--_ui-combobox-popup-border, var(--ui-overlay-border, var(--ui-border-default)));
|
|
521
|
+
border-radius: var(--_ui-combobox-popup-radius, var(--ui-overlay-radius, var(--ui-radius-md)));
|
|
522
|
+
background: var(--_ui-combobox-popup-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
|
|
483
523
|
color: var(--ui-text-primary);
|
|
484
|
-
box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
|
|
524
|
+
box-shadow: var(--_ui-combobox-popup-shadow, var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg))));
|
|
485
525
|
}
|
|
486
526
|
[hidden] {
|
|
487
527
|
display: none;
|
|
@@ -55,6 +55,8 @@ export default function controller(host) {
|
|
|
55
55
|
options: authoredOptions(authored),
|
|
56
56
|
allowFreeText: Boolean(host.state.allowFreeText),
|
|
57
57
|
allowCreate: Boolean(host.state.allowCreate),
|
|
58
|
+
// "none": the consumer already narrowed the options (a server search), so every one is listed.
|
|
59
|
+
filterByLabel: host.state.filter !== "none",
|
|
58
60
|
});
|
|
59
61
|
// Selected items work uncontrolled: the component keeps them and reports every change. A consumer
|
|
60
62
|
// that owns `items` stays in charge, because the prop resyncs whatever it sets. A consumer that sets
|
|
@@ -108,7 +110,6 @@ export default function controller(host) {
|
|
|
108
110
|
const cancelLookup = () => {
|
|
109
111
|
lookup?.abort();
|
|
110
112
|
lookup = undefined;
|
|
111
|
-
host.state.loading = false;
|
|
112
113
|
};
|
|
113
114
|
const close = () => {
|
|
114
115
|
host.state.expanded = false;
|
|
@@ -149,7 +150,7 @@ export default function controller(host) {
|
|
|
149
150
|
const filtered = options.filter((option) => {
|
|
150
151
|
if (ids.has(option.id)) return false;
|
|
151
152
|
ids.add(option.id);
|
|
152
|
-
if (fullSet) return true;
|
|
153
|
+
if (fullSet || !current.filterByLabel) return true;
|
|
153
154
|
return option.label.toLocaleLowerCase().includes(query.toLocaleLowerCase());
|
|
154
155
|
}).map((option) => ({
|
|
155
156
|
...option,
|
|
@@ -165,7 +166,6 @@ export default function controller(host) {
|
|
|
165
166
|
// the group-name list below. Concatenating a Map iterator produces one
|
|
166
167
|
// iterator row instead of the option objects it contains.
|
|
167
168
|
host.state.rows = Array.from(groups.values()).flat();
|
|
168
|
-
host.state.loading = false;
|
|
169
169
|
// When nothing matches what was typed, the offer to create it is the only choice, so it is
|
|
170
170
|
// the one Enter makes, and it is highlighted as such.
|
|
171
171
|
if (query && !host.state.rows.some((row) => !row.disabled) && canCreate()) host.state.active = host.state.rows.length;
|
|
@@ -267,7 +267,8 @@ export default function controller(host) {
|
|
|
267
267
|
};
|
|
268
268
|
// A strict combobox (single, no free text, no create) behaves like a select: leaving it resolves the
|
|
269
269
|
// typed text to a valid option (the highlighted one, an exact label, the first label it begins, or the
|
|
270
|
-
// only remaining option); with no match it reverts to the previous selection, or clears.
|
|
270
|
+
// only remaining option); with no match it reverts to the previous selection, or clears. With filter
|
|
271
|
+
// "none" the remaining options are all of them, whatever their labels, so the only one listed counts.
|
|
271
272
|
const resolveTyped = (trigger) => {
|
|
272
273
|
const current = config();
|
|
273
274
|
if (host.state.multiple || current.allowFreeText || current.allowCreate) return;
|
|
@@ -285,7 +286,7 @@ export default function controller(host) {
|
|
|
285
286
|
?? enabled.find((row) => row.label.toLocaleLowerCase() === query)
|
|
286
287
|
?? enabled.find((row) => row.label.toLocaleLowerCase().startsWith(query))
|
|
287
288
|
?? (() => {
|
|
288
|
-
const containing = enabled.filter((option) => option.label.toLocaleLowerCase().includes(query));
|
|
289
|
+
const containing = current.filterByLabel ? enabled.filter((option) => option.label.toLocaleLowerCase().includes(query)) : enabled;
|
|
289
290
|
return containing.length === 1 ? containing[0] : undefined;
|
|
290
291
|
})();
|
|
291
292
|
if (match) commit(match.value, match.label, match, "selection", trigger);
|
|
@@ -382,8 +383,7 @@ export default function controller(host) {
|
|
|
382
383
|
host.state.createIndex = rows.length;
|
|
383
384
|
// What a named combobox submits in single mode: the value, never the label the field shows.
|
|
384
385
|
host.state.submitted = String(host.state.selected ?? (host.state.allowFreeText ? host.state.raw ?? "" : ""));
|
|
385
|
-
host.state.message = host.state.loading ? "Loading suggestions…"
|
|
386
|
-
: host.state.lookupError || (!rows.length && !host.state.creatable ? "No suggestions." : "");
|
|
386
|
+
host.state.message = host.state.loading ? "Loading suggestions…" : host.state.lookupError || "";
|
|
387
387
|
const validation = host.state.validation ?? { status: "pristine", issues: [] };
|
|
388
388
|
host.state.validationStatus = validation.status;
|
|
389
389
|
const status = host.state.loading ? "Loading suggestions…" : host.state.lookupError || (host.state.expanded ? `${rows.length} suggestions available.` : "");
|
|
@@ -452,6 +452,9 @@ export default function controller(host) {
|
|
|
452
452
|
if (!host.state.multiple && config().allowFreeText && host.state.selected === null) commit(null, host.state.raw, null, "free-entry", "keyboard");
|
|
453
453
|
void validateCurrent();
|
|
454
454
|
};
|
|
455
|
+
const onFocusin = (event) => {
|
|
456
|
+
if (event.target === input && host.state.openOnFocus && !host.state.expanded) open();
|
|
457
|
+
};
|
|
455
458
|
const onCompositionstart = (event) => { if (event.target === input) composing = true; };
|
|
456
459
|
const onCompositionend = (event) => { if (event.target === input) { composing = false; onInput(new InputEvent("input")); } };
|
|
457
460
|
const onTooltipOpen = (event) => { if (event.target.closest?.('[role="tooltip"]')) host.state.helpOpen = true; };
|
|
@@ -479,7 +482,7 @@ export default function controller(host) {
|
|
|
479
482
|
|
|
480
483
|
const api = { get input() { return input; }, validate: validateCurrent };
|
|
481
484
|
instances.set(element, api);
|
|
482
|
-
const listeners = { input: onInput, keydown: onKeydown, click: onClick, pointerdown: onPointerdown, focusout: onFocusout, compositionstart: onCompositionstart, compositionend: onCompositionend, open: onTooltipOpen, close: onTooltipClose };
|
|
485
|
+
const listeners = { input: onInput, keydown: onKeydown, click: onClick, pointerdown: onPointerdown, focusin: onFocusin, focusout: onFocusout, compositionstart: onCompositionstart, compositionend: onCompositionend, open: onTooltipOpen, close: onTooltipClose };
|
|
483
486
|
for (const [name, listener] of Object.entries(listeners)) element.addEventListener(name, listener);
|
|
484
487
|
// Authored options can change after mount (renamed, replaced, or arriving late). A selected value
|
|
485
488
|
// then shows its current label, unless the user is editing the text.
|
|
@@ -11,11 +11,27 @@
|
|
|
11
11
|
name="close"
|
|
12
12
|
type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
|
|
13
13
|
></event>
|
|
14
|
-
<event name="select" type="object({ value: string, trigger: keyboard | pointer | programmatic })"></event>
|
|
14
|
+
<event name="select" type="object({ value: string, checked?: boolean, trigger: keyboard | pointer | programmatic })"></event>
|
|
15
15
|
</defs>
|
|
16
16
|
<span><div class="menu" $ref="menu" role="menu" aria-orientation="vertical"><slot></slot></div></span>
|
|
17
17
|
<style>
|
|
18
18
|
/* This component's hooks style the element they are set on, and never a nested one. */
|
|
19
|
+
@property --ui-context-menu-surface {
|
|
20
|
+
syntax: "*";
|
|
21
|
+
inherits: false;
|
|
22
|
+
}
|
|
23
|
+
@property --ui-context-menu-border {
|
|
24
|
+
syntax: "*";
|
|
25
|
+
inherits: false;
|
|
26
|
+
}
|
|
27
|
+
@property --ui-context-menu-radius {
|
|
28
|
+
syntax: "*";
|
|
29
|
+
inherits: false;
|
|
30
|
+
}
|
|
31
|
+
@property --ui-context-menu-shadow {
|
|
32
|
+
syntax: "*";
|
|
33
|
+
inherits: false;
|
|
34
|
+
}
|
|
19
35
|
@property --ui-context-menu-z-index {
|
|
20
36
|
syntax: "*";
|
|
21
37
|
inherits: false;
|
|
@@ -23,6 +39,10 @@
|
|
|
23
39
|
|
|
24
40
|
:host {
|
|
25
41
|
/* Hooks do not inherit, so the parts below read them from the root. */
|
|
42
|
+
--_ui-context-menu-surface: var(--ui-context-menu-surface);
|
|
43
|
+
--_ui-context-menu-border: var(--ui-context-menu-border);
|
|
44
|
+
--_ui-context-menu-radius: var(--ui-context-menu-radius);
|
|
45
|
+
--_ui-context-menu-shadow: var(--ui-context-menu-shadow);
|
|
26
46
|
--_ui-context-menu-z-index: var(--ui-context-menu-z-index);
|
|
27
47
|
|
|
28
48
|
display: contents;
|
|
@@ -36,10 +56,10 @@
|
|
|
36
56
|
flex-direction: column;
|
|
37
57
|
box-sizing: border-box;
|
|
38
58
|
padding: var(--ui-space-1);
|
|
39
|
-
background: var(--ui-surface-elevated);
|
|
40
|
-
border: 1px solid var(--ui-border-default);
|
|
41
|
-
border-radius: var(--ui-radius-lg);
|
|
42
|
-
box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
|
|
59
|
+
background: var(--_ui-context-menu-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
|
|
60
|
+
border: 1px solid var(--_ui-context-menu-border, var(--ui-overlay-border, var(--ui-border-default)));
|
|
61
|
+
border-radius: var(--_ui-context-menu-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
|
|
62
|
+
box-shadow: var(--_ui-context-menu-shadow, var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg))));
|
|
43
63
|
outline: 0;
|
|
44
64
|
overflow: auto;
|
|
45
65
|
z-index: var(--_ui-context-menu-z-index, 1000);
|
|
@@ -53,6 +73,14 @@
|
|
|
53
73
|
overlay var(--ui-motion-fast) allow-discrete;
|
|
54
74
|
}
|
|
55
75
|
|
|
76
|
+
.menu hr {
|
|
77
|
+
align-self: stretch;
|
|
78
|
+
block-size: 0;
|
|
79
|
+
margin: var(--ui-space-1) 0;
|
|
80
|
+
border: 0;
|
|
81
|
+
border-block-start: 1px solid var(--ui-border-default);
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
.menu:popover-open {
|
|
57
85
|
opacity: 1;
|
|
58
86
|
transform: translateY(0) scale(1);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closeOverlay, createAnchoredSurface, createIdResolver, openOverlay, requestTopOverlayClose } from "../shared/overlay.js";
|
|
2
|
+
import { menuItemFrom, menuItems, navigateMenu } from "../shared/menu-navigation.js";
|
|
2
3
|
|
|
3
4
|
function disabled(item) {
|
|
4
5
|
return item.getAttribute("aria-disabled") === "true" || item.hasAttribute("disabled") || item.getAttribute("disabled") === "true";
|
|
@@ -17,7 +18,7 @@ export default function controller(host) {
|
|
|
17
18
|
let lastOpenProp = Boolean(host.state.open);
|
|
18
19
|
host.state.internalOpen = lastOpenProp;
|
|
19
20
|
|
|
20
|
-
const items = () =>
|
|
21
|
+
const items = () => menuItems(element);
|
|
21
22
|
const focusFirst = () => requestAnimationFrame(() => items()[0]?.focus());
|
|
22
23
|
const targetEvents = { contextmenu: onContextMenu, keydown: onTargetKeydown, pointerdown: onPointerdown, pointerup: endPress, pointercancel: endPress };
|
|
23
24
|
const detach = () => {
|
|
@@ -101,33 +102,41 @@ export default function controller(host) {
|
|
|
101
102
|
clearTimeout(press?.timer);
|
|
102
103
|
press = null;
|
|
103
104
|
}
|
|
105
|
+
const select = (item, trigger) => {
|
|
106
|
+
if (!item || disabled(item)) return;
|
|
107
|
+
const value = item.getAttribute("data-value") ?? item.getAttribute("value") ?? "";
|
|
108
|
+
if (item.getAttribute("role") !== "menuitem") {
|
|
109
|
+
const detail = { trigger, checked: undefined };
|
|
110
|
+
item.dispatchEvent(new CustomEvent("ui-menu-item-activate", { detail }));
|
|
111
|
+
host.dispatch("select", { value, checked: detail.checked, trigger });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
host.dispatch("select", { value, trigger });
|
|
115
|
+
close("action", trigger);
|
|
116
|
+
};
|
|
104
117
|
const onKeydown = (event) => {
|
|
105
118
|
if (!host.state.internalOpen) return;
|
|
106
119
|
if (event.key === "Escape") {
|
|
107
120
|
event.preventDefault();
|
|
108
121
|
requestTopOverlayClose(document, "escape", "keyboard");
|
|
109
122
|
} else if (["Enter", " "].includes(event.key)) {
|
|
110
|
-
const item = event.target
|
|
123
|
+
const item = menuItemFrom(event.target);
|
|
111
124
|
if (!item || disabled(item)) return;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const next = event.key === "ArrowDown"
|
|
121
|
-
? (index < 0 ? 0 : Math.min(index + 1, enabled.length - 1))
|
|
122
|
-
: (index <= 0 ? enabled.length - 1 : index - 1);
|
|
123
|
-
enabled[next].focus();
|
|
125
|
+
if (item.localName === "a") {
|
|
126
|
+
if (event.key === " ") { event.preventDefault(); item.click(); }
|
|
127
|
+
} else {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
select(item, "keyboard");
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
navigateMenu(event, element);
|
|
124
133
|
}
|
|
125
134
|
};
|
|
126
135
|
const onClick = (event) => {
|
|
127
|
-
const item = event.target
|
|
136
|
+
const item = menuItemFrom(event.target);
|
|
128
137
|
if (!item || disabled(item)) return;
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
const trigger = event.detail === 0 ? "keyboard" : "pointer";
|
|
139
|
+
select(item, trigger);
|
|
131
140
|
};
|
|
132
141
|
resolveTargets();
|
|
133
142
|
if (menuSurface) surface = createAnchoredSurface(menuSurface, { anchor: target, placement: "bottom-start" });
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
<template component="ui-dialog" controller="./ui-dialog.js">
|
|
2
2
|
<defs>
|
|
3
3
|
<prop name="open" type="boolean" default="false">Whether the dialog is open initially or when set externally.</prop>
|
|
4
|
-
<prop name="
|
|
5
|
-
<prop name="
|
|
6
|
-
>
|
|
4
|
+
<prop name="modeless" type="boolean" default="false">Open without a backdrop or making the rest of the page inert; dialogs are modal by default.</prop>
|
|
5
|
+
<prop name="closedby" type="any | closerequest | none" default="closerequest"
|
|
6
|
+
>Which user close requests work: any includes outside presses, closerequest includes Escape, and none requires an action.</prop>
|
|
7
|
+
<prop name="alert" type="boolean" default="false">Use alert dialog semantics and require an explicit action instead of the header close button.</prop>
|
|
8
|
+
<prop name="size" type="sm | md | lg | fullscreen" default="md">Choose a compact, standard, wide, or viewport-filling dialog.</prop>
|
|
7
9
|
<prop name="label" type="string">Visible title in the dialog header; also its accessible name.</prop>
|
|
8
10
|
<prop name="for" type="string" default="">ID of the control that opens the dialog.</prop>
|
|
9
11
|
<state name="internalOpen" :value="false"></state>
|
|
10
12
|
<state name="accessibleLabel" :value="'Dialog'"></state>
|
|
13
|
+
<event
|
|
14
|
+
name="open"
|
|
15
|
+
type="object({ open: boolean, reason: action | programmatic, trigger: keyboard | pointer | programmatic })"
|
|
16
|
+
></event>
|
|
11
17
|
<event
|
|
12
18
|
name="close"
|
|
13
19
|
type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
|
|
@@ -27,6 +33,22 @@
|
|
|
27
33
|
syntax: "*";
|
|
28
34
|
inherits: false;
|
|
29
35
|
}
|
|
36
|
+
@property --ui-dialog-surface {
|
|
37
|
+
syntax: "*";
|
|
38
|
+
inherits: false;
|
|
39
|
+
}
|
|
40
|
+
@property --ui-dialog-border {
|
|
41
|
+
syntax: "*";
|
|
42
|
+
inherits: false;
|
|
43
|
+
}
|
|
44
|
+
@property --ui-dialog-radius {
|
|
45
|
+
syntax: "*";
|
|
46
|
+
inherits: false;
|
|
47
|
+
}
|
|
48
|
+
@property --ui-dialog-shadow {
|
|
49
|
+
syntax: "*";
|
|
50
|
+
inherits: false;
|
|
51
|
+
}
|
|
30
52
|
@property --ui-dialog-max-width {
|
|
31
53
|
syntax: "*";
|
|
32
54
|
inherits: false;
|
|
@@ -45,7 +67,7 @@
|
|
|
45
67
|
box-sizing: border-box;
|
|
46
68
|
inline-size: min(
|
|
47
69
|
calc(100% - var(--ui-dialog-viewport-gap, var(--ui-space-4)) * 2),
|
|
48
|
-
var(--ui-dialog-max-width, 440px)
|
|
70
|
+
var(--ui-dialog-max-width, var(--_ui-dialog-size, 440px))
|
|
49
71
|
);
|
|
50
72
|
max-inline-size: none;
|
|
51
73
|
max-block-size: calc(100dvh - var(--ui-dialog-viewport-gap, var(--ui-space-4)) * 2);
|
|
@@ -53,10 +75,10 @@
|
|
|
53
75
|
padding: 0;
|
|
54
76
|
overflow: hidden;
|
|
55
77
|
flex-direction: column;
|
|
56
|
-
background: var(--ui-surface-elevated);
|
|
57
|
-
border: 1px solid var(--ui-border-default);
|
|
58
|
-
border-radius: var(--ui-radius-dialog);
|
|
59
|
-
box-shadow: var(--ui-elevation-dialog, var(--ui-shadow-xl));
|
|
78
|
+
background: var(--ui-dialog-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
|
|
79
|
+
border: 1px solid var(--ui-dialog-border, var(--ui-overlay-border, var(--ui-border-default)));
|
|
80
|
+
border-radius: var(--ui-dialog-radius, var(--ui-overlay-radius, var(--ui-radius-dialog)));
|
|
81
|
+
box-shadow: var(--ui-dialog-shadow, var(--ui-overlay-shadow, var(--ui-elevation-dialog, var(--ui-shadow-xl))));
|
|
60
82
|
visibility: hidden;
|
|
61
83
|
pointer-events: none;
|
|
62
84
|
opacity: 0;
|
|
@@ -78,6 +100,27 @@
|
|
|
78
100
|
animation: ui-dialog-enter var(--ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease);
|
|
79
101
|
}
|
|
80
102
|
|
|
103
|
+
:host-state([size="sm"]) {
|
|
104
|
+
--_ui-dialog-size: 360px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:host-state([size="lg"]) {
|
|
108
|
+
--_ui-dialog-size: 640px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
:host-state([size="fullscreen"]) {
|
|
112
|
+
inset: 0;
|
|
113
|
+
inline-size: 100%;
|
|
114
|
+
block-size: 100dvh;
|
|
115
|
+
max-block-size: none;
|
|
116
|
+
margin: 0;
|
|
117
|
+
border-radius: var(--ui-dialog-radius, 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
:host-state([alert]) .close {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
81
124
|
/* Header and actions stay pinned; only the body scrolls when content is long. */
|
|
82
125
|
header {
|
|
83
126
|
display: flex;
|
|
@@ -13,16 +13,21 @@ export default function controller(host) {
|
|
|
13
13
|
let trigger = null;
|
|
14
14
|
let lastFor = "";
|
|
15
15
|
let lastExternalOpen = Boolean(host.state.open);
|
|
16
|
+
let lastOpen;
|
|
16
17
|
host.state.internalOpen = lastExternalOpen;
|
|
17
18
|
|
|
18
19
|
const requestClose = (reason, input) => {
|
|
19
20
|
host.state.internalOpen = false;
|
|
21
|
+
lastOpen = false;
|
|
20
22
|
host.dispatch("close", { open: false, reason, trigger: input });
|
|
21
23
|
};
|
|
22
24
|
const closeButton = host.refs.close;
|
|
23
25
|
const onCloseClick = (event) => requestClose("action", event.detail === 0 ? "keyboard" : "pointer");
|
|
24
|
-
const onTriggerClick = () => {
|
|
26
|
+
const onTriggerClick = (event) => {
|
|
27
|
+
if (host.state.internalOpen) return;
|
|
25
28
|
host.state.internalOpen = true;
|
|
29
|
+
lastOpen = true;
|
|
30
|
+
host.dispatch("open", { open: true, reason: "action", trigger: event.detail === 0 ? "keyboard" : "pointer" });
|
|
26
31
|
};
|
|
27
32
|
const ids = createIdResolver(document, () => {
|
|
28
33
|
lastFor = null;
|
|
@@ -49,30 +54,46 @@ export default function controller(host) {
|
|
|
49
54
|
}
|
|
50
55
|
host.state.accessibleLabel = inferredLabel(element, host.state.label);
|
|
51
56
|
dialog?.setAttribute("aria-label", String(host.state.accessibleLabel));
|
|
57
|
+
if (host.state.alert) dialog?.setAttribute("role", "alertdialog");
|
|
58
|
+
else dialog?.removeAttribute("role");
|
|
52
59
|
syncTrigger();
|
|
53
60
|
const open = Boolean(host.state.internalOpen);
|
|
61
|
+
const modal = !host.state.modeless;
|
|
62
|
+
const requestedClosedBy = String(host.state.closedby ?? "closerequest");
|
|
63
|
+
const closedBy = host.state.alert && requestedClosedBy === "any" ? "closerequest" : requestedClosedBy;
|
|
54
64
|
trigger?.setAttribute("aria-expanded", String(open));
|
|
55
65
|
if (!dialog) return;
|
|
66
|
+
dialog.setAttribute("closedby", closedBy);
|
|
56
67
|
if (open) {
|
|
68
|
+
if (dialog.open && dialog.matches(":modal") !== modal) dialog.close();
|
|
57
69
|
if (!dialog.open) {
|
|
58
|
-
if (
|
|
70
|
+
if (modal) dialog.showModal();
|
|
59
71
|
else dialog.show();
|
|
60
72
|
}
|
|
61
|
-
openOverlay({ id: overlayId, modal
|
|
73
|
+
openOverlay({ id: overlayId, modal, element, dismissible: true, canClose: (reason) => reason === "light-dismiss" ? closedBy === "any" : reason !== "escape" || closedBy !== "none", requestClose });
|
|
62
74
|
} else {
|
|
63
75
|
if (dialog.open) dialog.close();
|
|
64
76
|
closeOverlay(document, overlayId);
|
|
65
77
|
}
|
|
78
|
+
if (lastOpen !== undefined && lastOpen !== open) {
|
|
79
|
+
host.dispatch(open ? "open" : "close", { open, reason: "programmatic", trigger: "programmatic" });
|
|
80
|
+
}
|
|
81
|
+
lastOpen = open;
|
|
66
82
|
};
|
|
67
|
-
const onClose = () => {
|
|
68
|
-
if (
|
|
83
|
+
const onClose = (event) => {
|
|
84
|
+
if (event instanceof CustomEvent) return;
|
|
85
|
+
// Consumers receive one Looma close event with a reason, not an additional native close event.
|
|
86
|
+
event.stopImmediatePropagation();
|
|
87
|
+
if (dialog.open || !host.state.internalOpen) return;
|
|
69
88
|
host.state.internalOpen = false;
|
|
89
|
+
lastOpen = false;
|
|
70
90
|
closeOverlay(document, overlayId);
|
|
71
91
|
host.dispatch("close", { open: false, reason: "programmatic", trigger: "programmatic" });
|
|
72
92
|
};
|
|
73
93
|
const onKeydown = (event) => {
|
|
74
94
|
if (event.key === "Escape") {
|
|
75
95
|
event.preventDefault();
|
|
96
|
+
event.stopPropagation();
|
|
76
97
|
requestTopOverlayClose(document, "escape", "keyboard");
|
|
77
98
|
}
|
|
78
99
|
};
|
|
@@ -85,7 +106,7 @@ export default function controller(host) {
|
|
|
85
106
|
if (label !== host.state.accessibleLabel) host.state.accessibleLabel = label;
|
|
86
107
|
});
|
|
87
108
|
observer.observe(element, { childList: true, subtree: true, characterData: true });
|
|
88
|
-
dialog?.addEventListener("close", onClose);
|
|
109
|
+
dialog?.addEventListener("close", onClose, true);
|
|
89
110
|
dialog?.addEventListener("cancel", onCancel);
|
|
90
111
|
closeButton?.addEventListener("click", onCloseClick);
|
|
91
112
|
element.addEventListener("keydown", onKeydown);
|
|
@@ -95,7 +116,7 @@ export default function controller(host) {
|
|
|
95
116
|
stop();
|
|
96
117
|
ids.stop();
|
|
97
118
|
observer.disconnect();
|
|
98
|
-
dialog?.removeEventListener("close", onClose);
|
|
119
|
+
dialog?.removeEventListener("close", onClose, true);
|
|
99
120
|
dialog?.removeEventListener("cancel", onCancel);
|
|
100
121
|
closeButton?.removeEventListener("click", onCloseClick);
|
|
101
122
|
trigger?.removeEventListener("click", onTriggerClick);
|
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
>Whether the disclosure is open initially or when set externally. The trigger toggles it from there, reporting each change as an open or close event.</prop>
|
|
8
8
|
<prop name="disabled" type="boolean" default="false"
|
|
9
9
|
>Disables the trigger and mutes its text, so the disclosure keeps its current state and the user cannot toggle it.</prop>
|
|
10
|
+
<prop name="name" type="string" default=""
|
|
11
|
+
>Disclosures with the same nonempty name form an exclusive group in one document: opening one closes the others. An empty name allows multiple open
|
|
12
|
+
items.</prop>
|
|
13
|
+
<prop name="headingLevel" type="integer" default="0"
|
|
14
|
+
>Adds heading semantics around the trigger at level 2 through 6. Zero leaves it a plain disclosure; choose a level that matches the page outline.</prop>
|
|
10
15
|
<state name="internalOpen" :value="false"></state>
|
|
11
16
|
<state name="contentId" :value="''"></state>
|
|
17
|
+
<state name="headingRole" :value="null"></state>
|
|
18
|
+
<state name="headingAriaLevel" :value="null"></state>
|
|
12
19
|
<event
|
|
13
20
|
name="open"
|
|
14
21
|
type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
|
|
@@ -19,11 +26,13 @@
|
|
|
19
26
|
></event>
|
|
20
27
|
</defs>
|
|
21
28
|
<div>
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
<div class="heading" :role="headingRole" :aria-level="headingAriaLevel">
|
|
30
|
+
<button class="trigger" type="button" $ref="trigger" :aria-controls="contentId" :aria-expanded="internalOpen" :disabled="disabled">
|
|
31
|
+
<span class="summary"><slot name="summary"><template $value="summary"></template></slot></span>
|
|
32
|
+
<span class="chevron" aria-hidden="true"></span>
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="panel" $ref="panel" :id="contentId"><div class="panel-inner"><slot></slot></div></div>
|
|
27
36
|
</div>
|
|
28
37
|
<style>
|
|
29
38
|
/* This component's hooks style the element they are set on, and never a nested one. */
|