@getdom/studio 0.1.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.
Files changed (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,490 @@
1
+ export const builtInIconSelectorItems = [
2
+ { label: 'Add', group: 'Actions', path: 'M12 5v14M5 12h14', keywords: ['plus', 'new', 'create'] },
3
+ { label: 'Remove', group: 'Actions', path: 'M5 12h14', keywords: ['minus', 'delete'] },
4
+ { label: 'Close', group: 'Actions', path: 'M6 6l12 12M18 6 6 18', keywords: ['dismiss', 'x'] },
5
+ { label: 'Check', group: 'Actions', path: 'M20 6 9 17l-5-5', keywords: ['done', 'success'] },
6
+ { label: 'Edit', group: 'Actions', path: 'M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16v4Zm11-13 2 2', keywords: ['pencil', 'compose'] },
7
+ { label: 'Copy', group: 'Actions', path: 'M8 8h11v11H8V8Zm-3 8H4V4h12v1', keywords: ['duplicate'] },
8
+ { label: 'Share', group: 'Actions', path: 'M8 12h8M15 7l5 5-5 5M4 5v14', keywords: ['send'] },
9
+ { label: 'Download', group: 'Actions', path: 'M12 4v10M8 10l4 4 4-4M5 19h14', keywords: ['export'] },
10
+ { label: 'Upload', group: 'Actions', path: 'M12 16V4M8 8l4-4 4 4M5 20h14', keywords: ['import'] },
11
+ { label: 'Search', group: 'Navigation', path: 'M10.5 18a7.5 7.5 0 1 1 5.3-2.2L21 21', keywords: ['find'] },
12
+ { label: 'Filter', group: 'Navigation', path: 'M5 6h14M8 12h8M10 18h4', keywords: ['funnel'] },
13
+ { label: 'Settings', group: 'Navigation', path: 'M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0-5v3M12 18v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M3 12h3M18 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1', keywords: ['gear'] },
14
+ { label: 'Menu', group: 'Navigation', path: 'M6 7h12M6 12h12M6 17h12', keywords: ['list'] },
15
+ { label: 'More horizontal', group: 'Navigation', path: 'M5 12h.01M12 12h.01M19 12h.01', keywords: ['ellipsis'] },
16
+ { label: 'More vertical', group: 'Navigation', path: 'M12 5h.01M12 12h.01M12 19h.01', keywords: ['ellipsis'] },
17
+ { label: 'Chevron down', group: 'Navigation', path: 'M7 10l5 5 5-5', keywords: ['expand'] },
18
+ { label: 'Chevron up', group: 'Navigation', path: 'M7 14l5-5 5 5', keywords: ['collapse'] },
19
+ { label: 'Chevron left', group: 'Navigation', path: 'M14 7l-5 5 5 5', keywords: ['previous'] },
20
+ { label: 'Chevron right', group: 'Navigation', path: 'M10 7l5 5-5 5', keywords: ['next'] },
21
+ { label: 'Arrow left', group: 'Navigation', path: 'M19 12H5M12 5l-7 7 7 7', keywords: ['back'] },
22
+ { label: 'Arrow right', group: 'Navigation', path: 'M5 12h14M12 5l7 7-7 7', keywords: ['forward'] },
23
+ { label: 'Arrow up', group: 'Navigation', path: 'M12 19V5M5 12l7-7 7 7', keywords: ['top'] },
24
+ { label: 'Arrow down', group: 'Navigation', path: 'M12 5v14M5 12l7 7 7-7', keywords: ['bottom'] },
25
+ { label: 'Home', group: 'Navigation', path: 'M4 11 12 4l8 7v9h-5v-6H9v6H4v-9Z', keywords: ['house'] },
26
+ { label: 'External link', group: 'Navigation', path: 'M14 5h5v5M13 11l6-6M19 14v5H5V5h5', keywords: ['open'] },
27
+ { label: 'Info', group: 'Status', path: 'M12 17v-5M12 8h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z', keywords: ['notice'] },
28
+ { label: 'Success', group: 'Status', path: 'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM8.5 12.5l2.5 2.5 4.5-5', keywords: ['check', 'complete'] },
29
+ { label: 'Warning', group: 'Status', path: 'M12 4l9 16H3L12 4ZM12 9v4M12 17h.01', keywords: ['alert', 'risk'] },
30
+ { label: 'Error', group: 'Status', path: 'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM15 9l-6 6M9 9l6 6', keywords: ['danger', 'failed'] },
31
+ { label: 'Help', group: 'Status', path: 'M9.5 9a2.5 2.5 0 1 1 4.2 1.8c-.9.7-1.7 1.2-1.7 2.7M12 17h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z', keywords: ['question'] },
32
+ { label: 'Lock', group: 'Security', path: 'M7 11V8a5 5 0 0 1 10 0v3M6 11h12v9H6v-9Zm6 3v2', keywords: ['secure'] },
33
+ { label: 'Unlock', group: 'Security', path: 'M8 11V8a5 5 0 0 1 9.5-2.2M6 11h12v9H6v-9Zm6 3v2', keywords: ['open'] },
34
+ { label: 'Shield', group: 'Security', path: 'M12 3 5 6v5c0 4.5 3 8 7 10 4-2 7-5.5 7-10V6l-7-3ZM9.5 12l1.75 1.75L15 10', keywords: ['protect'] },
35
+ { label: 'User', group: 'People', path: 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM5 20a7 7 0 0 1 14 0', keywords: ['profile'] },
36
+ { label: 'Users', group: 'People', path: 'M9 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm8 1a3 3 0 1 0 0-6M4 20a5 5 0 0 1 10 0M13 20a5 5 0 0 1 7-4.5', keywords: ['team'] },
37
+ { label: 'Mail', group: 'Communication', path: 'M4 6h16v12H4V6Zm0 0 8 7 8-7', keywords: ['email'] },
38
+ { label: 'Message', group: 'Communication', path: 'M5 6h14v9H9l-4 4V6Z', keywords: ['chat', 'comment'] },
39
+ { label: 'Bell', group: 'Communication', path: 'M15 17H9m9-2H6l2-3V9a4 4 0 0 1 8 0v3l2 3ZM10 19a2 2 0 0 0 4 0', keywords: ['notification'] },
40
+ { label: 'Calendar', group: 'Time', path: 'M7 4v3M17 4v3M5 8h14M6 6h12v13H6V6Zm3 6h2M13 12h2M9 16h2', keywords: ['date'] },
41
+ { label: 'Clock', group: 'Time', path: 'M12 6v6l4 2M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z', keywords: ['time'] },
42
+ { label: 'File', group: 'Files', path: 'M6 4h8l4 4v12H6V4Zm8 0v5h5M9 13h6M9 16h4', keywords: ['document'] },
43
+ { label: 'Folder', group: 'Files', path: 'M4 6h6l2 2h8v10H4V6Z', keywords: ['directory'] },
44
+ { label: 'Image', group: 'Files', path: 'M5 6h14v12H5V6Zm3 9 3-3 2 2 2-3 3 4M8 9h.01', keywords: ['media'] },
45
+ { label: 'Code', group: 'Files', path: 'M8 9l-4 3 4 3M16 9l4 3-4 3M14 5l-4 14', keywords: ['developer'] },
46
+ { label: 'Database', group: 'Data', path: 'M5 7c0-2 14-2 14 0v10c0 2-14 2-14 0V7Zm0 0c0 2 14 2 14 0M5 12c0 2 14 2 14 0', keywords: ['storage'] },
47
+ { label: 'Table', group: 'Data', path: 'M5 5h14v14H5V5Zm0 5h14M5 14h14M10 5v14M15 5v14', keywords: ['grid'] },
48
+ { label: 'Chart bar', group: 'Data', path: 'M5 19V9M12 19V5M19 19v-7M4 19h16', keywords: ['analytics'] },
49
+ { label: 'Chart line', group: 'Data', path: 'M4 17l5-5 4 4 7-9M4 20h16', keywords: ['growth'] },
50
+ { label: 'Card', group: 'Interface', path: 'M5 7h14v10H5V7Zm3 3h8M8 14h5', keywords: ['panel'] },
51
+ { label: 'Dashboard', group: 'Interface', path: 'M4 5h7v7H4V5Zm9 0h7v5h-7V5ZM4 14h7v5H4v-5Zm9-2h7v7h-7v-7Z', keywords: ['layout'] },
52
+ { label: 'Mobile', group: 'Interface', path: 'M8 3h8a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Zm3 15h4', keywords: ['phone'] },
53
+ { label: 'Window', group: 'Interface', path: 'M4 5h16v14H4V5Zm0 4h16M8 7h.01M11 7h.01', keywords: ['browser'] },
54
+ { label: 'Globe', group: 'Places', path: 'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM3 12h18M12 3c2 2.5 3 5.5 3 9s-1 6.5-3 9M12 3c-2 2.5-3 5.5-3 9s1 6.5 3 9', keywords: ['world', 'web'] },
55
+ { label: 'Map pin', group: 'Places', path: 'M12 21s7-5.5 7-11a7 7 0 1 0-14 0c0 5.5 7 11 7 11Zm0-8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z', keywords: ['location'] },
56
+ { label: 'Star', group: 'Objects', path: 'M12 3l2.7 5.5 6.1.9-4.4 4.3 1 6-5.4-2.9-5.4 2.9 1-6-4.4-4.3 6.1-.9L12 3Z', keywords: ['favorite'] },
57
+ { label: 'Heart', group: 'Objects', path: 'M12 20s-8-4.8-8-11a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 9c0 6.2-8 11-8 11Z', keywords: ['love'] },
58
+ { label: 'Sparkles', group: 'Objects', path: 'M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Zm6 9 .9 2.6 2.6.9-2.6.9L18 20l-.9-2.6-2.6-.9 2.6-.9L18 12Z', keywords: ['magic', 'ai'] },
59
+ { label: 'Rocket', group: 'Objects', path: 'M5 19c1.5-4 4-6.5 8-8l2-6 4 4-6 2c-1.5 4-4 6.5-8 8Zm4-4-4 4M14 6l4 4', keywords: ['launch'] },
60
+ { label: 'Tag', group: 'Commerce', path: 'M4 12 12 4h7v7l-8 8-7-7Zm11-5h.01', keywords: ['label'] },
61
+ { label: 'Credit card', group: 'Commerce', path: 'M4 7h16v10H4V7Zm0 3h16M7 14h4', keywords: ['payment'] },
62
+ { label: 'Receipt', group: 'Commerce', path: 'M7 4h10v16l-2-1-2 1-2-1-2 1-2-1-2 1V4Zm3 5h4M10 13h4M10 17h2', keywords: ['invoice'] },
63
+ ];
64
+
65
+ export const emojiSelectorItems = [
66
+ { emoji: '😀', label: 'Grinning face', group: 'Smileys', keywords: ['happy', 'smile'] },
67
+ { emoji: '🙂', label: 'Slightly smiling face', group: 'Smileys', keywords: ['calm', 'smile'] },
68
+ { emoji: '😍', label: 'Heart eyes', group: 'Smileys', keywords: ['love', 'delight'] },
69
+ { emoji: '🤔', label: 'Thinking face', group: 'Smileys', keywords: ['question', 'consider'] },
70
+ { emoji: '😎', label: 'Cool face', group: 'Smileys', keywords: ['confidence', 'sun'] },
71
+ { emoji: '🙌', label: 'Raised hands', group: 'Gestures', keywords: ['celebrate', 'success'] },
72
+ { emoji: '👏', label: 'Clapping hands', group: 'Gestures', keywords: ['applause', 'approval'] },
73
+ { emoji: '👍', label: 'Thumbs up', group: 'Gestures', keywords: ['approve', 'yes'] },
74
+ { emoji: '👎', label: 'Thumbs down', group: 'Gestures', keywords: ['reject', 'no'] },
75
+ { emoji: '👋', label: 'Waving hand', group: 'Gestures', keywords: ['hello', 'goodbye'] },
76
+ { emoji: '🚀', label: 'Rocket', group: 'Objects', keywords: ['launch', 'ship'] },
77
+ { emoji: '✨', label: 'Sparkles', group: 'Objects', keywords: ['magic', 'polish'] },
78
+ { emoji: '🔥', label: 'Fire', group: 'Objects', keywords: ['hot', 'urgent'] },
79
+ { emoji: '💡', label: 'Light bulb', group: 'Objects', keywords: ['idea', 'insight'] },
80
+ { emoji: '📌', label: 'Pushpin', group: 'Objects', keywords: ['pin', 'important'] },
81
+ { emoji: '📎', label: 'Paperclip', group: 'Objects', keywords: ['attach', 'file'] },
82
+ { emoji: '🔒', label: 'Locked', group: 'Objects', keywords: ['security', 'private'] },
83
+ { emoji: '🔔', label: 'Bell', group: 'Objects', keywords: ['notification', 'alert'] },
84
+ { emoji: '✅', label: 'Check mark button', group: 'Status', keywords: ['done', 'complete'] },
85
+ { emoji: '⚠️', label: 'Warning', group: 'Status', keywords: ['caution', 'risk'] },
86
+ { emoji: '❌', label: 'Cross mark', group: 'Status', keywords: ['error', 'failed'] },
87
+ { emoji: '🟢', label: 'Green circle', group: 'Status', keywords: ['online', 'healthy'] },
88
+ { emoji: '🟡', label: 'Yellow circle', group: 'Status', keywords: ['pending', 'warning'] },
89
+ { emoji: '🔴', label: 'Red circle', group: 'Status', keywords: ['offline', 'blocked'] },
90
+ { emoji: '📈', label: 'Chart increasing', group: 'Work', keywords: ['growth', 'analytics'] },
91
+ { emoji: '📉', label: 'Chart decreasing', group: 'Work', keywords: ['decline', 'metrics'] },
92
+ { emoji: '🧾', label: 'Receipt', group: 'Work', keywords: ['billing', 'invoice'] },
93
+ { emoji: '📅', label: 'Calendar', group: 'Work', keywords: ['date', 'schedule'] },
94
+ { emoji: '💬', label: 'Speech balloon', group: 'Work', keywords: ['comment', 'message'] },
95
+ { emoji: '🧪', label: 'Test tube', group: 'Work', keywords: ['experiment', 'lab'] },
96
+ { emoji: '🎯', label: 'Direct hit', group: 'Work', keywords: ['target', 'goal'] },
97
+ { emoji: '🏷️', label: 'Label', group: 'Work', keywords: ['tag', 'price'] },
98
+ ];
99
+
100
+ const objectValueIds = new WeakMap();
101
+ let objectValueId = 0;
102
+
103
+ /**
104
+ * Build the selectable item list for the requested built-in kind and custom rows.
105
+ *
106
+ * @param {Object} options
107
+ * @param {string} [options.kind] Built-in collection to include.
108
+ * @param {Array<unknown>} [options.items] Custom icon, emoji, or symbol rows.
109
+ * @param {boolean} [options.includeBuiltIns] Whether built-in rows should be included.
110
+ * @returns {Array<Object>}
111
+ */
112
+ export function createIconSelectorItems(options = {}) {
113
+ const kind = options.kind || 'icons';
114
+ const includeBuiltIns = options.includeBuiltIns !== false;
115
+ const builtIns = includeBuiltIns ? builtInItemsForKind(kind) : [];
116
+ const customItems = (options.items || [])
117
+ .map((item, index) => normalizeIconSelectorItem(item, index, 'custom'))
118
+ .filter(Boolean);
119
+
120
+ return uniqueIconSelectorItems([...customItems, ...builtIns]);
121
+ }
122
+
123
+ /**
124
+ * Create an item representation for a value that is not present in the option list.
125
+ *
126
+ * @param {unknown} value Current model value.
127
+ * @returns {Object|null}
128
+ */
129
+ export function fallbackIconSelectorItem(value) {
130
+ if (value === true) {
131
+ return {
132
+ key: 'default-icon',
133
+ value,
134
+ optionValue: iconSelectorValueKey(value),
135
+ label: 'Default icon',
136
+ group: 'Current value',
137
+ kind: 'default',
138
+ searchText: 'default icon',
139
+ };
140
+ }
141
+ if (value === false) {
142
+ return {
143
+ key: 'no-icon',
144
+ value,
145
+ optionValue: iconSelectorValueKey(value),
146
+ label: 'No icon',
147
+ group: 'Current value',
148
+ kind: 'default',
149
+ searchText: 'no icon',
150
+ };
151
+ }
152
+ if (value == null || value === '') return null;
153
+ if (isObjectValue(value)) {
154
+ return {
155
+ key: `fallback-${iconSelectorValueKey(value)}`,
156
+ value,
157
+ optionValue: iconSelectorValueKey(value),
158
+ label: labelForComponent(value),
159
+ group: 'Current value',
160
+ kind: 'component',
161
+ component: value,
162
+ searchText: labelForComponent(value).toLowerCase(),
163
+ };
164
+ }
165
+
166
+ const text = String(value);
167
+ const isPath = looksLikeSvgPath(text);
168
+ return {
169
+ key: `fallback-${iconSelectorValueKey(text)}`,
170
+ value: text,
171
+ optionValue: iconSelectorValueKey(text),
172
+ label: isPath ? 'Custom SVG path' : text,
173
+ group: 'Current value',
174
+ kind: isPath ? 'icon' : 'emoji',
175
+ path: isPath ? text : '',
176
+ emoji: isPath ? '' : text,
177
+ searchText: text.toLowerCase(),
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Build a stable key for comparing selector values.
183
+ *
184
+ * @param {unknown} value Model or option value.
185
+ * @returns {string}
186
+ */
187
+ export function iconSelectorValueKey(value) {
188
+ if (isObjectValue(value)) {
189
+ if (!objectValueIds.has(value)) objectValueIds.set(value, `object:${objectValueId += 1}`);
190
+ return objectValueIds.get(value);
191
+ }
192
+ return `${typeof value}:${String(value ?? '')}`;
193
+ }
194
+
195
+ /**
196
+ * Check whether two selector values represent the same option.
197
+ *
198
+ * @param {unknown} first First value to compare.
199
+ * @param {unknown} second Second value to compare.
200
+ * @returns {boolean}
201
+ */
202
+ export function iconSelectorValuesMatch(first, second) {
203
+ return first === second || iconSelectorValueKey(first) === iconSelectorValueKey(second);
204
+ }
205
+
206
+ /**
207
+ * Build a stable key for rendering selector option rows.
208
+ *
209
+ * @param {Object} item Normalized selector item.
210
+ * @returns {string}
211
+ */
212
+ export function iconSelectorItemKey(item) {
213
+ return item?.key || iconSelectorValueKey(item?.value);
214
+ }
215
+
216
+ /**
217
+ * Build the string value used by the underlying DOM combobox.
218
+ *
219
+ * @param {Object|null|undefined} item Normalized selector item.
220
+ * @returns {string}
221
+ */
222
+ export function iconSelectorOptionValue(item) {
223
+ return item?.optionValue || iconSelectorValueKey(item?.value);
224
+ }
225
+
226
+ /**
227
+ * Resolve built-in icon and emoji collections for a selector kind.
228
+ *
229
+ * @param {string} kind Requested collection.
230
+ * @returns {Array<Object>}
231
+ */
232
+ function builtInItemsForKind(kind) {
233
+ if (kind === 'custom') return [];
234
+ if (kind === 'emoji') return emojiSelectorItems.map(normalizeEmojiItem).filter(Boolean);
235
+ if (kind === 'mixed') {
236
+ return [
237
+ ...builtInIconSelectorItems.map(normalizeBuiltInIcon).filter(Boolean),
238
+ ...emojiSelectorItems.map(normalizeEmojiItem).filter(Boolean),
239
+ ];
240
+ }
241
+ return builtInIconSelectorItems.map(normalizeBuiltInIcon).filter(Boolean);
242
+ }
243
+
244
+ /**
245
+ * Convert a built-in DOM Studio icon into a selector item.
246
+ *
247
+ * @param {Object} icon Built-in icon row.
248
+ * @returns {Object|null}
249
+ */
250
+ function normalizeBuiltInIcon(icon, index) {
251
+ if (!icon?.path) return null;
252
+ return normalizeIconSelectorItem({
253
+ value: icon.path,
254
+ label: icon.label || icon.name,
255
+ group: icon.group,
256
+ path: icon.path,
257
+ viewBox: icon.viewBox || '0 0 24 24',
258
+ keywords: icon.keywords || [],
259
+ }, index, 'icons');
260
+ }
261
+
262
+ /**
263
+ * Convert a built-in emoji record into a selector item.
264
+ *
265
+ * @param {Object} item Emoji row.
266
+ * @returns {Object|null}
267
+ */
268
+ function normalizeEmojiItem(item, index) {
269
+ return normalizeIconSelectorItem({
270
+ value: item.emoji,
271
+ label: item.label,
272
+ group: item.group,
273
+ emoji: item.emoji,
274
+ keywords: item.keywords,
275
+ }, index, 'emoji');
276
+ }
277
+
278
+ /**
279
+ * Normalize custom and built-in selector records into one rendering shape.
280
+ *
281
+ * @param {unknown} rawItem User-supplied item value.
282
+ * @param {number} index Item index in its source collection.
283
+ * @param {string} source Source collection name.
284
+ * @returns {Object|null}
285
+ */
286
+ function normalizeIconSelectorItem(rawItem, index, source) {
287
+ if (rawItem == null) return null;
288
+ if (typeof rawItem === 'string' || typeof rawItem === 'number' || typeof rawItem === 'boolean') {
289
+ return normalizePrimitiveItem(rawItem, index, source);
290
+ }
291
+ if (typeof rawItem === 'function') {
292
+ return normalizeIconSelectorItem({
293
+ value: rawItem,
294
+ component: rawItem,
295
+ label: labelForComponent(rawItem),
296
+ kind: 'component',
297
+ }, index, source);
298
+ }
299
+ if (typeof rawItem !== 'object') return null;
300
+
301
+ const path = String(rawItem.path || '').trim();
302
+ const emoji = String(rawItem.emoji || '').trim();
303
+ const rawComponent = rawItem.component || (looksLikeComponentRecord(rawItem) ? rawItem : null);
304
+ const value = rawItem.value ?? rawComponent ?? (path || emoji || rawItem.label);
305
+ if (value == null || value === '') return null;
306
+
307
+ const kind = rawItem.kind || (rawComponent ? 'component' : path ? 'icon' : emoji ? 'emoji' : source);
308
+ const component = rawComponent || (isObjectValue(value) ? value : null);
309
+ const label = rawItem.label || labelForValue(value, kind);
310
+ const item = {
311
+ ...rawItem,
312
+ key: rawItem.key || `${source}-${index}-${iconSelectorValueKey(value)}`,
313
+ value,
314
+ optionValue: rawItem.optionValue || iconSelectorValueKey(value),
315
+ label,
316
+ group: rawItem.group || groupForKind(kind),
317
+ kind: component && !path && !emoji ? 'component' : kind,
318
+ path,
319
+ emoji,
320
+ component,
321
+ viewBox: rawItem.viewBox || '0 0 24 24',
322
+ keywords: normalizeKeywords(rawItem.keywords),
323
+ };
324
+
325
+ return {
326
+ ...item,
327
+ searchText: searchTextForItem(item),
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Normalize a primitive selector value into a renderable item.
333
+ *
334
+ * @param {string|number|boolean} value Primitive item value.
335
+ * @param {number} index Source index.
336
+ * @param {string} source Source collection name.
337
+ * @returns {Object|null}
338
+ */
339
+ function normalizePrimitiveItem(value, index, source) {
340
+ if (value === '') return null;
341
+ const text = String(value);
342
+ const isPath = looksLikeSvgPath(text);
343
+ const kind = isPath ? 'icon' : 'emoji';
344
+ const item = {
345
+ key: `${source}-${index}-${iconSelectorValueKey(value)}`,
346
+ value,
347
+ optionValue: iconSelectorValueKey(value),
348
+ label: labelForValue(value, kind),
349
+ group: groupForKind(kind),
350
+ kind,
351
+ path: isPath ? text : '',
352
+ emoji: isPath ? '' : text,
353
+ viewBox: '0 0 24 24',
354
+ keywords: [],
355
+ };
356
+
357
+ return {
358
+ ...item,
359
+ searchText: searchTextForItem(item),
360
+ };
361
+ }
362
+
363
+ /**
364
+ * Remove duplicate selector records while preserving the first useful label.
365
+ *
366
+ * @param {Array<Object>} items Normalized selector items.
367
+ * @returns {Array<Object>}
368
+ */
369
+ function uniqueIconSelectorItems(items) {
370
+ const seen = new Set();
371
+ const out = [];
372
+ for (const item of items) {
373
+ const key = iconSelectorValueKey(item.value);
374
+ if (seen.has(key)) continue;
375
+ seen.add(key);
376
+ out.push(item);
377
+ }
378
+ return out;
379
+ }
380
+
381
+ /**
382
+ * Build the fallback label for a primitive value.
383
+ *
384
+ * @param {unknown} value Item value.
385
+ * @param {string} kind Normalized item kind.
386
+ * @returns {string}
387
+ */
388
+ function labelForValue(value, kind) {
389
+ if (kind === 'icon') return 'Custom icon';
390
+ if (kind === 'component') return labelForComponent(value);
391
+ return String(value);
392
+ }
393
+
394
+ /**
395
+ * Build a readable label for a Vue icon component.
396
+ *
397
+ * @param {unknown} component Component value.
398
+ * @returns {string}
399
+ */
400
+ function labelForComponent(component) {
401
+ return String(component?.displayName || component?.name || component?.__name || 'Custom component');
402
+ }
403
+
404
+ /**
405
+ * Build the fallback group label for an item kind.
406
+ *
407
+ * @param {string} kind Normalized item kind.
408
+ * @returns {string}
409
+ */
410
+ function groupForKind(kind) {
411
+ if (kind === 'icon') return 'Icons';
412
+ if (kind === 'emoji') return 'Emoji';
413
+ if (kind === 'component') return 'Components';
414
+ return 'Custom';
415
+ }
416
+
417
+ /**
418
+ * Normalize keyword input from strings or arrays into an array.
419
+ *
420
+ * @param {unknown} keywords Raw keyword value.
421
+ * @returns {Array<string>}
422
+ */
423
+ function normalizeKeywords(keywords) {
424
+ if (Array.isArray(keywords)) return keywords.map(String).filter(Boolean);
425
+ if (typeof keywords === 'string') {
426
+ return keywords
427
+ .split(/[,\s]+/)
428
+ .map((keyword) => keyword.trim())
429
+ .filter(Boolean);
430
+ }
431
+ return [];
432
+ }
433
+
434
+ /**
435
+ * Build normalized text used by search and the headless combobox filter.
436
+ *
437
+ * @param {Object} item Normalized selector item.
438
+ * @returns {string}
439
+ */
440
+ function searchTextForItem(item) {
441
+ return [
442
+ item.label,
443
+ searchableValueText(item.value),
444
+ item.group,
445
+ item.kind,
446
+ ...(item.keywords || []),
447
+ ].filter(Boolean).join(' ').toLowerCase();
448
+ }
449
+
450
+ /**
451
+ * Convert primitive values into searchable text without stringifying components.
452
+ *
453
+ * @param {unknown} value Item value.
454
+ * @returns {string}
455
+ */
456
+ function searchableValueText(value) {
457
+ if (isObjectValue(value)) return '';
458
+ return String(value ?? '');
459
+ }
460
+
461
+ /**
462
+ * Check whether a value looks like SVG path data.
463
+ *
464
+ * @param {string} value Candidate path value.
465
+ * @returns {boolean}
466
+ */
467
+ function looksLikeSvgPath(value) {
468
+ const text = String(value || '').trim();
469
+ return /^[Mm][MmLlHhVvCcSsQqTtAaZz0-9.\-+,\s]+$/.test(text) && /\d/.test(text);
470
+ }
471
+
472
+ /**
473
+ * Check whether a value should use object identity for selector matching.
474
+ *
475
+ * @param {unknown} value Candidate value.
476
+ * @returns {boolean}
477
+ */
478
+ function isObjectValue(value) {
479
+ return (typeof value === 'object' && value !== null) || typeof value === 'function';
480
+ }
481
+
482
+ /**
483
+ * Check whether an object looks like a Vue component rather than an option row.
484
+ *
485
+ * @param {unknown} value Candidate object.
486
+ * @returns {boolean}
487
+ */
488
+ function looksLikeComponentRecord(value) {
489
+ return Boolean(value && typeof value === 'object' && (value.render || value.setup || value.__name));
490
+ }