@threadlabs/looma 0.14.8 → 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 +46 -10
- package/components/ui-combobox/ui-combobox.js +5 -5
- 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 +3 -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 +11 -3
- package/vue/UiCombobox.vue +60 -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 +29 -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
|
@@ -8,10 +8,12 @@ export const icons = {
|
|
|
8
8
|
"book-user": [["path",{"d":"M15 13a3 3 0 1 0-6 0"}],["path",{"d":"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["circle",{"cx":"12","cy":"8","r":"2"}]],
|
|
9
9
|
"braces": [["path",{"d":"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"}],["path",{"d":"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"}]],
|
|
10
10
|
"calculator": [["rect",{"width":"16","height":"20","x":"4","y":"2","rx":"2"}],["line",{"x1":"8","x2":"16","y1":"6","y2":"6"}],["line",{"x1":"16","x2":"16","y1":"14","y2":"18"}],["path",{"d":"M16 10h.01"}],["path",{"d":"M12 10h.01"}],["path",{"d":"M8 10h.01"}],["path",{"d":"M12 14h.01"}],["path",{"d":"M8 14h.01"}],["path",{"d":"M12 18h.01"}],["path",{"d":"M8 18h.01"}]],
|
|
11
|
+
"check": [["path",{"d":"M20 6 9 17l-5-5"}]],
|
|
11
12
|
"chevron-down": [["path",{"d":"m6 9 6 6 6-6"}]],
|
|
12
13
|
"chevron-left": [["path",{"d":"m15 18-6-6 6-6"}]],
|
|
13
14
|
"chevron-right": [["path",{"d":"m9 18 6-6-6-6"}]],
|
|
14
15
|
"circle-check": [["circle",{"cx":"12","cy":"12","r":"10"}],["path",{"d":"m9 12 2 2 4-4"}]],
|
|
16
|
+
"circle-dot": [["circle",{"cx":"12","cy":"12","r":"10"}],["circle",{"cx":"12","cy":"12","r":"1"}]],
|
|
15
17
|
"circle-x": [["circle",{"cx":"12","cy":"12","r":"10"}],["path",{"d":"m15 9-6 6"}],["path",{"d":"m9 9 6 6"}]],
|
|
16
18
|
"code-xml": [["path",{"d":"m18 16 4-4-4-4"}],["path",{"d":"m6 8-4 4 4 4"}],["path",{"d":"m14.5 4-5 16"}]],
|
|
17
19
|
"columns": [["rect",{"width":"18","height":"18","x":"3","y":"3","rx":"2"}],["path",{"d":"M9 3v18"}],["path",{"d":"M15 3v18"}]],
|
|
@@ -31,6 +33,7 @@ export const icons = {
|
|
|
31
33
|
"info": [["circle",{"cx":"12","cy":"12","r":"10"}],["path",{"d":"M12 16v-4"}],["path",{"d":"M12 8h.01"}]],
|
|
32
34
|
"italic": [["line",{"x1":"19","x2":"10","y1":"4","y2":"4"}],["line",{"x1":"14","x2":"5","y1":"20","y2":"20"}],["line",{"x1":"15","x2":"9","y1":"4","y2":"20"}]],
|
|
33
35
|
"layout-dashboard": [["rect",{"width":"7","height":"9","x":"3","y":"3","rx":"1"}],["rect",{"width":"7","height":"5","x":"14","y":"3","rx":"1"}],["rect",{"width":"7","height":"9","x":"14","y":"12","rx":"1"}],["rect",{"width":"7","height":"5","x":"3","y":"16","rx":"1"}]],
|
|
36
|
+
"link": [["path",{"d":"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"}],["path",{"d":"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"}]],
|
|
34
37
|
"list": [["path",{"d":"M3 5h.01"}],["path",{"d":"M3 12h.01"}],["path",{"d":"M3 19h.01"}],["path",{"d":"M8 5h13"}],["path",{"d":"M8 12h13"}],["path",{"d":"M8 19h13"}]],
|
|
35
38
|
"list-ordered": [["path",{"d":"M11 5h10"}],["path",{"d":"M11 12h10"}],["path",{"d":"M11 19h10"}],["path",{"d":"M4 4h1v5"}],["path",{"d":"M4 9h2"}],["path",{"d":"M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02"}]],
|
|
36
39
|
"list-todo": [["path",{"d":"M13 5h8"}],["path",{"d":"M13 12h8"}],["path",{"d":"M13 19h8"}],["path",{"d":"m3 17 2 2 4-4"}],["rect",{"x":"3","y":"4","width":"6","height":"6","rx":"1"}]],
|
|
@@ -58,5 +61,7 @@ export const icons = {
|
|
|
58
61
|
"truck": [["path",{"d":"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"}],["path",{"d":"M15 18H9"}],["path",{"d":"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"}],["circle",{"cx":"17","cy":"18","r":"2"}],["circle",{"cx":"7","cy":"18","r":"2"}]],
|
|
59
62
|
"underline": [["path",{"d":"M6 4v6a6 6 0 0 0 12 0V4"}],["line",{"x1":"4","x2":"20","y1":"20","y2":"20"}]],
|
|
60
63
|
"undo": [["path",{"d":"M9 14 4 9l5-5"}],["path",{"d":"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"}]],
|
|
64
|
+
"user-round": [["circle",{"cx":"12","cy":"8","r":"5"}],["path",{"d":"M20 21a8 8 0 0 0-16 0"}]],
|
|
61
65
|
"users": [["path",{"d":"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["path",{"d":"M16 3.128a4 4 0 0 1 0 7.744"}],["path",{"d":"M22 21v-2a4 4 0 0 0-3-3.87"}],["circle",{"cx":"9","cy":"7","r":"4"}]],
|
|
66
|
+
"x": [["path",{"d":"M18 6 6 18"}],["path",{"d":"m6 6 12 12"}]],
|
|
62
67
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const itemSelector = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]';
|
|
2
|
+
const searches = new WeakMap();
|
|
3
|
+
|
|
4
|
+
export function menuItemFrom(target) {
|
|
5
|
+
return target?.closest?.(itemSelector) ?? null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function menuItems(menu) {
|
|
9
|
+
return Array.from(menu.querySelectorAll(itemSelector)).filter((item) =>
|
|
10
|
+
item.getAttribute("aria-disabled") !== "true" && !item.hasAttribute("disabled") && !item.closest("[hidden]"));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function label(item) {
|
|
14
|
+
return (item.querySelector(".label")?.textContent ?? item.textContent ?? "").trim().toLocaleLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Move within one open menu, including named groups; only enabled items receive focus. */
|
|
18
|
+
export function navigateMenu(event, menu) {
|
|
19
|
+
const items = menuItems(menu);
|
|
20
|
+
if (!items.length) return false;
|
|
21
|
+
const current = menuItemFrom(event.target);
|
|
22
|
+
const index = items.indexOf(current);
|
|
23
|
+
let next = -1;
|
|
24
|
+
switch (event.key) {
|
|
25
|
+
case "ArrowDown": next = index < 0 ? 0 : (index + 1) % items.length; break;
|
|
26
|
+
case "ArrowUp": next = index < 0 ? items.length - 1 : (index - 1 + items.length) % items.length; break;
|
|
27
|
+
case "Home": next = 0; break;
|
|
28
|
+
case "End": next = items.length - 1; break;
|
|
29
|
+
default: {
|
|
30
|
+
if (event.key.length !== 1 || event.key === " " || event.altKey || event.ctrlKey || event.metaKey) return false;
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const previous = searches.get(menu);
|
|
33
|
+
const query = `${previous && now - previous.time < 700 ? previous.query : ""}${event.key.toLocaleLowerCase()}`;
|
|
34
|
+
searches.set(menu, { query, time: now });
|
|
35
|
+
const find = (prefix) => {
|
|
36
|
+
for (let offset = 1; offset <= items.length; offset += 1) {
|
|
37
|
+
const candidate = (index + offset + items.length) % items.length;
|
|
38
|
+
if (label(items[candidate]).startsWith(prefix)) return candidate;
|
|
39
|
+
}
|
|
40
|
+
return -1;
|
|
41
|
+
};
|
|
42
|
+
next = find(query);
|
|
43
|
+
if (next < 0 && query.length > 1) next = find(event.key.toLocaleLowerCase());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
if (next >= 0) items[next].focus();
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
const stacks = new WeakMap();
|
|
2
|
-
let anchorSequence = 0;
|
|
3
|
-
const anchorBindings = new WeakMap();
|
|
4
2
|
|
|
5
3
|
function stateFor(document) {
|
|
6
4
|
let state = stacks.get(document);
|
|
@@ -18,7 +16,7 @@ function syncScrollLock(document, state) {
|
|
|
18
16
|
|
|
19
17
|
function requestClose(document, reason, trigger) {
|
|
20
18
|
const record = stateFor(document).records.at(-1);
|
|
21
|
-
if (!record || (record.dismissible === false && (reason === "escape" || reason === "light-dismiss"))) return false;
|
|
19
|
+
if (!record || (record.dismissible === false && (reason === "escape" || reason === "light-dismiss")) || record.canClose?.(reason) === false) return false;
|
|
22
20
|
record.requestClose(reason, trigger);
|
|
23
21
|
return true;
|
|
24
22
|
}
|
|
@@ -102,25 +100,6 @@ function hide(surface) {
|
|
|
102
100
|
surface.hidden = true;
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
function bindAnchor(anchor, name) {
|
|
106
|
-
const binding = anchorBindings.get(anchor) ?? { original: anchor.style.getPropertyValue("anchor-name"), names: new Set() };
|
|
107
|
-
binding.names.add(name);
|
|
108
|
-
anchorBindings.set(anchor, binding);
|
|
109
|
-
anchor.style.setProperty("anchor-name", [binding.original, ...binding.names].filter(Boolean).join(", "));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function unbindAnchor(anchor, name) {
|
|
113
|
-
const binding = anchorBindings.get(anchor);
|
|
114
|
-
if (!binding) return;
|
|
115
|
-
binding.names.delete(name);
|
|
116
|
-
if (binding.names.size) anchor.style.setProperty("anchor-name", [binding.original, ...binding.names].filter(Boolean).join(", "));
|
|
117
|
-
else {
|
|
118
|
-
if (binding.original) anchor.style.setProperty("anchor-name", binding.original);
|
|
119
|
-
else anchor.style.removeProperty("anchor-name");
|
|
120
|
-
anchorBindings.delete(anchor);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
103
|
function viewport(owner) {
|
|
125
104
|
const visual = owner.visualViewport;
|
|
126
105
|
const left = visual?.offsetLeft ?? 0;
|
|
@@ -151,13 +130,33 @@ function layoutRect(surface) {
|
|
|
151
130
|
|
|
152
131
|
function fallbackPosition(surface, anchor, placement, gap, viewportGap, surfaceRect = layoutRect(surface)) {
|
|
153
132
|
const bounds = viewport(surface.ownerDocument.defaultView);
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
133
|
+
const [preferred, align = "center"] = placement.split("-");
|
|
134
|
+
const side = ["top", "bottom", "left", "right"].includes(preferred) ? preferred : "bottom";
|
|
135
|
+
const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" }[side];
|
|
136
|
+
const available = {
|
|
137
|
+
top: anchor.top - bounds.top - viewportGap,
|
|
138
|
+
bottom: bounds.bottom - anchor.bottom - viewportGap,
|
|
139
|
+
left: anchor.left - bounds.left - viewportGap,
|
|
140
|
+
right: bounds.right - anchor.right - viewportGap,
|
|
141
|
+
};
|
|
142
|
+
const needed = (side === "top" || side === "bottom" ? surfaceRect.height : surfaceRect.width) + gap;
|
|
143
|
+
const actual = available[side] < needed && available[opposite] > available[side] ? opposite : side;
|
|
144
|
+
surface.dataset.uiActualPlacement = actual;
|
|
145
|
+
surface.dataset.uiActualAlign = align;
|
|
146
|
+
const rtl = getComputedStyle(surface).direction === "rtl";
|
|
147
|
+
let left;
|
|
148
|
+
let top;
|
|
149
|
+
if (actual === "top" || actual === "bottom") {
|
|
150
|
+
top = actual === "top" ? anchor.top - surfaceRect.height - gap : anchor.bottom + gap;
|
|
151
|
+
left = align === "start"
|
|
152
|
+
? (rtl ? anchor.right - surfaceRect.width : anchor.left)
|
|
153
|
+
: align === "end"
|
|
154
|
+
? (rtl ? anchor.left : anchor.right - surfaceRect.width)
|
|
155
|
+
: (anchor.left + anchor.right - surfaceRect.width) / 2;
|
|
156
|
+
} else {
|
|
157
|
+
left = actual === "left" ? anchor.left - surfaceRect.width - gap : anchor.right + gap;
|
|
158
|
+
top = align === "start" ? anchor.top : align === "end" ? anchor.bottom - surfaceRect.height : (anchor.top + anchor.bottom - surfaceRect.height) / 2;
|
|
159
|
+
}
|
|
161
160
|
const shift = clamp({ left, top, right: left + surfaceRect.width, bottom: top + surfaceRect.height, width: surfaceRect.width, height: surfaceRect.height }, bounds, viewportGap);
|
|
162
161
|
surface.style.left = `${Math.round(left + shift.x)}px`;
|
|
163
162
|
surface.style.top = `${Math.round(top + shift.y)}px`;
|
|
@@ -168,10 +167,8 @@ function fallbackPosition(surface, anchor, placement, gap, viewportGap, surfaceR
|
|
|
168
167
|
export function createAnchoredSurface(surface, options = {}) {
|
|
169
168
|
const owner = surface.ownerDocument.defaultView;
|
|
170
169
|
let placement = options.placement ?? "bottom-start";
|
|
171
|
-
const gap = Math.max(0, options.gap ?? 4);
|
|
170
|
+
const gap = () => Math.max(0, Number(typeof options.gap === "function" ? options.gap() : options.gap ?? 4) || 0);
|
|
172
171
|
const viewportGap = Math.max(0, options.viewportGap ?? 8);
|
|
173
|
-
const nativeAnchor = owner.CSS?.supports?.("anchor-name: --ui-anchor-test") && owner.CSS.supports("position-anchor: --ui-anchor-test") && owner.CSS.supports("top: anchor(bottom)");
|
|
174
|
-
const anchorName = `--ui-anchor-${++anchorSequence}`;
|
|
175
172
|
let anchor = options.anchor ?? null;
|
|
176
173
|
let point = null;
|
|
177
174
|
let open = false;
|
|
@@ -179,40 +176,17 @@ export function createAnchoredSurface(surface, options = {}) {
|
|
|
179
176
|
let abort = null;
|
|
180
177
|
let sizeObserver = null;
|
|
181
178
|
surface.setAttribute("popover", "manual");
|
|
182
|
-
surface.dataset.uiPositioning = nativeAnchor ? "anchor" : "fallback";
|
|
183
179
|
surface.style.position = "fixed";
|
|
184
180
|
surface.style.margin = "0";
|
|
185
|
-
const bind = (next) => {
|
|
186
|
-
if (anchor) unbindAnchor(anchor, anchorName);
|
|
187
|
-
anchor = next;
|
|
188
|
-
if (anchor) {
|
|
189
|
-
bindAnchor(anchor, anchorName);
|
|
190
|
-
surface.style.setProperty("position-anchor", anchorName);
|
|
191
|
-
} else surface.style.removeProperty("position-anchor");
|
|
192
|
-
};
|
|
193
181
|
const position = () => {
|
|
194
182
|
frame = null;
|
|
195
183
|
if (!open) return;
|
|
196
184
|
if (point) {
|
|
197
|
-
fallbackPosition(surface, { left: point.x, right: point.x, top: point.y, bottom: point.y, width: 0, height: 0 }, "bottom-start", gap, viewportGap);
|
|
185
|
+
fallbackPosition(surface, { left: point.x, right: point.x, top: point.y, bottom: point.y, width: 0, height: 0 }, "bottom-start", gap(), viewportGap);
|
|
198
186
|
return;
|
|
199
187
|
}
|
|
200
188
|
if (!anchor) return;
|
|
201
|
-
|
|
202
|
-
if (nativeAnchor) {
|
|
203
|
-
const blockEnd = placement.startsWith("bottom");
|
|
204
|
-
const inlineEnd = placement.endsWith("end");
|
|
205
|
-
surface.style.top = blockEnd ? `calc(anchor(bottom) + ${gap}px)` : "auto";
|
|
206
|
-
surface.style.bottom = blockEnd ? "auto" : `calc(anchor(top) + ${gap}px)`;
|
|
207
|
-
surface.style.left = inlineEnd ? "auto" : "anchor(left)";
|
|
208
|
-
surface.style.right = inlineEnd ? "anchor(right)" : "auto";
|
|
209
|
-
surface.style.setProperty("position-try-fallbacks", "flip-block, flip-inline");
|
|
210
|
-
rect = layoutRect(surface);
|
|
211
|
-
if (!rect.width || !rect.height) return;
|
|
212
|
-
const shift = clamp(rect, viewport(owner), viewportGap);
|
|
213
|
-
if (!shift.x && !shift.y) return;
|
|
214
|
-
}
|
|
215
|
-
fallbackPosition(surface, anchor.getBoundingClientRect(), placement, gap, viewportGap, rect);
|
|
189
|
+
fallbackPosition(surface, anchor.getBoundingClientRect(), placement, gap(), viewportGap);
|
|
216
190
|
};
|
|
217
191
|
const schedule = () => {
|
|
218
192
|
if (open && frame === null) frame = owner.requestAnimationFrame(position);
|
|
@@ -238,15 +212,14 @@ export function createAnchoredSurface(surface, options = {}) {
|
|
|
238
212
|
sizeObserver.observe(surface);
|
|
239
213
|
};
|
|
240
214
|
const stopSize = () => { sizeObserver?.disconnect(); sizeObserver = null; };
|
|
241
|
-
bind(anchor);
|
|
242
215
|
return {
|
|
243
|
-
setAnchor(next) { point = null;
|
|
216
|
+
setAnchor(next) { point = null; anchor = next; syncListeners(); schedule(); },
|
|
244
217
|
setPlacement(next) { placement = next || "bottom-start"; schedule(); },
|
|
245
218
|
show() { point = null; open = true; syncListeners(); show(surface); position(); schedule(); observeSize(); },
|
|
246
219
|
showAtPoint(next) { point = next; open = true; syncListeners(); show(surface); position(); schedule(); observeSize(); },
|
|
247
220
|
hide() { open = false; point = null; syncListeners(); stopSize(); if (frame !== null) owner.cancelAnimationFrame(frame); frame = null; hide(surface); },
|
|
248
221
|
refresh: schedule,
|
|
249
|
-
destroy() { open = false; abort?.abort(); abort = null; stopSize(); if (frame !== null) owner.cancelAnimationFrame(frame); frame = null; hide(surface);
|
|
222
|
+
destroy() { open = false; abort?.abort(); abort = null; stopSize(); if (frame !== null) owner.cancelAnimationFrame(frame); frame = null; hide(surface); anchor = null; },
|
|
250
223
|
};
|
|
251
224
|
}
|
|
252
225
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<link rel="component" href="../ui-icon/ui-icon.html">
|
|
1
2
|
<template component="ui-avatar" controller="./ui-avatar.js">
|
|
2
3
|
<defs>
|
|
3
4
|
<prop name="src" type="string"
|
|
@@ -14,17 +15,23 @@
|
|
|
14
15
|
circle clips anything wider.</prop>
|
|
15
16
|
<prop name="size" type="sm | md" default="md"
|
|
16
17
|
>md is 2.5rem, for a person on their own (a profile, a comment); sm is 1.75rem with smaller initials, for a row of people in a toolbar or header.</prop>
|
|
18
|
+
<prop name="shape" type="circle | square" default="circle">Circle by default; square uses the theme's medium radius.</prop>
|
|
19
|
+
<prop name="decorative" type="boolean" default="false">Hides the avatar from assistive technology when adjacent text already identifies the person.</prop>
|
|
17
20
|
<prop name="active" type="boolean" default="false"
|
|
18
21
|
>Marks the person as active now, such as editing the same page: a ring in the success colour around the avatar. The ring is not announced, so say what it
|
|
19
22
|
means in alt ("Ada Lovelace, editing").</prop>
|
|
20
23
|
<state name="hasImage" :value="false"></state>
|
|
21
24
|
<state name="hasAuthoredImage" :value="false"></state>
|
|
22
25
|
<state name="initials" :value="'?'"></state>
|
|
26
|
+
<state name="showGlyph" :value="false"></state>
|
|
23
27
|
</defs>
|
|
24
28
|
<span>
|
|
25
29
|
<slot></slot>
|
|
26
30
|
<img class="image" $ref="image" :src="src" alt="" aria-hidden="true" :hidden="hasAuthoredImage or not hasImage">
|
|
27
|
-
<span class="fallback" :aria-hidden="hasImage" :hidden="hasImage"
|
|
31
|
+
<span class="fallback" :aria-hidden="hasImage" :hidden="hasImage">
|
|
32
|
+
<span :hidden="showGlyph" $value="initials"></span>
|
|
33
|
+
<span :hidden="not showGlyph"><ui-icon name="user-round"></ui-icon></span>
|
|
34
|
+
</span>
|
|
28
35
|
</span>
|
|
29
36
|
<style>
|
|
30
37
|
/* This component's hooks style the element they are set on, and never a nested one. */
|
|
@@ -32,6 +39,10 @@
|
|
|
32
39
|
syntax: "*";
|
|
33
40
|
inherits: false;
|
|
34
41
|
}
|
|
42
|
+
@property --ui-avatar-radius {
|
|
43
|
+
syntax: "*";
|
|
44
|
+
inherits: false;
|
|
45
|
+
}
|
|
35
46
|
@property --ui-avatar-surface {
|
|
36
47
|
syntax: "*";
|
|
37
48
|
inherits: false;
|
|
@@ -46,12 +57,13 @@
|
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
:host {
|
|
60
|
+
--_ui-default-icon-size: 1.25rem;
|
|
49
61
|
display: inline-grid;
|
|
50
62
|
place-items: center;
|
|
51
63
|
overflow: hidden;
|
|
52
64
|
inline-size: 2.5rem;
|
|
53
65
|
block-size: 2.5rem;
|
|
54
|
-
border-radius:
|
|
66
|
+
border-radius: var(--ui-avatar-radius, var(--ui-radius-round));
|
|
55
67
|
border: 1px solid var(--ui-avatar-border, var(--ui-border-strong));
|
|
56
68
|
background: var(--ui-avatar-surface, var(--ui-accent-soft));
|
|
57
69
|
color: var(--ui-avatar-text, var(--ui-accent-subtle-text, var(--ui-accent-solid)));
|
|
@@ -62,11 +74,16 @@
|
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
:host-state([size="sm"]) {
|
|
77
|
+
--_ui-default-icon-size: 1rem;
|
|
65
78
|
inline-size: 1.75rem;
|
|
66
79
|
block-size: 1.75rem;
|
|
67
80
|
font-size: var(--ui-font-size-xs);
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
:host-state([shape="square"]) {
|
|
84
|
+
border-radius: var(--ui-avatar-radius, var(--ui-radius-md));
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
/* An outline, not a dot: the avatar clips its content to the circle, and an outline is drawn
|
|
71
88
|
outside the box, so the ring survives the clip and the overlap in an Avatar Group. */
|
|
72
89
|
:host-state([active]) {
|
|
@@ -29,11 +29,17 @@ export default function controller(host) {
|
|
|
29
29
|
authored.hidden = !host.state.hasImage;
|
|
30
30
|
element.removeAttribute("role");
|
|
31
31
|
element.removeAttribute("aria-label");
|
|
32
|
-
} else {
|
|
32
|
+
} else if (!host.state.decorative) {
|
|
33
33
|
element.setAttribute("role", "img");
|
|
34
34
|
element.setAttribute("aria-label", host.state.alt || host.state.name || "Avatar");
|
|
35
35
|
}
|
|
36
|
+
if (host.state.decorative) {
|
|
37
|
+
element.setAttribute("aria-hidden", "true");
|
|
38
|
+
element.removeAttribute("role");
|
|
39
|
+
element.removeAttribute("aria-label");
|
|
40
|
+
} else element.removeAttribute("aria-hidden");
|
|
36
41
|
host.state.initials = host.state.fallback || toInitials(host.state.name || host.state.alt);
|
|
42
|
+
host.state.showGlyph = !String(host.state.name || "").trim() && !String(host.state.alt || "").trim() && !String(host.state.fallback || "").trim();
|
|
37
43
|
};
|
|
38
44
|
const observer = new MutationObserver(apply);
|
|
39
45
|
observer.observe(element, { childList: true });
|
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
<prop name="max" type="number" default="5"
|
|
4
4
|
>How many avatars to show. The rest are hidden and counted in a +N badge after the last visible one. Fractions round down, and 0 hides every avatar behind
|
|
5
5
|
the badge.</prop>
|
|
6
|
+
<prop name="total" type="integer"
|
|
7
|
+
>The true number of people when only some avatars are authored. The overflow badge counts the people beyond the visible avatars; it never counts fewer than
|
|
8
|
+
the authored avatars.</prop>
|
|
9
|
+
<prop name="overflowLabel" type="string" default="{count} more"
|
|
10
|
+
>Accessible label for the overflow badge. Replace {count} with the number of people beyond the visible avatars.</prop>
|
|
6
11
|
<prop name="label" type="string" default="People"
|
|
7
12
|
>The group's accessible name, announced for the set of avatars as a whole. Name who they are rather than repeating the default.</prop>
|
|
8
13
|
<prop name="size" type="sm | md" default="md"
|
|
9
14
|
>The size of the avatars inside, matched by the +N badge and the overlap. Give each Avatar the same size.</prop>
|
|
10
15
|
<state name="overflowCount" :value="0"></state>
|
|
16
|
+
<state name="overflowAnnouncement" :value="''"></state>
|
|
11
17
|
</defs>
|
|
12
18
|
<div role="group" :aria-label="label">
|
|
13
19
|
<slot></slot>
|
|
14
|
-
<span class="overflow" $ref="overflow" role="img" :hidden="not overflowCount" :aria-label="
|
|
20
|
+
<span class="overflow" $ref="overflow" role="img" :hidden="not overflowCount" :aria-label="overflowAnnouncement"><template
|
|
15
21
|
$value="format('+%s', overflowCount)"
|
|
16
22
|
></template></span>
|
|
17
23
|
</div>
|
|
@@ -47,7 +53,6 @@
|
|
|
47
53
|
:slotted(*) {
|
|
48
54
|
margin-inline-start: -0.5rem;
|
|
49
55
|
flex-shrink: 0;
|
|
50
|
-
border-radius: 999px;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
:slotted(*:first-child) {
|
|
@@ -61,7 +66,7 @@
|
|
|
61
66
|
justify-content: center;
|
|
62
67
|
inline-size: 2.5rem;
|
|
63
68
|
block-size: 2.5rem;
|
|
64
|
-
border-radius:
|
|
69
|
+
border-radius: var(--ui-radius-round);
|
|
65
70
|
border: 1px solid var(--ui-border-strong);
|
|
66
71
|
background: var(--ui-surface-elevated);
|
|
67
72
|
color: var(--ui-text-primary);
|
|
@@ -11,13 +11,17 @@ export default function controller(host) {
|
|
|
11
11
|
avatars.forEach((avatar, index) => {
|
|
12
12
|
avatar.style.display = index >= visible ? "none" : "";
|
|
13
13
|
avatar.style.marginInlineStart = index === 0 ? "0px" : overlap;
|
|
14
|
-
avatar.style.borderRadius = "999px";
|
|
15
14
|
// Later avatars cast a crisp shadow back onto the one they overlap; the first overlaps nothing.
|
|
16
15
|
avatar.style.boxShadow = index === 0
|
|
17
16
|
? "var(--ui-avatar-group-edge-ring, var(--_edge-ring))"
|
|
18
17
|
: "var(--ui-avatar-group-overlap-shadow, var(--_overlap-shadow)), var(--ui-avatar-group-edge-ring, var(--_edge-ring))";
|
|
19
18
|
});
|
|
20
|
-
|
|
19
|
+
const authoredVisible = Math.min(avatars.length, visible);
|
|
20
|
+
const suppliedTotal = Number(host.state.total);
|
|
21
|
+
const total = host.state.total === undefined || !Number.isFinite(suppliedTotal) ? avatars.length : Math.max(avatars.length, suppliedTotal);
|
|
22
|
+
const count = Math.max(0, total - authoredVisible);
|
|
23
|
+
host.state.overflowCount = count;
|
|
24
|
+
host.state.overflowAnnouncement = String(host.state.overflowLabel || "{count} more").replaceAll("{count}", String(count));
|
|
21
25
|
};
|
|
22
26
|
const observer = new MutationObserver(update);
|
|
23
27
|
observer.observe(element, { childList: true });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template component="ui-badge">
|
|
2
2
|
<defs>
|
|
3
|
-
<prop name="variant" type="solid | subtle" default="subtle"
|
|
4
|
-
>Emphasis. subtle sets
|
|
5
|
-
|
|
3
|
+
<prop name="variant" type="solid | subtle | outline" default="subtle"
|
|
4
|
+
>Emphasis. subtle sets toned text over a soft wash; solid fills with the tone and uses contrasting text; outline keeps the surface with a toned edge.
|
|
5
|
+
Neutral looks the same in solid and subtle.</prop>
|
|
6
6
|
<prop name="tone" type="neutral | accent | info | success | warning | danger" default="neutral"
|
|
7
7
|
>Which colour the badge speaks in. neutral is a quiet grey for plain labels; accent draws the eye in the accent colour; info, success, warning, and danger
|
|
8
8
|
mark a status, so match the tone to what the badge says.</prop>
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
--_badge-text: var(--ui-text-secondary);
|
|
59
59
|
/* Every tone's edge is its fill, so it is unseen until forced colors draw it. */
|
|
60
60
|
--_badge-border: var(--ui-surface-subtle);
|
|
61
|
+
--_badge-outline-surface: var(--ui-badge-surface, var(--_badge-surface));
|
|
61
62
|
|
|
62
63
|
display: inline-flex;
|
|
63
64
|
align-items: center;
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
padding: var(--ui-badge-padding-block, var(--ui-space-0-5)) var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
67
68
|
border: 1px solid var(--ui-badge-border, var(--_badge-border));
|
|
68
69
|
border-radius: var(--ui-radius-round);
|
|
69
|
-
background: var(--
|
|
70
|
+
background: var(--_badge-outline-surface);
|
|
70
71
|
color: var(--ui-badge-text, var(--_badge-text));
|
|
71
72
|
font-family: var(--ui-font-family-sans);
|
|
72
73
|
font-size: var(--ui-badge-font-size, var(--ui-font-size-sm));
|
|
@@ -149,6 +150,37 @@
|
|
|
149
150
|
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
:host-state([variant="outline"]) {
|
|
154
|
+
--_badge-surface: var(--ui-surface-default);
|
|
155
|
+
--_badge-border: var(--ui-border-strong);
|
|
156
|
+
--_badge-text: var(--ui-text-secondary);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
:host-state([variant="outline"][tone="accent"]) {
|
|
160
|
+
--_badge-border: var(--ui-accent-solid);
|
|
161
|
+
--_badge-text: var(--ui-accent-subtle-text, var(--ui-accent-solid));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
:host-state([variant="outline"][tone="danger"]) {
|
|
165
|
+
--_badge-border: var(--ui-danger-solid);
|
|
166
|
+
--_badge-text: var(--ui-danger-subtle-text, var(--ui-danger-solid));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
:host-state([variant="outline"][tone="success"]) {
|
|
170
|
+
--_badge-border: var(--ui-success-solid);
|
|
171
|
+
--_badge-text: var(--ui-success-subtle-text, var(--ui-success-solid));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:host-state([variant="outline"][tone="warning"]) {
|
|
175
|
+
--_badge-border: var(--ui-warning-solid);
|
|
176
|
+
--_badge-text: var(--ui-warning-subtle-text, var(--ui-warning-solid));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
:host-state([variant="outline"][tone="info"]) {
|
|
180
|
+
--_badge-border: var(--ui-info-solid);
|
|
181
|
+
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
182
|
+
}
|
|
183
|
+
|
|
152
184
|
/* A tag is flat at the start and points at the end. The point is cut from the box, so it has no
|
|
153
185
|
outline to follow: the edge is the fill. The end padding grows by the point's depth so the
|
|
154
186
|
label never runs into it. */
|
|
@@ -167,6 +199,40 @@
|
|
|
167
199
|
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
168
200
|
}
|
|
169
201
|
|
|
202
|
+
/* The tag clips its point, including the native border. Draw the outer shape in the border
|
|
203
|
+
colour and an inset inner shape in the surface colour to keep a visible diagonal edge. */
|
|
204
|
+
:host-state([variant="outline"][shape="tag"]) {
|
|
205
|
+
position: relative;
|
|
206
|
+
isolation: isolate;
|
|
207
|
+
background: var(--ui-badge-border, var(--_badge-border));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
:host-state([variant="outline"][shape="tag"])::before {
|
|
211
|
+
content: "";
|
|
212
|
+
position: absolute;
|
|
213
|
+
inset: 1px;
|
|
214
|
+
z-index: -1;
|
|
215
|
+
background: var(--_badge-outline-surface);
|
|
216
|
+
clip-path: polygon(0 0, calc(100% - var(--_badge-point)) 0, 100% 50%, calc(100% - var(--_badge-point)) 100%, 0 100%);
|
|
217
|
+
pointer-events: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
:host-state([variant="outline"][shape="tag"]):dir(rtl)::before {
|
|
221
|
+
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@media (forced-colors: active) {
|
|
225
|
+
:host-state([variant="outline"][shape="tag"]) {
|
|
226
|
+
forced-color-adjust: none;
|
|
227
|
+
background: CanvasText;
|
|
228
|
+
color: CanvasText;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
:host-state([variant="outline"][shape="tag"])::before {
|
|
232
|
+
background: Canvas;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
170
236
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
171
237
|
:host[hidden] {
|
|
172
238
|
display: none;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<
|
|
1
|
+
<link rel="component" href="../ui-spinner/ui-spinner.html">
|
|
2
|
+
<template component="ui-button" controller="./ui-button.js">
|
|
2
3
|
<defs>
|
|
3
4
|
<prop name="variant" type="outline | solid | danger | ghost | link" default="outline"
|
|
4
5
|
>Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is
|
|
@@ -12,6 +13,9 @@
|
|
|
12
13
|
<prop name="disabled" type="boolean" default="false"
|
|
13
14
|
>Makes the button unavailable: it cannot be pressed, its tone fades, and it loses its raised look. A disabled link (as="a" with href) drops its href and
|
|
14
15
|
states aria-disabled="true" instead, because a link has no native disabled state.</prop>
|
|
16
|
+
<prop name="pending" type="boolean" default="false"
|
|
17
|
+
>Keeps the action focusable and its label visible while work is in progress. It shows a decorative spinner, states aria-disabled and aria-busy, and prevents
|
|
18
|
+
activation or form submission until cleared.</prop>
|
|
15
19
|
<prop name="align" type="center | start" default="center"
|
|
16
20
|
>Content alignment. start lays the content out like a list option: from the start edge, with start-aligned text.</prop>
|
|
17
21
|
<prop name="stretch" type="boolean" default="false">Fills the inline size of its container.</prop>
|
|
@@ -30,14 +34,18 @@
|
|
|
30
34
|
<template $match>
|
|
31
35
|
<a
|
|
32
36
|
$when="as = 'a'"
|
|
33
|
-
:href="{ true: null, false: href }[format('%s', disabled)]"
|
|
37
|
+
:href="{ true: null, false: href }[format('%s', disabled or pending)]"
|
|
34
38
|
:target="target"
|
|
35
39
|
:rel="rel"
|
|
36
|
-
:aria-disabled="{ true: 'true', false: null }[format('%s', disabled
|
|
40
|
+
:aria-disabled="{ true: 'true', false: null }[format('%s', disabled or pending)]"
|
|
41
|
+
:aria-busy="pending"
|
|
42
|
+
:tabindex="{ true: '0', false: null }[format('%s', pending and not disabled)]"
|
|
37
43
|
>
|
|
44
|
+
<ui-spinner $if="pending" size="sm" class="pending-spinner"></ui-spinner>
|
|
38
45
|
<slot></slot>
|
|
39
46
|
</a>
|
|
40
|
-
<button $else :type="type" :disabled="disabled">
|
|
47
|
+
<button $else :type="type" :disabled="disabled" :aria-disabled="{ true: 'true', false: null }[format('%s', pending)]" :aria-busy="pending">
|
|
48
|
+
<ui-spinner $if="pending" size="sm" class="pending-spinner"></ui-spinner>
|
|
41
49
|
<slot></slot>
|
|
42
50
|
</button>
|
|
43
51
|
</template>
|
|
@@ -148,7 +156,7 @@
|
|
|
148
156
|
border-style: solid;
|
|
149
157
|
border-width: 1px;
|
|
150
158
|
border-color: var(--ui-button-border, var(--_tone));
|
|
151
|
-
border-radius: var(--ui-button-radius, var(--ui-radius-md));
|
|
159
|
+
border-radius: var(--ui-button-radius, var(--ui-action-radius, var(--ui-radius-md)));
|
|
152
160
|
background-color: var(--ui-button-surface, oklch(from var(--_tone) l c h / 5%));
|
|
153
161
|
color: var(--ui-button-text, var(--_tone-text));
|
|
154
162
|
font: inherit;
|
|
@@ -167,6 +175,10 @@
|
|
|
167
175
|
margin: 0;
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
.pending-spinner {
|
|
179
|
+
margin-inline-end: var(--ui-space-2);
|
|
180
|
+
}
|
|
181
|
+
|
|
170
182
|
@supports (text-box-trim: trim-both) {
|
|
171
183
|
:host {
|
|
172
184
|
text-box-trim: trim-both;
|
|
@@ -307,6 +319,10 @@
|
|
|
307
319
|
box-shadow: none;
|
|
308
320
|
}
|
|
309
321
|
|
|
322
|
+
:host-state([pending]):focus-visible {
|
|
323
|
+
box-shadow: var(--ui-focus-halo);
|
|
324
|
+
}
|
|
325
|
+
|
|
310
326
|
/* Ghost has no hover to fall back on, so disabled is the one state it has to state outright:
|
|
311
327
|
it takes a surface rather than reading as plain text. The surface is a wash of the faded tone,
|
|
312
328
|
the way hover washes the live one, so it stays light in every tone. An opaque mix toward the
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Pending actions retain focus but cannot activate, navigate, submit, or notify click consumers.
|
|
2
|
+
export default function controller(host) {
|
|
3
|
+
const onClick = (event) => {
|
|
4
|
+
if (!host.state.pending) return;
|
|
5
|
+
event.preventDefault();
|
|
6
|
+
event.stopImmediatePropagation();
|
|
7
|
+
};
|
|
8
|
+
host.element.addEventListener("click", onClick, true);
|
|
9
|
+
return () => host.element.removeEventListener("click", onClick, true);
|
|
10
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template component="ui-callout">
|
|
2
2
|
<defs>
|
|
3
|
-
<prop name="tone" type="info | note | warning | success | danger" default="info"
|
|
4
|
-
>Which kind of message the callout carries, shown by its icon and colour: info (the default) for
|
|
5
|
-
|
|
3
|
+
<prop name="tone" type="info | neutral | note | warning | success | danger" default="info"
|
|
4
|
+
>Which kind of message the callout carries, shown by its icon and colour: info (the default) for information, neutral for a muted aside, warning for a
|
|
5
|
+
caution, success for a confirmation, and danger for a harmful consequence. note remains as an alias for neutral.</prop>
|
|
6
6
|
</defs>
|
|
7
7
|
<div role="note">
|
|
8
8
|
<span class="icon" aria-hidden="true">
|
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
<path d="M12 16v-4"></path>
|
|
12
12
|
<path d="M12 8h.01"></path>
|
|
13
13
|
</svg>
|
|
14
|
-
<svg
|
|
14
|
+
<svg
|
|
15
|
+
$if="tone = 'neutral' or tone = 'note'"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
stroke-width="2"
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
stroke-linejoin="round"
|
|
22
|
+
>
|
|
15
23
|
<path d="M13.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8.5L13.5 2Z"></path>
|
|
16
24
|
<path d="M13 2v7h7"></path>
|
|
17
25
|
<path d="m9.5 17.5 4-4 2 2-4 4H9.5v-2Z"></path>
|
|
@@ -125,7 +133,7 @@
|
|
|
125
133
|
min-inline-size: 0;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
:host-state([tone="note"]) {
|
|
136
|
+
:host-state([tone="neutral"]), :host-state([tone="note"]) {
|
|
129
137
|
--_callout-surface: var(--ui-surface-muted);
|
|
130
138
|
--_callout-border: var(--ui-border-strong);
|
|
131
139
|
--_callout-icon: var(--ui-text-secondary);
|