@threadlabs/looma 0.3.1 → 0.5.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/README.md +43 -12
- package/components/shared/editor.js +69 -0
- package/components/shared/input-modality.js +20 -0
- package/components/shared/overlay.js +281 -0
- package/components/shared/trigger.js +17 -0
- package/components/ui-affordance-scope/ui-affordance-scope.html +22 -0
- package/components/ui-affordance-scope/ui-affordance-scope.js +76 -0
- package/components/ui-avatar/ui-avatar.html +59 -0
- package/components/ui-avatar/ui-avatar.js +47 -0
- package/components/ui-avatar-group/ui-avatar-group.html +62 -0
- package/components/ui-avatar-group/ui-avatar-group.js +27 -0
- package/components/ui-badge/ui-badge.html +94 -0
- package/components/ui-button/ui-button.html +198 -0
- package/components/ui-callout/ui-callout.html +104 -0
- package/components/ui-checkbox/ui-checkbox.html +107 -0
- package/components/ui-checkbox/ui-checkbox.js +24 -0
- package/components/ui-cluster/ui-cluster.html +49 -0
- package/components/ui-combobox/ui-combobox.html +377 -0
- package/components/ui-combobox/ui-combobox.js +432 -0
- package/components/ui-container/ui-container.html +39 -0
- package/components/ui-context-menu/ui-context-menu.html +75 -0
- package/components/ui-context-menu/ui-context-menu.js +143 -0
- package/components/ui-dialog/ui-dialog.html +178 -0
- package/components/ui-dialog/ui-dialog.js +105 -0
- package/components/ui-disclosure/ui-disclosure.html +109 -0
- package/components/ui-disclosure/ui-disclosure.js +28 -0
- package/components/ui-editable/ui-editable.html +163 -0
- package/components/ui-editable/ui-editable.js +113 -0
- package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +97 -0
- package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.js +71 -0
- package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +157 -0
- package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +50 -0
- package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +197 -0
- package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +44 -0
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +164 -0
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +75 -0
- package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +422 -0
- package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +119 -0
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +271 -0
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +54 -0
- package/components/ui-editor-toolbar/ui-editor-toolbar.html +110 -0
- package/components/ui-floating-action-button/ui-floating-action-button.html +83 -0
- package/components/ui-form-field/ui-form-field.html +45 -0
- package/components/ui-form-field/ui-form-field.js +71 -0
- package/components/ui-grid/ui-grid.html +46 -0
- package/components/ui-icon/ui-icon.html +34 -0
- package/components/ui-icon/ui-icon.js +8 -0
- package/components/ui-icon-button/ui-icon-button.html +167 -0
- package/components/ui-icon-button/ui-icon-button.js +7 -0
- package/components/ui-input/ui-input.html +81 -0
- package/components/ui-input/ui-input.js +7 -0
- package/components/ui-menu/ui-menu.html +87 -0
- package/components/ui-menu/ui-menu.js +114 -0
- package/components/ui-menu-item/ui-menu-item.html +37 -0
- package/components/ui-popover/ui-popover.html +85 -0
- package/components/ui-popover/ui-popover.js +70 -0
- package/components/ui-radio/ui-radio.html +57 -0
- package/components/ui-radio/ui-radio.js +25 -0
- package/components/ui-radio-group/ui-radio-group.html +51 -0
- package/components/ui-radio-group/ui-radio-group.js +42 -0
- package/components/ui-reel/ui-reel.html +67 -0
- package/components/ui-search-result-row/ui-search-result-row.html +101 -0
- package/components/ui-search-shell/ui-search-shell.html +142 -0
- package/components/ui-search-shell/ui-search-shell.js +60 -0
- package/components/ui-select/ui-select.html +87 -0
- package/components/ui-select/ui-select.js +16 -0
- package/components/ui-separator/ui-separator.html +22 -0
- package/components/ui-sidebar/ui-sidebar.html +183 -0
- package/components/ui-sidebar/ui-sidebar.js +178 -0
- package/components/ui-stack/ui-stack.html +59 -0
- package/components/ui-switch/ui-switch.html +95 -0
- package/components/ui-switch/ui-switch.js +24 -0
- package/components/ui-switcher/ui-switcher.html +65 -0
- package/components/ui-tabs/ui-tabs.html +134 -0
- package/components/ui-tabs/ui-tabs.js +81 -0
- package/components/ui-textarea/ui-textarea.html +80 -0
- package/components/ui-textarea/ui-textarea.js +7 -0
- package/components/ui-toast-region/ui-toast-region.html +127 -0
- package/components/ui-toast-region/ui-toast-region.js +119 -0
- package/components/ui-tooltip/ui-tooltip.html +122 -0
- package/components/ui-tooltip/ui-tooltip.js +134 -0
- package/components/ui-top-bar/ui-top-bar.html +53 -0
- package/components/ui-tree/ui-tree.html +29 -0
- package/components/ui-tree/ui-tree.js +250 -0
- package/components/ui-tree-item/ui-tree-item.html +326 -0
- package/components/ui-tree-item/ui-tree-item.js +94 -0
- package/dist/index.d.ts +4 -414
- package/dist/index.js +55 -657
- package/editor/chunks/extensions.js +7267 -0
- package/editor/chunks/table-backgrounds.js +51 -0
- package/editor/chunks/ui.js +92 -0
- package/editor/extensions/callout.d.ts +20 -0
- package/editor/extensions/index.d.ts +12 -441
- package/editor/extensions/index.js +3 -64
- package/editor/extensions/list-behavior.d.ts +7 -0
- package/editor/extensions/mention.d.ts +35 -0
- package/editor/extensions/preset.d.ts +42 -0
- package/editor/extensions/slash-command.d.ts +80 -0
- package/editor/extensions/smart-paste.d.ts +13 -0
- package/editor/extensions/table-commands.d.ts +114 -0
- package/editor/extensions/table-formatting.d.ts +60 -0
- package/editor/icons.d.ts +55 -0
- package/editor/index.d.ts +8 -6
- package/editor/index.js +96 -71
- package/editor/insert-table-grid.d.ts +7 -0
- package/editor/mention-contract.d.ts +78 -0
- package/editor/mention-menu.d.ts +9 -0
- package/editor/slash-menu.d.ts +29 -0
- package/editor/table-backgrounds.d.ts +43 -0
- package/editor/table-context-menu.d.ts +12 -0
- package/editor/table-overlay.d.ts +79 -0
- package/editor/ui.d.ts +10 -50
- package/editor/ui.js +3 -14
- package/editor/viewport.d.ts +15 -0
- package/package.json +84 -80
- package/styles/ui-affordance-scope.css +19 -0
- package/styles/ui-avatar-group.css +56 -0
- package/styles/ui-avatar.css +47 -0
- package/styles/ui-badge.css +91 -0
- package/styles/ui-button.css +206 -0
- package/styles/ui-callout.css +76 -0
- package/styles/ui-checkbox.css +98 -0
- package/styles/ui-cluster.css +46 -0
- package/styles/ui-combobox.css +213 -0
- package/styles/ui-container.css +36 -0
- package/styles/ui-context-menu.css +68 -0
- package/styles/ui-dialog.css +160 -0
- package/styles/ui-disclosure.css +95 -0
- package/styles/ui-editable.css +139 -0
- package/styles/ui-editor-insert-table-grid.css +65 -0
- package/styles/ui-editor-mention-menu.css +121 -0
- package/styles/ui-editor-slash-menu.css +179 -0
- package/styles/ui-editor-table-context-menu.css +118 -0
- package/styles/ui-editor-table-overlay.css +318 -0
- package/styles/ui-editor-table-toolbar.css +191 -0
- package/styles/ui-editor-toolbar.css +115 -0
- package/styles/ui-floating-action-button.css +82 -0
- package/styles/ui-form-field.css +36 -0
- package/styles/ui-grid.css +43 -0
- package/styles/ui-icon-button.css +169 -0
- package/styles/ui-icon.css +23 -0
- package/styles/ui-input.css +82 -0
- package/styles/ui-menu-item.css +34 -0
- package/styles/ui-menu.css +80 -0
- package/styles/ui-popover.css +76 -0
- package/styles/ui-radio-group.css +38 -0
- package/styles/ui-radio.css +46 -0
- package/styles/ui-reel.css +63 -0
- package/styles/ui-search-result-row.css +92 -0
- package/styles/ui-search-shell.css +128 -0
- package/styles/ui-select.css +86 -0
- package/styles/ui-separator.css +16 -0
- package/styles/ui-sidebar.css +166 -0
- package/styles/ui-stack.css +55 -0
- package/styles/ui-switch.css +88 -0
- package/styles/ui-switcher.css +61 -0
- package/styles/ui-tabs.css +114 -0
- package/styles/ui-textarea.css +77 -0
- package/styles/ui-toast-region.css +103 -0
- package/styles/ui-tooltip.css +113 -0
- package/styles/ui-top-bar.css +51 -0
- package/styles/ui-tree-item.css +267 -0
- package/styles/ui-tree.css +16 -0
- package/vanilla/UiAffordanceScope.d.ts +14 -0
- package/vanilla/UiAffordanceScope.js +30 -0
- package/vanilla/UiAvatar.d.ts +17 -0
- package/vanilla/UiAvatar.js +41 -0
- package/vanilla/UiAvatarGroup.d.ts +15 -0
- package/vanilla/UiAvatarGroup.js +40 -0
- package/vanilla/UiBadge.d.ts +15 -0
- package/vanilla/UiBadge.js +30 -0
- package/vanilla/UiButton.d.ts +16 -0
- package/vanilla/UiButton.js +32 -0
- package/vanilla/UiCallout.d.ts +14 -0
- package/vanilla/UiCallout.js +124 -0
- package/vanilla/UiCheckbox.d.ts +19 -0
- package/vanilla/UiCheckbox.js +47 -0
- package/vanilla/UiCluster.d.ts +15 -0
- package/vanilla/UiCluster.js +30 -0
- package/vanilla/UiCombobox.d.ts +42 -0
- package/vanilla/UiCombobox.js +194 -0
- package/vanilla/UiContainer.d.ts +15 -0
- package/vanilla/UiContainer.js +30 -0
- package/vanilla/UiContextMenu.d.ts +18 -0
- package/vanilla/UiContextMenu.js +35 -0
- package/vanilla/UiDialog.d.ts +19 -0
- package/vanilla/UiDialog.js +53 -0
- package/vanilla/UiDisclosure.d.ts +18 -0
- package/vanilla/UiDisclosure.js +49 -0
- package/vanilla/UiEditable.d.ts +22 -0
- package/vanilla/UiEditable.js +59 -0
- package/vanilla/UiEditorInsertTableGrid.d.ts +18 -0
- package/vanilla/UiEditorInsertTableGrid.js +46 -0
- package/vanilla/UiEditorMentionMenu.d.ts +21 -0
- package/vanilla/UiEditorMentionMenu.js +63 -0
- package/vanilla/UiEditorSlashMenu.d.ts +20 -0
- package/vanilla/UiEditorSlashMenu.js +74 -0
- package/vanilla/UiEditorTableContextMenu.d.ts +17 -0
- package/vanilla/UiEditorTableContextMenu.js +69 -0
- package/vanilla/UiEditorTableOverlay.d.ts +16 -0
- package/vanilla/UiEditorTableOverlay.js +113 -0
- package/vanilla/UiEditorTableToolbar.d.ts +18 -0
- package/vanilla/UiEditorTableToolbar.js +121 -0
- package/vanilla/UiEditorToolbar.d.ts +14 -0
- package/vanilla/UiEditorToolbar.js +30 -0
- package/vanilla/UiFloatingActionButton.d.ts +16 -0
- package/vanilla/UiFloatingActionButton.js +35 -0
- package/vanilla/UiFormField.d.ts +16 -0
- package/vanilla/UiFormField.js +54 -0
- package/vanilla/UiGrid.d.ts +15 -0
- package/vanilla/UiGrid.js +30 -0
- package/vanilla/UiIcon.d.ts +14 -0
- package/vanilla/UiIcon.js +40 -0
- package/vanilla/UiIconButton.d.ts +19 -0
- package/vanilla/UiIconButton.js +37 -0
- package/vanilla/UiInput.d.ts +18 -0
- package/vanilla/UiInput.js +37 -0
- package/vanilla/UiMenu.d.ts +19 -0
- package/vanilla/UiMenu.js +35 -0
- package/vanilla/UiMenuItem.d.ts +15 -0
- package/vanilla/UiMenuItem.js +43 -0
- package/vanilla/UiPopover.d.ts +18 -0
- package/vanilla/UiPopover.js +33 -0
- package/vanilla/UiRadio.d.ts +19 -0
- package/vanilla/UiRadio.js +48 -0
- package/vanilla/UiRadioGroup.d.ts +21 -0
- package/vanilla/UiRadioGroup.js +35 -0
- package/vanilla/UiReel.d.ts +16 -0
- package/vanilla/UiReel.js +34 -0
- package/vanilla/UiSearchResultRow.d.ts +15 -0
- package/vanilla/UiSearchResultRow.js +82 -0
- package/vanilla/UiSearchShell.d.ts +18 -0
- package/vanilla/UiSearchShell.js +74 -0
- package/vanilla/UiSelect.d.ts +18 -0
- package/vanilla/UiSelect.js +42 -0
- package/vanilla/UiSeparator.d.ts +14 -0
- package/vanilla/UiSeparator.js +28 -0
- package/vanilla/UiSidebar.d.ts +24 -0
- package/vanilla/UiSidebar.js +47 -0
- package/vanilla/UiStack.d.ts +16 -0
- package/vanilla/UiStack.js +32 -0
- package/vanilla/UiSwitch.d.ts +18 -0
- package/vanilla/UiSwitch.js +46 -0
- package/vanilla/UiSwitcher.d.ts +16 -0
- package/vanilla/UiSwitcher.js +32 -0
- package/vanilla/UiTabs.d.ts +18 -0
- package/vanilla/UiTabs.js +47 -0
- package/vanilla/UiTextarea.d.ts +19 -0
- package/vanilla/UiTextarea.js +40 -0
- package/vanilla/UiToastRegion.d.ts +20 -0
- package/vanilla/UiToastRegion.js +58 -0
- package/vanilla/UiTooltip.d.ts +21 -0
- package/vanilla/UiTooltip.js +34 -0
- package/vanilla/UiTopBar.d.ts +13 -0
- package/vanilla/UiTopBar.js +59 -0
- package/vanilla/UiTree.d.ts +18 -0
- package/vanilla/UiTree.js +31 -0
- package/vanilla/UiTreeItem.d.ts +26 -0
- package/vanilla/UiTreeItem.js +164 -0
- package/vanilla/index.js +48 -0
- package/vue/UiAffordanceScope.d.ts +18 -0
- package/vue/UiAffordanceScope.js +2 -0
- package/vue/UiAffordanceScope.vue +118 -0
- package/vue/UiAvatar.d.ts +23 -0
- package/vue/UiAvatar.js +2 -0
- package/vue/UiAvatar.vue +152 -0
- package/vue/UiAvatarGroup.d.ts +20 -0
- package/vue/UiAvatarGroup.js +2 -0
- package/vue/UiAvatarGroup.vue +167 -0
- package/vue/UiBadge.d.ts +20 -0
- package/vue/UiBadge.js +2 -0
- package/vue/UiBadge.vue +123 -0
- package/vue/UiButton.d.ts +22 -0
- package/vue/UiButton.js +2 -0
- package/vue/UiButton.vue +229 -0
- package/vue/UiCallout.d.ts +18 -0
- package/vue/UiCallout.js +2 -0
- package/vue/UiCallout.vue +104 -0
- package/vue/UiCheckbox.d.ts +38 -0
- package/vue/UiCheckbox.js +2 -0
- package/vue/UiCheckbox.vue +216 -0
- package/vue/UiCluster.d.ts +17 -0
- package/vue/UiCluster.js +2 -0
- package/vue/UiCluster.vue +75 -0
- package/vue/UiCombobox.d.ts +255 -0
- package/vue/UiCombobox.js +2 -0
- package/vue/UiCombobox.vue +466 -0
- package/vue/UiContainer.d.ts +17 -0
- package/vue/UiContainer.js +2 -0
- package/vue/UiContainer.vue +65 -0
- package/vue/UiContextMenu.d.ts +50 -0
- package/vue/UiContextMenu.js +2 -0
- package/vue/UiContextMenu.vue +166 -0
- package/vue/UiDialog.d.ts +39 -0
- package/vue/UiDialog.js +2 -0
- package/vue/UiDialog.vue +273 -0
- package/vue/UiDisclosure.d.ts +44 -0
- package/vue/UiDisclosure.js +2 -0
- package/vue/UiDisclosure.vue +211 -0
- package/vue/UiEditable.d.ts +48 -0
- package/vue/UiEditable.js +2 -0
- package/vue/UiEditable.vue +267 -0
- package/vue/UiEditorInsertTableGrid.d.ts +26 -0
- package/vue/UiEditorInsertTableGrid.js +2 -0
- package/vue/UiEditorInsertTableGrid.vue +210 -0
- package/vue/UiEditorMentionMenu.d.ts +44 -0
- package/vue/UiEditorMentionMenu.js +2 -0
- package/vue/UiEditorMentionMenu.vue +284 -0
- package/vue/UiEditorSlashMenu.d.ts +41 -0
- package/vue/UiEditorSlashMenu.js +2 -0
- package/vue/UiEditorSlashMenu.vue +306 -0
- package/vue/UiEditorTableContextMenu.d.ts +19 -0
- package/vue/UiEditorTableContextMenu.js +2 -0
- package/vue/UiEditorTableContextMenu.vue +252 -0
- package/vue/UiEditorTableOverlay.d.ts +66 -0
- package/vue/UiEditorTableOverlay.js +2 -0
- package/vue/UiEditorTableOverlay.vue +455 -0
- package/vue/UiEditorTableToolbar.d.ts +21 -0
- package/vue/UiEditorTableToolbar.js +2 -0
- package/vue/UiEditorTableToolbar.vue +339 -0
- package/vue/UiEditorToolbar.d.ts +18 -0
- package/vue/UiEditorToolbar.js +2 -0
- package/vue/UiEditorToolbar.vue +138 -0
- package/vue/UiFloatingActionButton.d.ts +22 -0
- package/vue/UiFloatingActionButton.js +2 -0
- package/vue/UiFloatingActionButton.vue +116 -0
- package/vue/UiFormField.d.ts +28 -0
- package/vue/UiFormField.js +2 -0
- package/vue/UiFormField.vue +124 -0
- package/vue/UiGrid.d.ts +17 -0
- package/vue/UiGrid.js +2 -0
- package/vue/UiGrid.vue +72 -0
- package/vue/UiIcon.d.ts +8 -0
- package/vue/UiIcon.js +2 -0
- package/vue/UiIcon.vue +135 -0
- package/vue/UiIconButton.d.ts +27 -0
- package/vue/UiIconButton.js +2 -0
- package/vue/UiIconButton.vue +268 -0
- package/vue/UiInput.d.ts +20 -0
- package/vue/UiInput.js +2 -0
- package/vue/UiInput.vue +177 -0
- package/vue/UiMenu.d.ts +52 -0
- package/vue/UiMenu.js +2 -0
- package/vue/UiMenu.vue +194 -0
- package/vue/UiMenuItem.d.ts +20 -0
- package/vue/UiMenuItem.js +2 -0
- package/vue/UiMenuItem.vue +55 -0
- package/vue/UiPopover.d.ts +44 -0
- package/vue/UiPopover.js +2 -0
- package/vue/UiPopover.vue +188 -0
- package/vue/UiRadio.d.ts +38 -0
- package/vue/UiRadio.js +2 -0
- package/vue/UiRadio.vue +166 -0
- package/vue/UiRadioGroup.d.ts +50 -0
- package/vue/UiRadioGroup.js +2 -0
- package/vue/UiRadioGroup.vue +161 -0
- package/vue/UiReel.d.ts +18 -0
- package/vue/UiReel.js +2 -0
- package/vue/UiReel.vue +93 -0
- package/vue/UiSearchResultRow.d.ts +28 -0
- package/vue/UiSearchResultRow.js +2 -0
- package/vue/UiSearchResultRow.vue +124 -0
- package/vue/UiSearchShell.d.ts +42 -0
- package/vue/UiSearchShell.js +2 -0
- package/vue/UiSearchShell.vue +233 -0
- package/vue/UiSelect.d.ts +30 -0
- package/vue/UiSelect.js +2 -0
- package/vue/UiSelect.vue +183 -0
- package/vue/UiSeparator.d.ts +8 -0
- package/vue/UiSeparator.js +2 -0
- package/vue/UiSeparator.vue +38 -0
- package/vue/UiSidebar.d.ts +54 -0
- package/vue/UiSidebar.js +2 -0
- package/vue/UiSidebar.vue +288 -0
- package/vue/UiStack.d.ts +18 -0
- package/vue/UiStack.js +2 -0
- package/vue/UiStack.vue +85 -0
- package/vue/UiSwitch.d.ts +36 -0
- package/vue/UiSwitch.js +2 -0
- package/vue/UiSwitch.vue +203 -0
- package/vue/UiSwitcher.d.ts +18 -0
- package/vue/UiSwitcher.js +2 -0
- package/vue/UiSwitcher.vue +91 -0
- package/vue/UiTabs.d.ts +36 -0
- package/vue/UiTabs.js +2 -0
- package/vue/UiTabs.vue +269 -0
- package/vue/UiTextarea.d.ts +22 -0
- package/vue/UiTextarea.js +2 -0
- package/vue/UiTextarea.vue +177 -0
- package/vue/UiToastRegion.d.ts +48 -0
- package/vue/UiToastRegion.js +2 -0
- package/vue/UiToastRegion.vue +256 -0
- package/vue/UiTooltip.d.ts +50 -0
- package/vue/UiTooltip.js +2 -0
- package/vue/UiTooltip.vue +230 -0
- package/vue/UiTopBar.d.ts +19 -0
- package/vue/UiTopBar.js +2 -0
- package/vue/UiTopBar.vue +56 -0
- package/vue/UiTree.d.ts +62 -0
- package/vue/UiTree.js +2 -0
- package/vue/UiTree.vue +117 -0
- package/vue/UiTreeItem.d.ts +56 -0
- package/vue/UiTreeItem.js +2 -0
- package/vue/UiTreeItem.vue +418 -0
- package/vue/chunks/UiAffordanceScope.js +112 -0
- package/vue/chunks/UiAvatar.js +144 -0
- package/vue/chunks/UiAvatarGroup.js +142 -0
- package/vue/chunks/UiBadge.js +33 -0
- package/vue/chunks/UiButton.js +47 -0
- package/vue/chunks/UiCallout.js +117 -0
- package/vue/chunks/UiCheckbox.js +167 -0
- package/vue/chunks/UiCluster.js +33 -0
- package/vue/chunks/UiCombobox.js +684 -0
- package/vue/chunks/UiContainer.js +33 -0
- package/vue/chunks/UiContextMenu.js +153 -0
- package/vue/chunks/UiDialog.js +162 -0
- package/vue/chunks/UiDisclosure.js +185 -0
- package/vue/chunks/UiEditable.js +223 -0
- package/vue/chunks/UiEditorInsertTableGrid.js +221 -0
- package/vue/chunks/UiEditorMentionMenu.js +245 -0
- package/vue/chunks/UiEditorSlashMenu.js +210 -0
- package/vue/chunks/UiEditorTableContextMenu.js +201 -0
- package/vue/chunks/UiEditorTableOverlay.js +301 -0
- package/vue/chunks/UiEditorTableToolbar.js +299 -0
- package/vue/chunks/UiEditorToolbar.js +37 -0
- package/vue/chunks/UiFloatingActionButton.js +56 -0
- package/vue/chunks/UiFormField.js +113 -0
- package/vue/chunks/UiGrid.js +33 -0
- package/vue/chunks/UiIcon.js +170 -0
- package/vue/chunks/UiIconButton.js +141 -0
- package/vue/chunks/UiInput.js +136 -0
- package/vue/chunks/UiMenu.js +163 -0
- package/vue/chunks/UiMenuItem.js +37 -0
- package/vue/chunks/UiPopover.js +149 -0
- package/vue/chunks/UiRadio.js +164 -0
- package/vue/chunks/UiRadioGroup.js +160 -0
- package/vue/chunks/UiReel.js +42 -0
- package/vue/chunks/UiSearchResultRow.js +63 -0
- package/vue/chunks/UiSearchShell.js +152 -0
- package/vue/chunks/UiSelect.js +134 -0
- package/vue/chunks/UiSeparator.js +24 -0
- package/vue/chunks/UiSidebar.js +180 -0
- package/vue/chunks/UiStack.js +38 -0
- package/vue/chunks/UiSwitch.js +162 -0
- package/vue/chunks/UiSwitcher.js +38 -0
- package/vue/chunks/UiTabs.js +210 -0
- package/vue/chunks/UiTextarea.js +139 -0
- package/vue/chunks/UiToastRegion.js +227 -0
- package/vue/chunks/UiTooltip.js +163 -0
- package/vue/chunks/UiTopBar.js +25 -0
- package/vue/chunks/UiTree.js +140 -0
- package/vue/chunks/UiTreeItem.js +315 -0
- package/vue/chunks/_plugin-vue_export-helper.js +8 -0
- package/vue/components.css +4405 -0
- package/vue/editor/LoomaEditor.d.ts +171 -0
- package/vue/editor/image-delivery.d.ts +61 -0
- package/vue/editor/index.d.ts +4 -386
- package/vue/editor/index.js +995 -1134
- package/vue/editor/primitives.d.ts +23 -0
- package/vue/index.d.ts +48 -604
- package/vue/index.js +49 -86
- package/dist/chunk-MLKGABMK.js +0 -9
- package/dist/chunk-RWLYVQY6.js +0 -5719
- package/dist/chunk-XDNZIKNI.js +0 -2721
- package/dist/declarative-generated.cjs +0 -149
- package/dist/declarative-generated.d.cts +0 -16
- package/dist/declarative-generated.d.ts +0 -16
- package/dist/declarative-generated.js +0 -124
- package/dist/declarative.cjs +0 -2750
- package/dist/declarative.d.cts +0 -101
- package/dist/declarative.d.ts +0 -101
- package/dist/declarative.js +0 -15
- package/dist/index.cjs +0 -9077
- package/dist/index.d.cts +0 -414
- package/dist/loader.cjs +0 -8442
- package/dist/loader.d.cts +0 -4
- package/dist/loader.d.ts +0 -4
- package/dist/loader.js +0 -16
- package/editor/chunk-EUIH3TBV.js +0 -6826
- package/editor/chunk-JK53ENSG.js +0 -29
- package/editor/chunk-U36XNFJN.js +0 -2592
- package/editor/table-overlay-DZFGHQDt.d.ts +0 -138
- package/editor.css +0 -1592
- package/layout/index.cjs +0 -356
- package/layout/index.d.cts +0 -18
- package/layout/index.d.ts +0 -18
- package/layout/index.js +0 -341
- package/layout.css +0 -191
- package/react/index.d.ts +0 -1276
- package/react/index.js +0 -1526
- package/styles.css +0 -940
- package/svelte/index.d.ts +0 -204
- package/svelte/index.js +0 -1892
- package/vue/chunk-OX3EACZ7.js +0 -4369
package/README.md
CHANGED
|
@@ -1,23 +1,54 @@
|
|
|
1
1
|
# @threadlabs/looma
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Looma's components, written once as [Declarative HTML Components](https://nextwebwg.org/html-next/)
|
|
4
|
+
and shipped three ways: as HTML, as Vue components, and as plain DOM factories.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
pnpm add @threadlabs/looma
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
## Vue
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import "@threadlabs/looma/tokens.css";
|
|
14
|
+
import "@threadlabs/looma/vue.css";
|
|
15
|
+
import { Button, TopBar } from "@threadlabs/looma/vue";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The Vue components are ordinary Vue 3.5 single-file components, compiled to JavaScript with
|
|
19
|
+
declarations; the `.vue` sources ship beside them. They depend on Vue and Looma only.
|
|
20
|
+
|
|
21
|
+
The editor is Tiptap-based and needs `@tiptap/vue-3@^2.11.5`:
|
|
22
|
+
|
|
10
23
|
```ts
|
|
11
|
-
import { openOverlay } from "@threadlabs/looma";
|
|
12
|
-
import { TopBar } from "@threadlabs/looma/vue";
|
|
13
24
|
import { LoomaEditor } from "@threadlabs/looma/vue/editor";
|
|
25
|
+
import { getDefaultEditorExtensions } from "@threadlabs/looma/editor/extensions";
|
|
14
26
|
```
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
The editor components' event and geometry types, without Tiptap, are in
|
|
29
|
+
`@threadlabs/looma/editor/ui`.
|
|
30
|
+
|
|
31
|
+
## HTML
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<link rel="stylesheet" href="…/@threadlabs/looma/tokens.css">
|
|
35
|
+
<script type="module">
|
|
36
|
+
import "@threadlabs/looma";
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<ui-stack gap="m">
|
|
40
|
+
<ui-button variant="solid">Save</ui-button>
|
|
41
|
+
</ui-stack>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Importing the package registers every component with the
|
|
45
|
+
[HTML Next runtime](https://www.npmjs.com/package/@nextwebwg/declarative-components), which turns
|
|
46
|
+
each `<ui-*>` element into its native root. Each component's styles are scoped to it; the token
|
|
47
|
+
stylesheet is the only stylesheet to include. Without a build, link components one by one from
|
|
48
|
+
`@threadlabs/looma/components/<tag>/<tag>.html`.
|
|
49
|
+
|
|
50
|
+
## Theming
|
|
51
|
+
|
|
52
|
+
Components read design tokens (`--ui-*` custom properties) from `tokens.css` and a theme
|
|
53
|
+
(`theme-light.css`, `theme-dark.css`, `theme-high-contrast.css`), and each exposes its own
|
|
54
|
+
`--ui-<component>-*` properties. Set them on the component or any ancestor.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const backgrounds = [
|
|
2
|
+
["background-none", "Default", ""], ["background-gray", "Gray", "#f3f4f6"],
|
|
3
|
+
["background-yellow", "Yellow", "#fef3c7"], ["background-blue", "Blue", "#dbeafe"],
|
|
4
|
+
["background-green", "Green", "#dcfce7"], ["background-red", "Red", "#fee2e2"],
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
// Serialized from Looma's framework-neutral icon catalog. Keeping the icon nodes here lets the
|
|
8
|
+
// migration controllers stay directly importable in a browser without introducing a bundler-only
|
|
9
|
+
// package resolution step.
|
|
10
|
+
export const icons = {
|
|
11
|
+
"align-left": [["path", { d: "M21 5H3" }], ["path", { d: "M15 12H3" }], ["path", { d: "M17 19H3" }]],
|
|
12
|
+
"align-center": [["path", { d: "M21 5H3" }], ["path", { d: "M17 12H7" }], ["path", { d: "M19 19H5" }]],
|
|
13
|
+
"align-right": [["path", { d: "M21 5H3" }], ["path", { d: "M21 12H9" }], ["path", { d: "M21 19H7" }]],
|
|
14
|
+
rows: [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M21 9H3" }], ["path", { d: "M21 15H3" }]],
|
|
15
|
+
columns: [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M9 3v18" }], ["path", { d: "M15 3v18" }]],
|
|
16
|
+
table: [["path", { d: "M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18" }]],
|
|
17
|
+
"chevron-down": [["path", { d: "m6 9 6 6 6-6" }]],
|
|
18
|
+
"panel-top": [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M3 9h18" }]],
|
|
19
|
+
"panel-bottom": [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M3 15h18" }]],
|
|
20
|
+
"panel-left": [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M9 3v18" }]],
|
|
21
|
+
"panel-right": [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }], ["path", { d: "M15 3v18" }]],
|
|
22
|
+
eraser: [["path", { d: "M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21" }], ["path", { d: "m5.082 11.09 8.828 8.828" }]],
|
|
23
|
+
merge: [["path", { d: "m8 6 4-4 4 4" }], ["path", { d: "M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22" }], ["path", { d: "m20 22-5-5" }]],
|
|
24
|
+
split: [["path", { d: "M16 3h5v5" }], ["path", { d: "M8 3H3v5" }], ["path", { d: "M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3" }], ["path", { d: "m15 9 6-6" }]],
|
|
25
|
+
trash: [["path", { d: "M10 11v6" }], ["path", { d: "M14 11v6" }], ["path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }], ["path", { d: "M3 6h18" }], ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }]],
|
|
26
|
+
plus: [["path", { d: "M5 12h14" }], ["path", { d: "M12 5v14" }]],
|
|
27
|
+
"grip-vertical": [["circle", { cx: "9", cy: "12", r: "1" }], ["circle", { cx: "9", cy: "5", r: "1" }], ["circle", { cx: "9", cy: "19", r: "1" }], ["circle", { cx: "15", cy: "12", r: "1" }], ["circle", { cx: "15", cy: "5", r: "1" }], ["circle", { cx: "15", cy: "19", r: "1" }]],
|
|
28
|
+
"grip-horizontal": [["circle", { cx: "12", cy: "9", r: "1" }], ["circle", { cx: "19", cy: "9", r: "1" }], ["circle", { cx: "5", cy: "9", r: "1" }], ["circle", { cx: "12", cy: "15", r: "1" }], ["circle", { cx: "19", cy: "15", r: "1" }], ["circle", { cx: "5", cy: "15", r: "1" }]],
|
|
29
|
+
"heading-1": [["path", { d: "M4 12h8" }], ["path", { d: "M4 18V6" }], ["path", { d: "M12 18V6" }], ["path", { d: "m17 12 3-2v8" }]],
|
|
30
|
+
pilcrow: [["path", { d: "M13 4v16" }], ["path", { d: "M17 4v16" }], ["path", { d: "M19 4H9.5a4.5 4.5 0 0 0 0 9H13" }]],
|
|
31
|
+
quote: [["path", { d: "M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z" }], ["path", { d: "M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z" }]],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function normalizeAnchor(value) {
|
|
35
|
+
if (!value || typeof value !== "object") return null;
|
|
36
|
+
const finite = (candidate) => typeof candidate === "number" && Number.isFinite(candidate) ? candidate : null;
|
|
37
|
+
const x = finite(value.x); const y = finite(value.y); const width = finite(value.width); const height = finite(value.height);
|
|
38
|
+
const left = finite(value.left) ?? x; const top = finite(value.top) ?? y;
|
|
39
|
+
const right = finite(value.right) ?? (left !== null && width !== null ? left + width : null);
|
|
40
|
+
const bottom = finite(value.bottom) ?? (top !== null && height !== null ? top + height : null);
|
|
41
|
+
return left === null || top === null || right === null || bottom === null ? null : { left, top, right, bottom };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function viewport() {
|
|
45
|
+
const visual = window.visualViewport;
|
|
46
|
+
const left = visual?.offsetLeft ?? 0; const top = visual?.offsetTop ?? 0;
|
|
47
|
+
const width = visual?.width ?? window.innerWidth; const height = visual?.height ?? window.innerHeight;
|
|
48
|
+
return { left, top, width, height, right: left + width, bottom: top + height };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Places a floating menu below its anchor (or above, when there is more room), or as a bottom sheet on
|
|
53
|
+
* narrow screens.
|
|
54
|
+
*/
|
|
55
|
+
export function positionMenu(element, rect, width) {
|
|
56
|
+
if (!rect) return;
|
|
57
|
+
const view = viewport();
|
|
58
|
+
Object.assign(element.style, { position: "fixed", zIndex: "500" });
|
|
59
|
+
if (window.innerWidth < 768) {
|
|
60
|
+
const height = Math.min(320, Math.max(0, view.height - 56));
|
|
61
|
+
Object.assign(element.style, { left: `${view.left}px`, right: "", bottom: "", top: `${view.top + view.height - height - 56}px`, width: `${view.width}px`, maxHeight: `${height}px` });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const below = view.bottom - rect.bottom - 8;
|
|
65
|
+
const above = rect.top - view.top - 8;
|
|
66
|
+
const top = below >= 320 || below >= above ? rect.bottom + 8 : rect.top - 328;
|
|
67
|
+
const left = Math.max(view.left + 8, Math.min(rect.left, view.right - width - 8));
|
|
68
|
+
Object.assign(element.style, { top: `${Math.max(view.top + 8, top)}px`, left: `${left}px`, right: "", bottom: "", width: `${width}px`, maxHeight: "" });
|
|
69
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const initialized = new WeakSet();
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Marks the document `data-ui-input-modality="touch"` once the user actually touches it, rather than
|
|
5
|
+
* guessing from capability media queries: hybrid devices keep compact pointer affordances until then.
|
|
6
|
+
* Idempotent per document; touch is sticky once observed.
|
|
7
|
+
*/
|
|
8
|
+
export function trackInputModality(document) {
|
|
9
|
+
if (initialized.has(document)) return;
|
|
10
|
+
initialized.add(document);
|
|
11
|
+
const markTouch = () => {
|
|
12
|
+
if (document.documentElement.getAttribute("data-ui-input-modality") === "touch") return;
|
|
13
|
+
document.documentElement.setAttribute("data-ui-input-modality", "touch");
|
|
14
|
+
document.dispatchEvent(new CustomEvent("ui-input-modality-change", { detail: { modality: "touch" } }));
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener("pointerdown", (event) => {
|
|
17
|
+
if (event.pointerType === "touch") markTouch();
|
|
18
|
+
}, { capture: true, passive: true });
|
|
19
|
+
document.addEventListener("touchstart", markTouch, { capture: true, passive: true });
|
|
20
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
const stacks = new WeakMap();
|
|
2
|
+
let anchorSequence = 0;
|
|
3
|
+
const anchorBindings = new WeakMap();
|
|
4
|
+
|
|
5
|
+
function stateFor(document) {
|
|
6
|
+
let state = stacks.get(document);
|
|
7
|
+
if (state) return state;
|
|
8
|
+
state = { records: [], modalCount: 0, listening: false };
|
|
9
|
+
stacks.set(document, state);
|
|
10
|
+
return state;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function syncScrollLock(document, state) {
|
|
14
|
+
document.documentElement.toggleAttribute("data-ui-scroll-lock", state.modalCount > 0);
|
|
15
|
+
if (state.modalCount > 0) document.documentElement.style.overflow = "hidden";
|
|
16
|
+
else document.documentElement.style.removeProperty("overflow");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requestClose(document, reason, trigger) {
|
|
20
|
+
const record = stateFor(document).records.at(-1);
|
|
21
|
+
if (!record || (record.dismissible === false && (reason === "escape" || reason === "light-dismiss"))) return false;
|
|
22
|
+
record.requestClose(reason, trigger);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ensureListeners(document, state) {
|
|
27
|
+
if (state.listening) return;
|
|
28
|
+
state.onKeydown = (event) => {
|
|
29
|
+
if (event.key === "Escape") requestClose(document, "escape", "keyboard");
|
|
30
|
+
};
|
|
31
|
+
state.onPointerdown = (event) => {
|
|
32
|
+
const top = state.records.at(-1);
|
|
33
|
+
if (!top || top.dismissible === false) return;
|
|
34
|
+
const boundary = [top.element, ...(top.relatedElements ?? [])];
|
|
35
|
+
// A modal dialog's ::backdrop reports the dialog itself as the target; a press outside its box is outside.
|
|
36
|
+
const rect = top.element.getBoundingClientRect();
|
|
37
|
+
const onBackdrop = event.target === top.element
|
|
38
|
+
&& (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom);
|
|
39
|
+
if (!onBackdrop && event.composedPath().some((target) => target instanceof Node && boundary.some((element) => element.contains(target)))) return;
|
|
40
|
+
requestClose(document, "light-dismiss", "pointer");
|
|
41
|
+
};
|
|
42
|
+
document.addEventListener("keydown", state.onKeydown);
|
|
43
|
+
document.addEventListener("pointerdown", state.onPointerdown, true);
|
|
44
|
+
state.listening = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function removeListeners(document, state) {
|
|
48
|
+
if (!state.listening || state.records.length) return;
|
|
49
|
+
document.removeEventListener("keydown", state.onKeydown);
|
|
50
|
+
document.removeEventListener("pointerdown", state.onPointerdown, true);
|
|
51
|
+
state.listening = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function openOverlay(record) {
|
|
55
|
+
const document = record.element.ownerDocument;
|
|
56
|
+
const state = stateFor(document);
|
|
57
|
+
closeOverlay(document, record.id);
|
|
58
|
+
state.records.push(record);
|
|
59
|
+
if (record.modal) {
|
|
60
|
+
state.modalCount += 1;
|
|
61
|
+
syncScrollLock(document, state);
|
|
62
|
+
}
|
|
63
|
+
ensureListeners(document, state);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function closeOverlay(document, id) {
|
|
67
|
+
const state = stateFor(document);
|
|
68
|
+
const index = state.records.findIndex((record) => record.id === id);
|
|
69
|
+
if (index >= 0) {
|
|
70
|
+
const [record] = state.records.splice(index, 1);
|
|
71
|
+
if (record?.modal) {
|
|
72
|
+
state.modalCount = Math.max(0, state.modalCount - 1);
|
|
73
|
+
syncScrollLock(document, state);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
removeListeners(document, state);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function requestTopOverlayClose(document, reason, trigger) {
|
|
80
|
+
return requestClose(document, reason, trigger);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function topLayerOpen(surface) {
|
|
84
|
+
try { return surface.matches(":popover-open"); } catch { return !surface.hidden; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function show(surface) {
|
|
88
|
+
surface.setAttribute("popover", "manual");
|
|
89
|
+
surface.hidden = false;
|
|
90
|
+
if (typeof surface.showPopover === "function" && !topLayerOpen(surface)) {
|
|
91
|
+
try { surface.showPopover(); } catch {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hide(surface) {
|
|
96
|
+
if (typeof surface.hidePopover === "function" && topLayerOpen(surface)) {
|
|
97
|
+
try { surface.hidePopover(); } catch {}
|
|
98
|
+
}
|
|
99
|
+
surface.hidden = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function bindAnchor(anchor, name) {
|
|
103
|
+
const binding = anchorBindings.get(anchor) ?? { original: anchor.style.getPropertyValue("anchor-name"), names: new Set() };
|
|
104
|
+
binding.names.add(name);
|
|
105
|
+
anchorBindings.set(anchor, binding);
|
|
106
|
+
anchor.style.setProperty("anchor-name", [binding.original, ...binding.names].filter(Boolean).join(", "));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function unbindAnchor(anchor, name) {
|
|
110
|
+
const binding = anchorBindings.get(anchor);
|
|
111
|
+
if (!binding) return;
|
|
112
|
+
binding.names.delete(name);
|
|
113
|
+
if (binding.names.size) anchor.style.setProperty("anchor-name", [binding.original, ...binding.names].filter(Boolean).join(", "));
|
|
114
|
+
else {
|
|
115
|
+
if (binding.original) anchor.style.setProperty("anchor-name", binding.original);
|
|
116
|
+
else anchor.style.removeProperty("anchor-name");
|
|
117
|
+
anchorBindings.delete(anchor);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function viewport(owner) {
|
|
122
|
+
const visual = owner.visualViewport;
|
|
123
|
+
const left = visual?.offsetLeft ?? 0;
|
|
124
|
+
const top = visual?.offsetTop ?? 0;
|
|
125
|
+
const width = visual?.width ?? owner.innerWidth;
|
|
126
|
+
const height = visual?.height ?? owner.innerHeight;
|
|
127
|
+
return { left, top, right: left + width, bottom: top + height, width, height };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function clamp(rect, bounds, gutter) {
|
|
131
|
+
const availableWidth = Math.max(0, bounds.width - gutter * 2);
|
|
132
|
+
const availableHeight = Math.max(0, bounds.height - gutter * 2);
|
|
133
|
+
const left = rect.width > availableWidth ? bounds.left + gutter : Math.min(Math.max(rect.left, bounds.left + gutter), bounds.right - gutter - rect.width);
|
|
134
|
+
const top = rect.height > availableHeight ? bounds.top + gutter : Math.min(Math.max(rect.top, bounds.top + gutter), bounds.bottom - gutter - rect.height);
|
|
135
|
+
return { x: left - rect.left, y: top - rect.top };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Entry transitions scale and nudge the surface; measure its untransformed layout box so a position
|
|
139
|
+
// computed mid-animation is not shrunk or offset (offsetWidth/Height ignore transforms).
|
|
140
|
+
function layoutRect(surface) {
|
|
141
|
+
const box = surface.getBoundingClientRect();
|
|
142
|
+
const width = surface.offsetWidth || box.width;
|
|
143
|
+
const height = surface.offsetHeight || box.height;
|
|
144
|
+
const left = box.left + (box.width - width) / 2;
|
|
145
|
+
const top = box.top + (box.height - height) / 2;
|
|
146
|
+
return { left, top, right: left + width, bottom: top + height, width, height };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function fallbackPosition(surface, anchor, placement, gap, viewportGap, surfaceRect = layoutRect(surface)) {
|
|
150
|
+
const bounds = viewport(surface.ownerDocument.defaultView);
|
|
151
|
+
const preferTop = placement.startsWith("top");
|
|
152
|
+
const preferEnd = placement.endsWith("end");
|
|
153
|
+
const below = bounds.bottom - anchor.bottom - viewportGap;
|
|
154
|
+
const above = anchor.top - bounds.top - viewportGap;
|
|
155
|
+
const useTop = preferTop ? !(above < surfaceRect.height + gap && below > above) : below < surfaceRect.height + gap && above > below;
|
|
156
|
+
const top = useTop ? anchor.top - surfaceRect.height - gap : anchor.bottom + gap;
|
|
157
|
+
const left = preferEnd ? anchor.right - surfaceRect.width : anchor.left;
|
|
158
|
+
const shift = clamp({ left, top, right: left + surfaceRect.width, bottom: top + surfaceRect.height, width: surfaceRect.width, height: surfaceRect.height }, bounds, viewportGap);
|
|
159
|
+
surface.style.left = `${Math.round(left + shift.x)}px`;
|
|
160
|
+
surface.style.top = `${Math.round(top + shift.y)}px`;
|
|
161
|
+
surface.style.right = "auto";
|
|
162
|
+
surface.style.bottom = "auto";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createAnchoredSurface(surface, options = {}) {
|
|
166
|
+
const owner = surface.ownerDocument.defaultView;
|
|
167
|
+
let placement = options.placement ?? "bottom-start";
|
|
168
|
+
const gap = Math.max(0, options.gap ?? 4);
|
|
169
|
+
const viewportGap = Math.max(0, options.viewportGap ?? 8);
|
|
170
|
+
const nativeAnchor = owner.CSS?.supports?.("anchor-name: --ui-anchor-test") && owner.CSS.supports("position-anchor: --ui-anchor-test") && owner.CSS.supports("top: anchor(bottom)");
|
|
171
|
+
const anchorName = `--ui-anchor-${++anchorSequence}`;
|
|
172
|
+
let anchor = options.anchor ?? null;
|
|
173
|
+
let point = null;
|
|
174
|
+
let open = false;
|
|
175
|
+
let frame = null;
|
|
176
|
+
let abort = null;
|
|
177
|
+
let sizeObserver = null;
|
|
178
|
+
surface.setAttribute("popover", "manual");
|
|
179
|
+
surface.dataset.uiPositioning = nativeAnchor ? "anchor" : "fallback";
|
|
180
|
+
surface.style.position = "fixed";
|
|
181
|
+
surface.style.margin = "0";
|
|
182
|
+
const bind = (next) => {
|
|
183
|
+
if (anchor) unbindAnchor(anchor, anchorName);
|
|
184
|
+
anchor = next;
|
|
185
|
+
if (anchor) {
|
|
186
|
+
bindAnchor(anchor, anchorName);
|
|
187
|
+
surface.style.setProperty("position-anchor", anchorName);
|
|
188
|
+
} else surface.style.removeProperty("position-anchor");
|
|
189
|
+
};
|
|
190
|
+
const position = () => {
|
|
191
|
+
frame = null;
|
|
192
|
+
if (!open) return;
|
|
193
|
+
if (point) {
|
|
194
|
+
fallbackPosition(surface, { left: point.x, right: point.x, top: point.y, bottom: point.y, width: 0, height: 0 }, "bottom-start", gap, viewportGap);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!anchor) return;
|
|
198
|
+
let rect;
|
|
199
|
+
if (nativeAnchor) {
|
|
200
|
+
const blockEnd = placement.startsWith("bottom");
|
|
201
|
+
const inlineEnd = placement.endsWith("end");
|
|
202
|
+
surface.style.top = blockEnd ? `calc(anchor(bottom) + ${gap}px)` : "auto";
|
|
203
|
+
surface.style.bottom = blockEnd ? "auto" : `calc(anchor(top) + ${gap}px)`;
|
|
204
|
+
surface.style.left = inlineEnd ? "auto" : "anchor(left)";
|
|
205
|
+
surface.style.right = inlineEnd ? "anchor(right)" : "auto";
|
|
206
|
+
surface.style.setProperty("position-try-fallbacks", "flip-block, flip-inline");
|
|
207
|
+
rect = layoutRect(surface);
|
|
208
|
+
if (!rect.width || !rect.height) return;
|
|
209
|
+
const shift = clamp(rect, viewport(owner), viewportGap);
|
|
210
|
+
if (!shift.x && !shift.y) return;
|
|
211
|
+
}
|
|
212
|
+
fallbackPosition(surface, anchor.getBoundingClientRect(), placement, gap, viewportGap, rect);
|
|
213
|
+
};
|
|
214
|
+
const schedule = () => {
|
|
215
|
+
if (open && frame === null) frame = owner.requestAnimationFrame(position);
|
|
216
|
+
};
|
|
217
|
+
const syncListeners = () => {
|
|
218
|
+
const needed = open && (point || anchor);
|
|
219
|
+
if (!needed) {
|
|
220
|
+
abort?.abort();
|
|
221
|
+
abort = null;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (abort) return;
|
|
225
|
+
abort = new AbortController();
|
|
226
|
+
const signal = abort.signal;
|
|
227
|
+
owner.addEventListener("resize", schedule, { passive: true, signal });
|
|
228
|
+
owner.addEventListener("scroll", schedule, { passive: true, capture: true, signal });
|
|
229
|
+
owner.visualViewport?.addEventListener("resize", schedule, { passive: true, signal });
|
|
230
|
+
owner.visualViewport?.addEventListener("scroll", schedule, { passive: true, signal });
|
|
231
|
+
};
|
|
232
|
+
const observeSize = () => {
|
|
233
|
+
if (sizeObserver || !owner.ResizeObserver) return;
|
|
234
|
+
sizeObserver = new owner.ResizeObserver(schedule);
|
|
235
|
+
sizeObserver.observe(surface);
|
|
236
|
+
};
|
|
237
|
+
const stopSize = () => { sizeObserver?.disconnect(); sizeObserver = null; };
|
|
238
|
+
bind(anchor);
|
|
239
|
+
return {
|
|
240
|
+
setAnchor(next) { point = null; bind(next); syncListeners(); schedule(); },
|
|
241
|
+
setPlacement(next) { placement = next || "bottom-start"; schedule(); },
|
|
242
|
+
show() { point = null; open = true; syncListeners(); show(surface); position(); schedule(); observeSize(); },
|
|
243
|
+
showAtPoint(next) { point = next; open = true; syncListeners(); show(surface); position(); schedule(); observeSize(); },
|
|
244
|
+
hide() { open = false; point = null; syncListeners(); stopSize(); if (frame !== null) owner.cancelAnimationFrame(frame); frame = null; hide(surface); },
|
|
245
|
+
refresh: schedule,
|
|
246
|
+
destroy() { open = false; abort?.abort(); abort = null; stopSize(); if (frame !== null) owner.cancelAnimationFrame(frame); frame = null; hide(surface); bind(null); },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolves `for`-style ID references that may not exist yet. `get(id)` returns the element or, when
|
|
252
|
+
* it is missing, watches the document and calls `onLate` once an element with that ID appears
|
|
253
|
+
* (examples, templates, and frameworks often render the trigger after the overlay).
|
|
254
|
+
*/
|
|
255
|
+
export function createIdResolver(document, onLate) {
|
|
256
|
+
let observer = null;
|
|
257
|
+
let waiting = "";
|
|
258
|
+
const stop = () => {
|
|
259
|
+
observer?.disconnect();
|
|
260
|
+
observer = null;
|
|
261
|
+
waiting = "";
|
|
262
|
+
};
|
|
263
|
+
const get = (id) => {
|
|
264
|
+
const found = id ? document.getElementById(id) : null;
|
|
265
|
+
if (found || !id) {
|
|
266
|
+
if (waiting) stop();
|
|
267
|
+
return found;
|
|
268
|
+
}
|
|
269
|
+
if (waiting === id) return null;
|
|
270
|
+
stop();
|
|
271
|
+
waiting = id;
|
|
272
|
+
observer = new MutationObserver(() => {
|
|
273
|
+
if (!document.getElementById(waiting)) return;
|
|
274
|
+
stop();
|
|
275
|
+
onLate();
|
|
276
|
+
});
|
|
277
|
+
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["id"] });
|
|
278
|
+
return null;
|
|
279
|
+
};
|
|
280
|
+
return { get, stop };
|
|
281
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports what caused the next change on a component: a `change` event is a plain Event, so the
|
|
3
|
+
* keyboard or pointer that preceded it is remembered here. Returns [take, stop].
|
|
4
|
+
*/
|
|
5
|
+
export function trackTrigger(host) {
|
|
6
|
+
let trigger = "programmatic";
|
|
7
|
+
const offKey = host.on("keydown", (event) => {
|
|
8
|
+
if (event.key === " " || event.key === "Enter" || event.key.startsWith("Arrow")) trigger = "keyboard";
|
|
9
|
+
});
|
|
10
|
+
const offPointer = host.on("pointerdown", () => { trigger = "pointer"; });
|
|
11
|
+
const take = () => {
|
|
12
|
+
const current = trigger;
|
|
13
|
+
trigger = "programmatic";
|
|
14
|
+
return current;
|
|
15
|
+
};
|
|
16
|
+
return [take, () => { offKey(); offPointer(); }];
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template component="ui-affordance-scope" controller="./ui-affordance-scope.js">
|
|
2
|
+
<defs>
|
|
3
|
+
<prop name="nearRadius" type="number" default="16">How close, in pixels, the pointer must come to reveal an affordance.</prop>
|
|
4
|
+
<state name="engaged" :value="false"></state>
|
|
5
|
+
</defs>
|
|
6
|
+
<span><slot></slot></span>
|
|
7
|
+
<style>
|
|
8
|
+
:host {
|
|
9
|
+
display: contents;
|
|
10
|
+
--ui-affordance-scope-engaged: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:host-state([engaged]) {
|
|
14
|
+
--ui-affordance-scope-engaged: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
18
|
+
:host[hidden] {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function distance(point, rect) {
|
|
2
|
+
const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);
|
|
3
|
+
const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);
|
|
4
|
+
return Math.hypot(dx, dy);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function controller(host) {
|
|
8
|
+
const element = host.element;
|
|
9
|
+
const owner = element.ownerDocument.defaultView;
|
|
10
|
+
let anchors = [];
|
|
11
|
+
let frame = null;
|
|
12
|
+
let point = null;
|
|
13
|
+
let dirty = false;
|
|
14
|
+
let nearRadius = Number(host.state.nearRadius ?? 16);
|
|
15
|
+
const clear = () => {
|
|
16
|
+
for (const anchor of anchors) anchor.element.removeAttribute("data-ui-proximity");
|
|
17
|
+
host.state.engaged = false;
|
|
18
|
+
};
|
|
19
|
+
const measure = () => {
|
|
20
|
+
anchors = Array.from(element.querySelectorAll("[data-ui-affordance]"))
|
|
21
|
+
.filter((anchor) => !anchor.hasAttribute("disabled") && anchor.getAttribute("aria-disabled") !== "true")
|
|
22
|
+
.map((anchor) => ({ element: anchor, rect: anchor.getBoundingClientRect() }));
|
|
23
|
+
dirty = false;
|
|
24
|
+
};
|
|
25
|
+
const update = () => {
|
|
26
|
+
frame = null;
|
|
27
|
+
if (dirty) measure();
|
|
28
|
+
if (!point) {
|
|
29
|
+
clear();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let engaged = false;
|
|
33
|
+
for (const anchor of anchors) {
|
|
34
|
+
const near = distance(point, anchor.rect) <= nearRadius;
|
|
35
|
+
anchor.element.toggleAttribute("data-ui-proximity", near);
|
|
36
|
+
if (near) anchor.element.setAttribute("data-ui-proximity", "near");
|
|
37
|
+
engaged ||= near;
|
|
38
|
+
}
|
|
39
|
+
host.state.engaged = engaged;
|
|
40
|
+
};
|
|
41
|
+
const schedule = () => {
|
|
42
|
+
if (frame === null) frame = owner.requestAnimationFrame(update);
|
|
43
|
+
};
|
|
44
|
+
const invalidate = () => {
|
|
45
|
+
dirty = true;
|
|
46
|
+
schedule();
|
|
47
|
+
};
|
|
48
|
+
const onPointermove = (event) => {
|
|
49
|
+
point = event.pointerType === "touch" ? null : { x: event.clientX, y: event.clientY };
|
|
50
|
+
schedule();
|
|
51
|
+
};
|
|
52
|
+
const observer = new MutationObserver(invalidate);
|
|
53
|
+
observer.observe(element, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-ui-affordance", "disabled", "aria-disabled"] });
|
|
54
|
+
element.ownerDocument.addEventListener("pointermove", onPointermove, { passive: true });
|
|
55
|
+
owner.addEventListener("resize", invalidate, { passive: true });
|
|
56
|
+
owner.addEventListener("scroll", invalidate, { passive: true, capture: true });
|
|
57
|
+
owner.visualViewport?.addEventListener("resize", invalidate, { passive: true });
|
|
58
|
+
owner.visualViewport?.addEventListener("scroll", invalidate, { passive: true });
|
|
59
|
+
const stop = host.effect(() => {
|
|
60
|
+
const configured = Number(host.state.nearRadius ?? 16);
|
|
61
|
+
nearRadius = Number.isFinite(configured) ? Math.max(0, configured) : 16;
|
|
62
|
+
invalidate();
|
|
63
|
+
});
|
|
64
|
+
measure();
|
|
65
|
+
return () => {
|
|
66
|
+
stop();
|
|
67
|
+
observer.disconnect();
|
|
68
|
+
element.ownerDocument.removeEventListener("pointermove", onPointermove);
|
|
69
|
+
owner.removeEventListener("resize", invalidate);
|
|
70
|
+
owner.removeEventListener("scroll", invalidate, true);
|
|
71
|
+
owner.visualViewport?.removeEventListener("resize", invalidate);
|
|
72
|
+
owner.visualViewport?.removeEventListener("scroll", invalidate);
|
|
73
|
+
if (frame !== null) owner.cancelAnimationFrame(frame);
|
|
74
|
+
clear();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template component="ui-avatar" controller="./ui-avatar.js">
|
|
2
|
+
<defs>
|
|
3
|
+
<prop name="src" type="string">Image URL.</prop>
|
|
4
|
+
<prop name="alt" type="string" default="">alt token.</prop>
|
|
5
|
+
<prop name="name" type="string" default="">name token.</prop>
|
|
6
|
+
<prop name="fallback" type="string" default="">fallback token.</prop>
|
|
7
|
+
<state name="hasImage" :value="false"></state>
|
|
8
|
+
<state name="hasAuthoredImage" :value="false"></state>
|
|
9
|
+
<state name="initials" :value="'?'"></state>
|
|
10
|
+
</defs>
|
|
11
|
+
<span>
|
|
12
|
+
<slot></slot>
|
|
13
|
+
<img class="image" $ref="image" :src="src" alt="" aria-hidden="true" :hidden="hasAuthoredImage or not hasImage">
|
|
14
|
+
<span class="fallback" :aria-hidden="hasImage" :hidden="hasImage" $value="initials"></span>
|
|
15
|
+
</span>
|
|
16
|
+
<style>
|
|
17
|
+
:host {
|
|
18
|
+
display: inline-grid;
|
|
19
|
+
place-items: center;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
inline-size: 2.5rem;
|
|
22
|
+
block-size: 2.5rem;
|
|
23
|
+
border-radius: 999px;
|
|
24
|
+
border: 1px solid var(--ui-avatar-border, var(--ui-border-strong));
|
|
25
|
+
background: var(--ui-avatar-surface, var(--ui-accent-soft));
|
|
26
|
+
color: var(--ui-avatar-text, var(--ui-accent-solid));
|
|
27
|
+
font: inherit;
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
text-transform: uppercase;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.image,
|
|
34
|
+
:slotted(img) {
|
|
35
|
+
grid-area: 1 / 1;
|
|
36
|
+
inline-size: 100%;
|
|
37
|
+
block-size: 100%;
|
|
38
|
+
object-fit: cover;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.fallback {
|
|
42
|
+
grid-area: 1 / 1;
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
inline-size: 100%;
|
|
47
|
+
block-size: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[hidden] {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
55
|
+
:host[hidden] {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function toInitials(value) {
|
|
2
|
+
const tokens = (value || "").trim().split(/\s+/).filter((token) => token.length > 0);
|
|
3
|
+
if (tokens.length === 0) return "?";
|
|
4
|
+
if (tokens.length === 1) return tokens[0].slice(0, 2).toUpperCase();
|
|
5
|
+
return `${tokens[0][0] ?? ""}${tokens[1][0] ?? ""}`.toUpperCase();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Shows an authored <img>, else the `src` image, else initials, and tracks whether the image loaded.
|
|
9
|
+
export default function controller(host) {
|
|
10
|
+
const element = host.element;
|
|
11
|
+
const managed = host.refs.image;
|
|
12
|
+
let active;
|
|
13
|
+
const onLoad = () => { host.state.hasImage = true; };
|
|
14
|
+
const onError = () => { host.state.hasImage = false; };
|
|
15
|
+
const watch = (image) => {
|
|
16
|
+
if (image === active) return;
|
|
17
|
+
active?.removeEventListener("load", onLoad);
|
|
18
|
+
active?.removeEventListener("error", onError);
|
|
19
|
+
active = image;
|
|
20
|
+
active.addEventListener("load", onLoad);
|
|
21
|
+
active.addEventListener("error", onError);
|
|
22
|
+
host.state.hasImage = active.complete && active.naturalWidth > 0;
|
|
23
|
+
};
|
|
24
|
+
const apply = () => {
|
|
25
|
+
const authored = Array.from(element.querySelectorAll("img")).find((image) => image !== managed);
|
|
26
|
+
host.state.hasAuthoredImage = Boolean(authored);
|
|
27
|
+
watch(authored ?? managed);
|
|
28
|
+
if (authored) {
|
|
29
|
+
authored.hidden = !host.state.hasImage;
|
|
30
|
+
element.removeAttribute("role");
|
|
31
|
+
element.removeAttribute("aria-label");
|
|
32
|
+
} else {
|
|
33
|
+
element.setAttribute("role", "img");
|
|
34
|
+
element.setAttribute("aria-label", host.state.alt || host.state.name || "Avatar");
|
|
35
|
+
}
|
|
36
|
+
host.state.initials = host.state.fallback || toInitials(host.state.name || host.state.alt);
|
|
37
|
+
};
|
|
38
|
+
const observer = new MutationObserver(apply);
|
|
39
|
+
observer.observe(element, { childList: true });
|
|
40
|
+
const stop = host.effect(apply);
|
|
41
|
+
return () => {
|
|
42
|
+
stop();
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
active?.removeEventListener("load", onLoad);
|
|
45
|
+
active?.removeEventListener("error", onError);
|
|
46
|
+
};
|
|
47
|
+
}
|