@happyvertical/smrt-svelte 0.55.6 → 0.55.8

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 (318) hide show
  1. package/AGENTS.md +9 -31
  2. package/README.md +195 -5
  3. package/agents/browser-ai.md +47 -0
  4. package/agents/command-palette.md +85 -0
  5. package/agents/import-export.md +62 -0
  6. package/agents/overview-surfaces.md +255 -0
  7. package/agents/screens.md +57 -0
  8. package/agents/workspace.md +202 -5
  9. package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -1
  10. package/dist/browser-ai/adapters/stt/dictation-source.js +8 -2
  11. package/dist/browser-ai/adapters/stt/factory.d.ts +6 -3
  12. package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -1
  13. package/dist/browser-ai/adapters/stt/factory.js +19 -9
  14. package/dist/browser-ai/adapters/stt/index.d.ts +3 -1
  15. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
  16. package/dist/browser-ai/adapters/stt/index.js +3 -1
  17. package/dist/browser-ai/adapters/stt/local-speech-model.d.ts +111 -0
  18. package/dist/browser-ai/adapters/stt/local-speech-model.d.ts.map +1 -0
  19. package/dist/browser-ai/adapters/stt/local-speech-model.js +377 -0
  20. package/dist/browser-ai/adapters/stt/local-speech.d.ts +82 -0
  21. package/dist/browser-ai/adapters/stt/local-speech.d.ts.map +1 -0
  22. package/dist/browser-ai/adapters/stt/local-speech.js +317 -0
  23. package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts +2 -0
  24. package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts.map +1 -0
  25. package/dist/browser-ai/adapters/stt/local-speech.worker.js +32 -0
  26. package/dist/browser-ai/adapters/stt/transformers-env.d.ts +8 -0
  27. package/dist/browser-ai/adapters/stt/transformers-env.d.ts.map +1 -0
  28. package/dist/browser-ai/adapters/stt/transformers-env.js +17 -0
  29. package/dist/browser-ai/adapters/stt/types.d.ts +54 -9
  30. package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -1
  31. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +5 -0
  32. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -1
  33. package/dist/browser-ai/adapters/tts/browser-synthesis.js +83 -55
  34. package/dist/browser-ai/capabilities/speech-probe.d.ts +29 -0
  35. package/dist/browser-ai/capabilities/speech-probe.d.ts.map +1 -0
  36. package/dist/browser-ai/capabilities/speech-probe.js +19 -0
  37. package/dist/browser-ai/core/errors.d.ts +9 -0
  38. package/dist/browser-ai/core/errors.d.ts.map +1 -1
  39. package/dist/browser-ai/core/errors.js +12 -0
  40. package/dist/browser-ai/index.d.ts +1 -0
  41. package/dist/browser-ai/index.d.ts.map +1 -1
  42. package/dist/browser-ai/index.js +1 -0
  43. package/dist/components/app/AppShell.svelte +587 -33
  44. package/dist/components/app/AppShell.svelte.d.ts +75 -4
  45. package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
  46. package/dist/components/app/DockToggles.svelte +0 -1
  47. package/dist/components/app/dock-toggle.d.ts +7 -1
  48. package/dist/components/app/dock-toggle.d.ts.map +1 -1
  49. package/dist/components/app/index.d.ts +2 -1
  50. package/dist/components/app/index.d.ts.map +1 -1
  51. package/dist/components/app/slot-item.d.ts +21 -0
  52. package/dist/components/app/slot-item.d.ts.map +1 -0
  53. package/dist/components/app/slot-item.js +1 -0
  54. package/dist/components/command-palette/CommandPalette.svelte +75 -0
  55. package/dist/components/command-palette/CommandPalette.svelte.d.ts +22 -0
  56. package/dist/components/command-palette/CommandPalette.svelte.d.ts.map +1 -0
  57. package/dist/components/command-palette/CommandPaletteDialog.svelte +406 -0
  58. package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts +18 -0
  59. package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts.map +1 -0
  60. package/dist/components/command-palette/CommandPaletteTrigger.svelte +91 -0
  61. package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts +17 -0
  62. package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts.map +1 -0
  63. package/dist/components/command-palette/context.d.ts +18 -0
  64. package/dist/components/command-palette/context.d.ts.map +1 -0
  65. package/dist/components/command-palette/context.js +38 -0
  66. package/dist/components/command-palette/controller.svelte.d.ts +70 -0
  67. package/dist/components/command-palette/controller.svelte.d.ts.map +1 -0
  68. package/dist/components/command-palette/controller.svelte.js +379 -0
  69. package/dist/components/command-palette/hotkeys.d.ts +30 -0
  70. package/dist/components/command-palette/hotkeys.d.ts.map +1 -0
  71. package/dist/components/command-palette/hotkeys.js +161 -0
  72. package/dist/components/command-palette/index.d.ts +19 -0
  73. package/dist/components/command-palette/index.d.ts.map +1 -0
  74. package/dist/components/command-palette/index.js +17 -0
  75. package/dist/components/command-palette/match.d.ts +38 -0
  76. package/dist/components/command-palette/match.d.ts.map +1 -0
  77. package/dist/components/command-palette/match.js +172 -0
  78. package/dist/components/command-palette/providers.d.ts +159 -0
  79. package/dist/components/command-palette/providers.d.ts.map +1 -0
  80. package/dist/components/command-palette/providers.js +345 -0
  81. package/dist/components/command-palette/rank.d.ts +20 -0
  82. package/dist/components/command-palette/rank.d.ts.map +1 -0
  83. package/dist/components/command-palette/rank.js +74 -0
  84. package/dist/components/command-palette/types.d.ts +119 -0
  85. package/dist/components/command-palette/types.d.ts.map +1 -0
  86. package/dist/components/command-palette/types.js +10 -0
  87. package/dist/components/command-palette/use-provider.svelte.d.ts +14 -0
  88. package/dist/components/command-palette/use-provider.svelte.d.ts.map +1 -0
  89. package/dist/components/command-palette/use-provider.svelte.js +23 -0
  90. package/dist/components/forms/AddressInput.svelte +2 -15
  91. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
  92. package/dist/components/forms/DateRangeInput.svelte +2 -15
  93. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
  94. package/dist/components/forms/DateTimeInput.svelte +8 -15
  95. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
  96. package/dist/components/forms/MeasurementInput.svelte +2 -15
  97. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
  98. package/dist/components/forms/MoneyInput.svelte +8 -25
  99. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
  100. package/dist/components/forms/NumberInput.svelte +7 -15
  101. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
  102. package/dist/components/forms/PhoneInput.svelte +7 -15
  103. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
  104. package/dist/components/forms/SelectInput.svelte +34 -40
  105. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
  106. package/dist/components/forms/TextInput.svelte +20 -37
  107. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
  108. package/dist/components/forms/TextareaInput.svelte +19 -35
  109. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
  110. package/dist/components/import-export/ImportExport.svelte +678 -0
  111. package/dist/components/import-export/ImportExport.svelte.d.ts +5 -0
  112. package/dist/components/import-export/ImportExport.svelte.d.ts.map +1 -0
  113. package/dist/components/import-export/collection-adapter.d.ts +35 -0
  114. package/dist/components/import-export/collection-adapter.d.ts.map +1 -0
  115. package/dist/components/import-export/collection-adapter.js +42 -0
  116. package/dist/components/import-export/csv.d.ts +60 -0
  117. package/dist/components/import-export/csv.d.ts.map +1 -0
  118. package/dist/components/import-export/csv.js +192 -0
  119. package/dist/components/import-export/download.d.ts +4 -0
  120. package/dist/components/import-export/download.d.ts.map +1 -0
  121. package/dist/components/import-export/download.js +17 -0
  122. package/dist/components/import-export/export.d.ts +25 -0
  123. package/dist/components/import-export/export.d.ts.map +1 -0
  124. package/dist/components/import-export/export.js +68 -0
  125. package/dist/components/import-export/fields.d.ts +48 -0
  126. package/dist/components/import-export/fields.d.ts.map +1 -0
  127. package/dist/components/import-export/fields.js +103 -0
  128. package/dist/components/import-export/index.d.ts +12 -0
  129. package/dist/components/import-export/index.d.ts.map +1 -0
  130. package/dist/components/import-export/index.js +9 -0
  131. package/dist/components/import-export/mapping.d.ts +12 -0
  132. package/dist/components/import-export/mapping.d.ts.map +1 -0
  133. package/dist/components/import-export/mapping.js +38 -0
  134. package/dist/components/import-export/props.d.ts +34 -0
  135. package/dist/components/import-export/props.d.ts.map +1 -0
  136. package/dist/components/import-export/props.js +1 -0
  137. package/dist/components/import-export/report.d.ts +13 -0
  138. package/dist/components/import-export/report.d.ts.map +1 -0
  139. package/dist/components/import-export/report.js +50 -0
  140. package/dist/components/import-export/runner.d.ts +17 -0
  141. package/dist/components/import-export/runner.d.ts.map +1 -0
  142. package/dist/components/import-export/runner.js +39 -0
  143. package/dist/components/import-export/types.d.ts +100 -0
  144. package/dist/components/import-export/types.d.ts.map +1 -0
  145. package/dist/components/import-export/types.js +6 -0
  146. package/dist/components/import-export/validate.d.ts +39 -0
  147. package/dist/components/import-export/validate.d.ts.map +1 -0
  148. package/dist/components/import-export/validate.js +268 -0
  149. package/dist/components/overview/OverviewGrid.svelte +579 -0
  150. package/dist/components/overview/OverviewGrid.svelte.d.ts +5 -0
  151. package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -0
  152. package/dist/components/overview/WidgetOptionsForm.svelte +193 -0
  153. package/dist/components/overview/WidgetOptionsForm.svelte.d.ts +5 -0
  154. package/dist/components/overview/WidgetOptionsForm.svelte.d.ts.map +1 -0
  155. package/dist/components/overview/controller.svelte.d.ts +92 -0
  156. package/dist/components/overview/controller.svelte.d.ts.map +1 -0
  157. package/dist/components/overview/controller.svelte.js +336 -0
  158. package/dist/components/overview/format.d.ts +13 -0
  159. package/dist/components/overview/format.d.ts.map +1 -0
  160. package/dist/components/overview/format.js +45 -0
  161. package/dist/components/overview/grid-types.d.ts +52 -0
  162. package/dist/components/overview/grid-types.d.ts.map +1 -0
  163. package/dist/components/overview/grid-types.js +1 -0
  164. package/dist/components/overview/index.d.ts +24 -0
  165. package/dist/components/overview/index.d.ts.map +1 -0
  166. package/dist/components/overview/index.js +22 -0
  167. package/dist/components/overview/load.d.ts +30 -0
  168. package/dist/components/overview/load.d.ts.map +1 -0
  169. package/dist/components/overview/load.js +92 -0
  170. package/dist/components/overview/markdown.d.ts +53 -0
  171. package/dist/components/overview/markdown.d.ts.map +1 -0
  172. package/dist/components/overview/markdown.js +203 -0
  173. package/dist/components/overview/model.d.ts +100 -0
  174. package/dist/components/overview/model.d.ts.map +1 -0
  175. package/dist/components/overview/model.js +460 -0
  176. package/dist/components/overview/recipe-widgets.d.ts +77 -0
  177. package/dist/components/overview/recipe-widgets.d.ts.map +1 -0
  178. package/dist/components/overview/recipe-widgets.js +151 -0
  179. package/dist/components/overview/registry.d.ts +45 -0
  180. package/dist/components/overview/registry.d.ts.map +1 -0
  181. package/dist/components/overview/registry.js +112 -0
  182. package/dist/components/overview/resize.d.ts +25 -0
  183. package/dist/components/overview/resize.d.ts.map +1 -0
  184. package/dist/components/overview/resize.js +41 -0
  185. package/dist/components/overview/schema.d.ts +34 -0
  186. package/dist/components/overview/schema.d.ts.map +1 -0
  187. package/dist/components/overview/schema.js +180 -0
  188. package/dist/components/overview/server.d.ts +14 -0
  189. package/dist/components/overview/server.d.ts.map +1 -0
  190. package/dist/components/overview/server.js +13 -0
  191. package/dist/components/overview/types.d.ts +235 -0
  192. package/dist/components/overview/types.d.ts.map +1 -0
  193. package/dist/components/overview/types.js +17 -0
  194. package/dist/components/overview/widgets/ChartWidget.svelte +111 -0
  195. package/dist/components/overview/widgets/ChartWidget.svelte.d.ts +6 -0
  196. package/dist/components/overview/widgets/ChartWidget.svelte.d.ts.map +1 -0
  197. package/dist/components/overview/widgets/MetricWidget.svelte +86 -0
  198. package/dist/components/overview/widgets/MetricWidget.svelte.d.ts +6 -0
  199. package/dist/components/overview/widgets/MetricWidget.svelte.d.ts.map +1 -0
  200. package/dist/components/overview/widgets/NoteWidget.svelte +61 -0
  201. package/dist/components/overview/widgets/NoteWidget.svelte.d.ts +5 -0
  202. package/dist/components/overview/widgets/NoteWidget.svelte.d.ts.map +1 -0
  203. package/dist/components/overview/widgets/RecordListWidget.svelte +91 -0
  204. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts +6 -0
  205. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -0
  206. package/dist/components/overview/widgets/ShortcutsWidget.svelte +80 -0
  207. package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts +6 -0
  208. package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts.map +1 -0
  209. package/dist/components/overview/widgets/core.d.ts +44 -0
  210. package/dist/components/overview/widgets/core.d.ts.map +1 -0
  211. package/dist/components/overview/widgets/core.js +222 -0
  212. package/dist/components/overview/widgets/data.d.ts +76 -0
  213. package/dist/components/overview/widgets/data.d.ts.map +1 -0
  214. package/dist/components/overview/widgets/data.js +40 -0
  215. package/dist/components/screens/DetailScreen.svelte +220 -0
  216. package/dist/components/screens/DetailScreen.svelte.d.ts +5 -0
  217. package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -0
  218. package/dist/components/screens/EditForm.svelte +313 -0
  219. package/dist/components/screens/EditForm.svelte.d.ts +5 -0
  220. package/dist/components/screens/EditForm.svelte.d.ts.map +1 -0
  221. package/dist/components/screens/ListScreen.svelte +191 -0
  222. package/dist/components/screens/ListScreen.svelte.d.ts +5 -0
  223. package/dist/components/screens/ListScreen.svelte.d.ts.map +1 -0
  224. package/dist/components/screens/RecipeScreens.svelte +237 -0
  225. package/dist/components/screens/RecipeScreens.svelte.d.ts +5 -0
  226. package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -0
  227. package/dist/components/screens/fields.d.ts +54 -0
  228. package/dist/components/screens/fields.d.ts.map +1 -0
  229. package/dist/components/screens/fields.js +229 -0
  230. package/dist/components/screens/index.d.ts +8 -0
  231. package/dist/components/screens/index.d.ts.map +1 -0
  232. package/dist/components/screens/index.js +6 -0
  233. package/dist/components/screens/types.d.ts +215 -0
  234. package/dist/components/screens/types.d.ts.map +1 -0
  235. package/dist/components/screens/types.js +1 -0
  236. package/dist/components/screens/values.d.ts +62 -0
  237. package/dist/components/screens/values.d.ts.map +1 -0
  238. package/dist/components/screens/values.js +289 -0
  239. package/dist/components/sortable/Sortable.svelte +6 -33
  240. package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -1
  241. package/dist/components/sortable/announce.d.ts +5 -0
  242. package/dist/components/sortable/announce.d.ts.map +1 -0
  243. package/dist/components/sortable/announce.js +33 -0
  244. package/dist/components/sortable/controller.svelte.d.ts +10 -0
  245. package/dist/components/sortable/controller.svelte.d.ts.map +1 -1
  246. package/dist/components/sortable/controller.svelte.js +29 -7
  247. package/dist/components/workspace/admin-shell/AdminShell.svelte +424 -58
  248. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +27 -2
  249. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  250. package/dist/components/workspace/admin-shell/ShellBrand.svelte +86 -0
  251. package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts +18 -0
  252. package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts.map +1 -0
  253. package/dist/components/workspace/admin-shell/ShellIconButton.svelte +77 -0
  254. package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts +26 -0
  255. package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts.map +1 -0
  256. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +88 -16
  257. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -1
  258. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte +531 -0
  259. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts +26 -0
  260. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts.map +1 -0
  261. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte +57 -0
  262. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts +22 -0
  263. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts.map +1 -0
  264. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte +290 -0
  265. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts +32 -0
  266. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts.map +1 -0
  267. package/dist/components/workspace/admin-shell/TenantNav.svelte +67 -2
  268. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -0
  269. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  270. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +67 -0
  271. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -1
  272. package/dist/components/workspace/admin-shell/layout-controller.svelte.js +129 -1
  273. package/dist/components/workspace/admin-shell/layout.d.ts +122 -0
  274. package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -1
  275. package/dist/components/workspace/admin-shell/layout.js +410 -10
  276. package/dist/components/workspace/admin-shell/settings.js +4 -4
  277. package/dist/components/workspace/admin-shell/shell-icons.d.ts +45 -0
  278. package/dist/components/workspace/admin-shell/shell-icons.d.ts.map +1 -0
  279. package/dist/components/workspace/admin-shell/shell-icons.js +66 -0
  280. package/dist/components/workspace/admin-shell/slot-labels.d.ts +33 -0
  281. package/dist/components/workspace/admin-shell/slot-labels.d.ts.map +1 -0
  282. package/dist/components/workspace/admin-shell/slot-labels.js +33 -0
  283. package/dist/components/workspace/admin-shell/slots.d.ts +50 -0
  284. package/dist/components/workspace/admin-shell/slots.d.ts.map +1 -0
  285. package/dist/components/workspace/admin-shell/slots.js +77 -0
  286. package/dist/components/workspace/admin-shell/state.svelte.d.ts +35 -0
  287. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  288. package/dist/components/workspace/admin-shell/state.svelte.js +111 -0
  289. package/dist/components/workspace/admin-shell/types.d.ts +101 -1
  290. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  291. package/dist/components/workspace/index.d.ts +9 -2
  292. package/dist/components/workspace/index.d.ts.map +1 -1
  293. package/dist/components/workspace/index.js +6 -0
  294. package/dist/components/workspace/manifest-nav.d.ts +20 -0
  295. package/dist/components/workspace/manifest-nav.d.ts.map +1 -1
  296. package/dist/components/workspace/manifest-nav.js +42 -13
  297. package/dist/i18n/server.d.ts +6 -0
  298. package/dist/i18n/server.d.ts.map +1 -1
  299. package/dist/i18n/server.js +6 -0
  300. package/dist/i18n/strings.import-export.d.ts +88 -0
  301. package/dist/i18n/strings.import-export.d.ts.map +1 -0
  302. package/dist/i18n/strings.import-export.js +88 -0
  303. package/dist/i18n/strings.overview.d.ts +100 -0
  304. package/dist/i18n/strings.overview.d.ts.map +1 -0
  305. package/dist/i18n/strings.overview.js +107 -0
  306. package/dist/i18n/strings.palette.d.ts +18 -0
  307. package/dist/i18n/strings.palette.d.ts.map +1 -0
  308. package/dist/i18n/strings.palette.js +18 -0
  309. package/dist/i18n/strings.screens.d.ts +31 -0
  310. package/dist/i18n/strings.screens.d.ts.map +1 -0
  311. package/dist/i18n/strings.screens.js +31 -0
  312. package/dist/i18n/strings.workspace.d.ts +49 -0
  313. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  314. package/dist/i18n/strings.workspace.js +49 -0
  315. package/package.json +59 -19
  316. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +0 -51
  317. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +0 -1
  318. package/dist/browser-ai/adapters/stt/whisper-wasm.js +0 -380
@@ -0,0 +1,531 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The navigation as it looks on the site, made editable in place: every
4
+ * section heading and item has a grip (drag, or Space plus arrow keys), section
5
+ * headings carry overlay icon buttons (show, title, host actions) and open a
6
+ * floating toolbar (rename, title, hide, delete, host actions), and a "New
7
+ * section" button ends the list. Rendered by `AppShell` in place of
8
+ * `TenantNav` while the layout is being edited.
9
+ */
10
+ import { Input } from '@happyvertical/smrt-ui/forms';
11
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
12
+ import { Button } from '@happyvertical/smrt-ui/ui';
13
+ import { type Component, type Snippet, tick, untrack } from 'svelte';
14
+ import { M } from '../../../i18n/strings.workspace.js';
15
+ import Sortable from '../../sortable/Sortable.svelte';
16
+ import type {
17
+ SortableContainer,
18
+ SortableContainerMove,
19
+ SortableItem,
20
+ SortableItemMove,
21
+ } from '../../sortable/types.js';
22
+ import { useShellLayout } from './layout-context.js';
23
+ import type { ShellLayoutController } from './layout-controller.svelte.js';
24
+ import ShellIconButton from './ShellIconButton.svelte';
25
+ import ShellSectionIcon from './ShellSectionIcon.svelte';
26
+ import {
27
+ SHELL_DEFAULT_SECTION_ICON,
28
+ SHELL_SECTION_ICONS,
29
+ } from './shell-icons.js';
30
+ import type { ShellSectionActionsContext } from './types.js';
31
+
32
+ interface Props {
33
+ /** The layout API to edit; defaults to the nearest `AppShell`'s. */
34
+ controller?: ShellLayoutController;
35
+ /** Accessible name for the navigation landmark. */
36
+ 'aria-label'?: string;
37
+ /** Component used to render icon names, as for `TenantNav`. */
38
+ iconComponent?: Component<{ name: string; size?: number }>;
39
+ /** Host icon buttons for each section (overlay and floating toolbar). */
40
+ sectionActions?: Snippet<[ShellSectionActionsContext]>;
41
+ /**
42
+ * `'sections'` edits only the sections (grip, rename, show, delete, icon
43
+ * picker); their entries are edited on the section's own page with
44
+ * `ShellSectionMenu`. Default `'items'`.
45
+ */
46
+ navMode?: 'items' | 'sections';
47
+ }
48
+
49
+ let {
50
+ controller: provided,
51
+ 'aria-label': ariaLabel,
52
+ iconComponent: IconComponent,
53
+ sectionActions,
54
+ navMode = 'items',
55
+ }: Props = $props();
56
+
57
+ const { t } = useI18n();
58
+ const layout = untrack(() => provided ?? useShellLayout());
59
+
60
+ interface NavContainer extends SortableContainer {
61
+ hidden: boolean;
62
+ root: boolean;
63
+ custom: boolean;
64
+ titleVisible: boolean;
65
+ count: number;
66
+ icon: string;
67
+ }
68
+
69
+ const sections = $derived(layout.sections);
70
+ const containers = $derived<NavContainer[]>(
71
+ sections.map((section) => ({
72
+ id: section.id,
73
+ label: section.heading ?? t(M['ui.shell_layout_editor.top_level']),
74
+ fixed: section.group === null,
75
+ root: section.group === null,
76
+ hidden: section.hidden,
77
+ custom: section.custom,
78
+ titleVisible: section.titleVisible,
79
+ count: section.items.length,
80
+ icon: section.icon ?? SHELL_DEFAULT_SECTION_ICON,
81
+ })),
82
+ );
83
+ const items = $derived<SortableItem[]>(
84
+ (navMode === 'sections' ? [] : sections).flatMap((section) =>
85
+ section.items.map((entry) => ({
86
+ id: entry.id,
87
+ containerId: section.id,
88
+ label: entry.label,
89
+ })),
90
+ ),
91
+ );
92
+ const itemDefaults = $derived(
93
+ new Map(
94
+ sections.flatMap((section) =>
95
+ section.items.map((entry) => [entry.id, entry.defaultLabel] as const),
96
+ ),
97
+ ),
98
+ );
99
+ const itemIcons = $derived(
100
+ new Map(
101
+ sections.flatMap((section) =>
102
+ section.items.map((entry) => [entry.id, entry.item.icon] as const),
103
+ ),
104
+ ),
105
+ );
106
+
107
+ let rootEl: HTMLElement | undefined = $state();
108
+
109
+ // One floating toolbar at a time. `pinned` (opened by click/tap) stays until
110
+ // dismissed; an unpinned one (opened by hovering with a fine pointer) closes
111
+ // when the pointer leaves the section.
112
+ let open = $state<{ id: string; pinned: boolean } | null>(null);
113
+
114
+ function canHover(): boolean {
115
+ return (
116
+ typeof window !== 'undefined' &&
117
+ window.matchMedia?.('(hover: hover) and (pointer: fine)').matches === true
118
+ );
119
+ }
120
+
121
+ function sectionEl(id: string): HTMLElement | null {
122
+ return (
123
+ rootEl?.querySelector<HTMLElement>(
124
+ `[data-nav-section="${CSS.escape(id)}"]`,
125
+ ) ?? null
126
+ );
127
+ }
128
+
129
+ function onhover(id: string, event: PointerEvent): void {
130
+ if (event.pointerType === 'touch' || !canHover()) return;
131
+ if (open?.pinned) return;
132
+ open = { id, pinned: false };
133
+ }
134
+
135
+ async function onactivate(id: string): Promise<void> {
136
+ if (open?.id === id && open.pinned) {
137
+ open = null;
138
+ return;
139
+ }
140
+ open = { id, pinned: true };
141
+ await tick();
142
+ const input = sectionEl(id)?.querySelector<HTMLInputElement>(
143
+ 'input[data-section-name]',
144
+ );
145
+ input?.focus();
146
+ input?.select();
147
+ }
148
+
149
+ function onleave(id: string): void {
150
+ if (open?.id === id && !open.pinned) open = null;
151
+ }
152
+
153
+ function onkeydown(id: string, event: KeyboardEvent): void {
154
+ if (event.key !== 'Escape' || open?.id !== id) return;
155
+ event.preventDefault();
156
+ event.stopPropagation();
157
+ // An open icon picker closes first; the toolbar stays.
158
+ if (iconPicker === id) {
159
+ iconPicker = null;
160
+ pickerButton(id)?.focus();
161
+ return;
162
+ }
163
+ open = null;
164
+ sectionEl(id)
165
+ ?.querySelector<HTMLElement>('button[data-section-title]')
166
+ ?.focus();
167
+ }
168
+
169
+ function onfocusout(id: string, event: FocusEvent): void {
170
+ if (open?.id !== id || !open.pinned) return;
171
+ const next = event.relatedTarget;
172
+ if (next instanceof Node && !sectionEl(id)?.contains(next)) open = null;
173
+ }
174
+
175
+ $effect(() => {
176
+ const current = open;
177
+ if (!current?.pinned || typeof document === 'undefined') return;
178
+ const dismiss = (event: PointerEvent) => {
179
+ if (
180
+ event.target instanceof Node &&
181
+ !sectionEl(current.id)?.contains(event.target)
182
+ ) {
183
+ open = null;
184
+ }
185
+ };
186
+ document.addEventListener('pointerdown', dismiss, true);
187
+ return () => document.removeEventListener('pointerdown', dismiss, true);
188
+ });
189
+
190
+ // The section whose icon picker is open (inside its toolbar).
191
+ let iconPicker = $state<string | null>(null);
192
+
193
+ function pickerButton(id: string): HTMLElement | null {
194
+ return (
195
+ sectionEl(id)?.querySelector<HTMLElement>('button[data-section-icon]') ??
196
+ null
197
+ );
198
+ }
199
+
200
+ function chooseIcon(id: string, icon: string): void {
201
+ layout.setSectionIcon(id, icon);
202
+ iconPicker = null;
203
+ pickerButton(id)?.focus();
204
+ }
205
+
206
+ // The picker lives in the toolbar: it closes with it.
207
+ $effect(() => {
208
+ if (iconPicker !== null && open?.id !== iconPicker) iconPicker = null;
209
+ });
210
+
211
+ function humanizeIcon(name: string): string {
212
+ return name.replace(/([A-Z])/g, ' $1').toLowerCase();
213
+ }
214
+
215
+ // The item whose name is being edited in place (one at a time).
216
+ let renamingItem = $state<string | null>(null);
217
+
218
+ async function startRenameItem(id: string): Promise<void> {
219
+ renamingItem = id;
220
+ await tick();
221
+ const input = rootEl?.querySelector<HTMLInputElement>(
222
+ `input[data-item-name="${CSS.escape(id)}"]`,
223
+ );
224
+ input?.focus();
225
+ input?.select();
226
+ }
227
+
228
+ function commitRenameItem(id: string, value: string): void {
229
+ // Blur after Escape or a save lands here too; only the open editor commits.
230
+ if (renamingItem !== id) return;
231
+ renamingItem = null;
232
+ layout.renameItem(id, value);
233
+ }
234
+
235
+ function cancelRenameItem(id: string): void {
236
+ renamingItem = null;
237
+ rootEl
238
+ ?.querySelector<HTMLElement>(`button[data-item-rename="${CSS.escape(id)}"]`)
239
+ ?.focus();
240
+ }
241
+
242
+ function setShown(id: string, shown: boolean): void {
243
+ if (shown) layout.show(id);
244
+ else layout.hide(id);
245
+ }
246
+
247
+ function rename(id: string, input: HTMLInputElement): void {
248
+ layout.renameSection(id, input.value);
249
+ // Blank or rejected input snaps back to the section's real name.
250
+ input.value = containers.find((entry) => entry.id === id)?.label ?? '';
251
+ }
252
+
253
+ async function createSection(): Promise<void> {
254
+ const id = layout.createSection(
255
+ t(M['ui.shell_layout_editor.new_section_label']),
256
+ );
257
+ if (!id) return;
258
+ await tick();
259
+ await onactivate(id);
260
+ }
261
+
262
+ function deleteSection(id: string, label: string, count: number): void {
263
+ if (
264
+ count > 0 &&
265
+ !globalThis.confirm?.(
266
+ t(M['ui.shell_layout_editor.delete_confirm'], { label }),
267
+ )
268
+ ) {
269
+ return;
270
+ }
271
+ open = null;
272
+ layout.deleteSection(id);
273
+ }
274
+
275
+ function onmove(move: SortableItemMove<SortableItem, NavContainer>): void {
276
+ layout.moveItem(move.item.id, move.target.containerId, move.target.index);
277
+ }
278
+
279
+ function oncontainermove(move: SortableContainerMove<NavContainer>): void {
280
+ layout.moveSection(move.container.id, move.target);
281
+ }
282
+ </script>
283
+
284
+ {#snippet toggles(entry: NavContainer)}
285
+ <ShellIconButton
286
+ icon={entry.hidden ? 'eyeOff' : 'eye'}
287
+ pressed={!entry.hidden}
288
+ label={t(M['ui.shell_layout_editor.show_entry'], { label: entry.label })}
289
+ onclick={() => setShown(entry.id, entry.hidden)}
290
+ />
291
+ {#if navMode !== 'sections'}
292
+ <ShellIconButton
293
+ icon={entry.titleVisible ? 'heading' : 'headingOff'}
294
+ pressed={entry.titleVisible}
295
+ label={t(M['ui.shell_layout_editor.show_title_for'], { label: entry.label })}
296
+ onclick={() => layout.setSectionTitleVisible(entry.id, !entry.titleVisible)}
297
+ />
298
+ {/if}
299
+ {/snippet}
300
+
301
+ {#snippet actions(entry: NavContainer)}
302
+ {@render sectionActions?.({
303
+ sectionId: entry.id,
304
+ label: entry.label,
305
+ custom: entry.custom,
306
+ editing: true,
307
+ })}
308
+ {/snippet}
309
+
310
+ <nav
311
+ class="smrt-nav-editor"
312
+ data-layout-editing=""
313
+ data-nav-mode={navMode}
314
+ aria-label={ariaLabel || t(M['ui.shell_layout_editor.sortable_label'])}
315
+ bind:this={rootEl}
316
+ >
317
+ <Sortable
318
+ {containers}
319
+ {items}
320
+ label={t(M['ui.shell_layout_editor.sortable_label'])}
321
+ reorderContainers={true}
322
+ {onmove}
323
+ {oncontainermove}
324
+ >
325
+ {#snippet containerHeader({ container })}
326
+ {@const entry = containers.find((candidate) => candidate.id === container.id)}
327
+ {#if !entry}
328
+ <strong>{container.label}</strong>
329
+ {:else if entry.root}
330
+ <span class="smrt-nav-editor__top">{container.label}</span>
331
+ {:else}
332
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
333
+ <div
334
+ class="smrt-nav-editor__head"
335
+ data-nav-section={entry.id}
336
+ data-toolbar-open={open?.id === entry.id ? '' : undefined}
337
+ onpointerleave={() => onleave(entry.id)}
338
+ onkeydown={(event) => onkeydown(entry.id, event)}
339
+ onfocusout={(event) => onfocusout(entry.id, event)}
340
+ >
341
+ <!-- raw-primitive-allow: heading button that opens the section toolbar -->
342
+ <button
343
+ type="button"
344
+ class="smrt-nav-editor__title"
345
+ class:smrt-nav-editor__title--off={!entry.titleVisible}
346
+ class:smrt-nav-editor__title--hidden={entry.hidden}
347
+ data-section-title=""
348
+ aria-haspopup="true"
349
+ aria-expanded={open?.id === entry.id}
350
+ aria-label={t(M['ui.layout_edit.edit_section'], { label: entry.label })}
351
+ onpointerenter={(event) => onhover(entry.id, event)}
352
+ onclick={() => onactivate(entry.id)}
353
+ >{#if navMode === 'sections'}<span class="smrt-nav-editor__section-icon"><ShellSectionIcon name={entry.icon} iconComponent={IconComponent} /></span>{/if}{entry.label}</button>
354
+ <span class="smrt-nav-editor__overlay">
355
+ {@render toggles(entry)}
356
+ {@render actions(entry)}
357
+ </span>
358
+ {#if open?.id === entry.id}
359
+ <div
360
+ class="smrt-nav-editor__toolbar"
361
+ role="group"
362
+ aria-label={t(M['ui.layout_edit.section_options'], { label: entry.label })}
363
+ data-testid="section-toolbar"
364
+ >
365
+ <Input
366
+ class="smrt-nav-editor__name"
367
+ interaction={false}
368
+ data-section-name={entry.id}
369
+ aria-label={t(M['ui.shell_layout_editor.section_name'], { label: entry.label })}
370
+ value={entry.label}
371
+ onchange={(event) => rename(entry.id, event.currentTarget)}
372
+ onkeydown={(event) => {
373
+ if (event.key === 'Enter') {
374
+ event.preventDefault();
375
+ rename(entry.id, event.currentTarget);
376
+ }
377
+ }}
378
+ />
379
+ {@render toggles(entry)}
380
+ {#if navMode === 'sections'}
381
+ <ShellIconButton
382
+ data-section-icon={entry.id}
383
+ aria-haspopup="true"
384
+ aria-expanded={iconPicker === entry.id}
385
+ label={t(M['ui.shell_layout_editor.section_icon'], { label: entry.label })}
386
+ onclick={() => (iconPicker = iconPicker === entry.id ? null : entry.id)}
387
+ >
388
+ <ShellSectionIcon name={entry.icon} iconComponent={IconComponent} />
389
+ </ShellIconButton>
390
+ {/if}
391
+ {#if entry.custom}
392
+ <ShellIconButton
393
+ icon="trash"
394
+ label={t(M['ui.shell_layout_editor.delete_section'], { label: entry.label })}
395
+ onclick={() => deleteSection(entry.id, entry.label, entry.count)}
396
+ />
397
+ {/if}
398
+ {@render actions(entry)}
399
+ {#if navMode === 'sections' && iconPicker === entry.id}
400
+ <div
401
+ class="smrt-nav-editor__icons"
402
+ role="group"
403
+ aria-label={t(M['ui.shell_layout_editor.section_icon'], { label: entry.label })}
404
+ >
405
+ {#each SHELL_SECTION_ICONS as name (name)}
406
+ <ShellIconButton
407
+ data-icon-option={name}
408
+ pressed={entry.icon === name}
409
+ label={t(M['ui.shell_layout_editor.icon_option'], { icon: humanizeIcon(name) })}
410
+ onclick={() => chooseIcon(entry.id, name)}
411
+ >
412
+ <ShellSectionIcon {name} />
413
+ </ShellIconButton>
414
+ {/each}
415
+ </div>
416
+ {/if}
417
+ </div>
418
+ {/if}
419
+ </div>
420
+ {/if}
421
+ {/snippet}
422
+ {#snippet item({ item: entry, container })}
423
+ {@const hidden = layout.isHidden(entry.id)}
424
+ {@const icon = itemIcons.get(entry.id)}
425
+ {@const original = itemDefaults.get(entry.id)}
426
+ <span
427
+ class="smrt-nav-editor__row"
428
+ class:smrt-nav-editor__row--muted={hidden || layout.isHidden(container.id)}
429
+ >
430
+ {#if icon}
431
+ <span class="smrt-nav-editor__icon" aria-hidden="true">
432
+ {#if IconComponent}
433
+ <IconComponent name={icon} size={18} />
434
+ {:else}
435
+ {icon}
436
+ {/if}
437
+ </span>
438
+ {/if}
439
+ {#if renamingItem === entry.id}
440
+ <Input
441
+ class="smrt-nav-editor__item-name"
442
+ interaction={false}
443
+ data-item-name={entry.id}
444
+ aria-label={t(M['ui.shell_layout_editor.item_name'], { label: entry.label })}
445
+ value={entry.label}
446
+ onblur={(event) => commitRenameItem(entry.id, event.currentTarget.value)}
447
+ onkeydown={(event) => {
448
+ if (event.key === 'Enter') {
449
+ event.preventDefault();
450
+ event.stopPropagation();
451
+ const value = event.currentTarget.value;
452
+ commitRenameItem(entry.id, value);
453
+ cancelRenameItem(entry.id);
454
+ } else if (event.key === 'Escape') {
455
+ event.preventDefault();
456
+ event.stopPropagation();
457
+ cancelRenameItem(entry.id);
458
+ }
459
+ }}
460
+ />
461
+ {:else}
462
+ <strong title={original !== entry.label ? original : undefined}>{entry.label}</strong>
463
+ {/if}
464
+ </span>
465
+ <ShellIconButton
466
+ icon="edit"
467
+ data-item-rename={entry.id}
468
+ label={t(M['ui.shell_layout_editor.rename_item'], { label: entry.label })}
469
+ onclick={() => startRenameItem(entry.id)}
470
+ />
471
+ {#if original !== undefined && original !== entry.label}
472
+ <ShellIconButton
473
+ icon="undo"
474
+ label={t(M['ui.shell_layout_editor.reset_item'], { label: entry.label, original })}
475
+ onclick={() => layout.renameItem(entry.id, null)}
476
+ />
477
+ {/if}
478
+ <ShellIconButton
479
+ icon={hidden ? 'eyeOff' : 'eye'}
480
+ pressed={!hidden}
481
+ label={t(M['ui.shell_layout_editor.show_entry'], { label: entry.label })}
482
+ onclick={() => setShown(entry.id, hidden)}
483
+ />
484
+ {/snippet}
485
+ </Sortable>
486
+ <Button variant="secondary" size="sm" class="smrt-nav-editor__new" onclick={createSection}>
487
+ {t(M['ui.shell_layout_editor.new_section'])}
488
+ </Button>
489
+ </nav>
490
+
491
+ <style>
492
+ .smrt-nav-editor { display: grid; gap: var(--smrt-spacing-2); min-inline-size: 0; }
493
+ /* Make the generic Sortable read as the real navigation. */
494
+ .smrt-nav-editor :global(.smrt-sortable) { gap: var(--smrt-spacing-1); }
495
+ .smrt-nav-editor :global(.smrt-sortable__containers) { gap: var(--smrt-spacing-1); }
496
+ .smrt-nav-editor :global(.smrt-sortable__container) { padding: 0; border: 0; background: transparent; gap: var(--smrt-spacing-1); }
497
+ .smrt-nav-editor :global(.smrt-sortable__item) { padding: 0; border: 0; border-radius: var(--smrt-radius-medium); background: transparent; }
498
+ .smrt-nav-editor :global(.smrt-sortable__item:hover) { background: var(--smrt-color-surface-container-high); }
499
+ .smrt-nav-editor :global(.smrt-sortable__items) { padding-inline-start: var(--smrt-spacing-3); min-block-size: 0; }
500
+ .smrt-nav-editor :global(.smrt-sortable__header) { min-block-size: 0; }
501
+ .smrt-nav-editor :global(.smrt-sortable__empty) { padding-block: 0; }
502
+ .smrt-nav-editor :global(.smrt-sortable__handle) { inline-size: 1.5rem; block-size: 2rem; }
503
+
504
+ .smrt-nav-editor__top { color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-small-font); text-transform: uppercase; letter-spacing: 0.04em; }
505
+ .smrt-nav-editor__head { position: relative; flex: 1 1 auto; display: flex; align-items: center; gap: var(--smrt-spacing-1); min-inline-size: 0; }
506
+ .smrt-nav-editor__title { flex: 1 1 auto; min-inline-size: 0; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 0; border-radius: var(--smrt-radius-medium); background: transparent; color: var(--smrt-color-on-surface-variant); font-size: var(--smrt-typography-label-large-size); font-weight: var(--smrt-typography-weight-semibold); text-align: start; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
507
+ .smrt-nav-editor__title:hover, .smrt-nav-editor__head[data-toolbar-open] .smrt-nav-editor__title { background: var(--smrt-color-surface-container-high); }
508
+ .smrt-nav-editor__title:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
509
+ .smrt-nav-editor__title--off { font-style: italic; font-weight: var(--smrt-typography-weight-normal, normal); }
510
+ .smrt-nav-editor__title--hidden { opacity: 0.55; }
511
+ .smrt-nav-editor__overlay { display: inline-flex; align-items: center; flex: 0 0 auto; }
512
+ .smrt-nav-editor__toolbar { position: absolute; inset-block-start: 100%; inset-inline: 0; z-index: 40; display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-1); padding: var(--smrt-spacing-2); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface-container-high); box-shadow: var(--smrt-elevation-2, 0 4px 12px rgb(0 0 0 / 0.2)); }
513
+ :global(.smrt-nav-editor__name) { flex: 1 1 100%; min-inline-size: 0; }
514
+ .smrt-nav-editor__row { flex: 1 1 auto; min-inline-size: 0; display: flex; align-items: center; gap: var(--smrt-spacing-2); padding: var(--smrt-spacing-2) var(--smrt-spacing-1); color: var(--smrt-color-on-surface); }
515
+ .smrt-nav-editor__row--muted { opacity: 0.55; }
516
+ .smrt-nav-editor__row strong { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--smrt-typography-weight-medium, 500); }
517
+ /* Keep editor rows inside the sidebar: the implicit grid column would size to
518
+ the rows' min-content and push the trailing icons past the clipped rail. */
519
+ .smrt-nav-editor :global(.smrt-sortable) { grid-template-columns: minmax(0, 1fr); }
520
+ .smrt-nav-editor :global(.smrt-sortable__container),
521
+ .smrt-nav-editor :global(.smrt-sortable__items),
522
+ .smrt-nav-editor :global(.smrt-sortable__item) { min-inline-size: 0; max-inline-size: 100%; box-sizing: border-box; }
523
+ .smrt-nav-editor__icon { display: inline-grid; place-items: center; inline-size: 1.25rem; block-size: 1.25rem; min-inline-size: 1.25rem; }
524
+ :global(.smrt-nav-editor__item-name) { flex: 1 1 auto; min-inline-size: 0; }
525
+ :global(.smrt-nav-editor__new) { justify-self: start; }
526
+ /* Sections mode: the entries are edited on the section's page. */
527
+ .smrt-nav-editor[data-nav-mode='sections'] :global(.smrt-sortable__items) { display: none; }
528
+ .smrt-nav-editor__section-icon { display: inline-flex; margin-inline-end: var(--smrt-spacing-2); vertical-align: middle; }
529
+ .smrt-nav-editor__icons { flex: 1 1 100%; display: grid; grid-template-columns: repeat(auto-fill, minmax(2rem, 1fr)); gap: var(--smrt-spacing-1); padding-block-start: var(--smrt-spacing-1); }
530
+ .smrt-nav-editor__icons :global(.smrt-shell-icon-button[aria-pressed='true']) { background: var(--smrt-color-primary-container); color: var(--smrt-color-on-primary-container); }
531
+ </style>
@@ -0,0 +1,26 @@
1
+ import { type Component, type Snippet } from 'svelte';
2
+ import type { ShellLayoutController } from './layout-controller.svelte.js';
3
+ import type { ShellSectionActionsContext } from './types.js';
4
+ interface Props {
5
+ /** The layout API to edit; defaults to the nearest `AppShell`'s. */
6
+ controller?: ShellLayoutController;
7
+ /** Accessible name for the navigation landmark. */
8
+ 'aria-label'?: string;
9
+ /** Component used to render icon names, as for `TenantNav`. */
10
+ iconComponent?: Component<{
11
+ name: string;
12
+ size?: number;
13
+ }>;
14
+ /** Host icon buttons for each section (overlay and floating toolbar). */
15
+ sectionActions?: Snippet<[ShellSectionActionsContext]>;
16
+ /**
17
+ * `'sections'` edits only the sections (grip, rename, show, delete, icon
18
+ * picker); their entries are edited on the section's own page with
19
+ * `ShellSectionMenu`. Default `'items'`.
20
+ */
21
+ navMode?: 'items' | 'sections';
22
+ }
23
+ declare const ShellNavEditor: Component<Props, {}, "">;
24
+ type ShellNavEditor = ReturnType<typeof ShellNavEditor>;
25
+ export default ShellNavEditor;
26
+ //# sourceMappingURL=ShellNavEditor.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShellNavEditor.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/ShellNavEditor.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAiB,MAAM,QAAQ,CAAC;AAUrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAO3E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAG7D,UAAU,KAAK;IACb,oEAAoE;IACpE,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACvD;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;CAChC;AA+WD,QAAA,MAAM,cAAc,0BAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Draws a section or entry icon by name: a built-in shell icon
4
+ * (`SHELL_ICON_PATHS`) as an inline SVG, else the host's `iconComponent`, else
5
+ * the name itself as text. Decorative (`aria-hidden`); callers name the
6
+ * control.
7
+ */
8
+ import type { Component } from 'svelte';
9
+ import {
10
+ isShellIconName,
11
+ SHELL_DEFAULT_SECTION_ICON,
12
+ SHELL_ICON_PATHS,
13
+ } from './shell-icons.js';
14
+
15
+ const warned = new Set<string>();
16
+
17
+ interface Props {
18
+ /** Icon name: a built-in shell icon, else whatever `iconComponent` knows. */
19
+ name: string;
20
+ /** Edge length in pixels (default 18). */
21
+ size?: number;
22
+ /** Renders names that are not built-in shell icons. */
23
+ iconComponent?: Component<{ name: string; size?: number }>;
24
+ }
25
+
26
+ let { name, size = 18, iconComponent: Host }: Props = $props();
27
+
28
+ // An unknown name with no host renderer never prints as text: it falls back to
29
+ // the default glyph, with one dev warning per name.
30
+ const fallback = $derived(!isShellIconName(name) && !Host);
31
+ $effect(() => {
32
+ if (
33
+ fallback &&
34
+ (import.meta as { env?: { DEV?: boolean } }).env?.DEV &&
35
+ !warned.has(name)
36
+ ) {
37
+ warned.add(name);
38
+ console.warn(
39
+ `[smrt-svelte] Unknown shell icon "${name}"; using "${SHELL_DEFAULT_SECTION_ICON}".`,
40
+ );
41
+ }
42
+ });
43
+ </script>
44
+
45
+ <span class="smrt-shell-section-icon" aria-hidden="true">
46
+ {#if isShellIconName(name)}
47
+ <svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" focusable="false"><path d={SHELL_ICON_PATHS[name]} /></svg>
48
+ {:else if Host}
49
+ <Host {name} {size} />
50
+ {:else}
51
+ <svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" focusable="false"><path d={SHELL_ICON_PATHS[SHELL_DEFAULT_SECTION_ICON]} /></svg>
52
+ {/if}
53
+ </span>
54
+
55
+ <style>
56
+ .smrt-shell-section-icon { display: inline-grid; place-items: center; flex: 0 0 auto; }
57
+ </style>
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Draws a section or entry icon by name: a built-in shell icon
3
+ * (`SHELL_ICON_PATHS`) as an inline SVG, else the host's `iconComponent`, else
4
+ * the name itself as text. Decorative (`aria-hidden`); callers name the
5
+ * control.
6
+ */
7
+ import type { Component } from 'svelte';
8
+ interface Props {
9
+ /** Icon name: a built-in shell icon, else whatever `iconComponent` knows. */
10
+ name: string;
11
+ /** Edge length in pixels (default 18). */
12
+ size?: number;
13
+ /** Renders names that are not built-in shell icons. */
14
+ iconComponent?: Component<{
15
+ name: string;
16
+ size?: number;
17
+ }>;
18
+ }
19
+ declare const ShellSectionIcon: Component<Props, {}, "">;
20
+ type ShellSectionIcon = ReturnType<typeof ShellSectionIcon>;
21
+ export default ShellSectionIcon;
22
+ //# sourceMappingURL=ShellSectionIcon.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShellSectionIcon.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/ShellSectionIcon.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAQxC,UAAU,KAAK;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AA0CD,QAAA,MAAM,gBAAgB,0BAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}