@jini-ai/ui 0.5.4 → 0.5.6

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 (270) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +21 -0
  29. package/dist/features/admin-widgets/components/Select/Select.hooks.js +24 -2
  30. package/dist/features/admin-widgets/components/Select/Select.js +4 -3
  31. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  32. package/dist/features/admin-widgets/index.d.ts +14 -2
  33. package/dist/features/admin-widgets/index.js +7 -1
  34. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  35. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  36. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  37. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  38. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  39. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  40. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  41. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  42. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  44. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  45. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  46. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  47. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  48. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  49. package/dist/features/asset-tree-browser/rules.js +40 -22
  50. package/dist/features/browser-chrome/rules.js +74 -70
  51. package/dist/features/charts/browser.js +11 -5
  52. package/dist/features/charts/react.js +2 -2
  53. package/dist/features/charts/table.js +2 -2
  54. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  55. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  56. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  57. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  58. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  59. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  60. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  61. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  62. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  63. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  64. package/dist/features/connectors/rules.d.ts +8 -1
  65. package/dist/features/connectors/rules.js +42 -20
  66. package/dist/features/connectors/types.d.ts +11 -0
  67. package/dist/features/execution/constants.d.ts +4 -10
  68. package/dist/features/execution/constants.js +9 -94
  69. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  70. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  71. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  72. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  73. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  74. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  75. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  76. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  77. package/dist/features/execution/react/hooks/useReasoningControl.d.ts +1 -1
  78. package/dist/features/execution/react/hooks/useReasoningControl.js +116 -75
  79. package/dist/features/execution/rules.d.ts +13 -2
  80. package/dist/features/execution/rules.js +51 -11
  81. package/dist/features/execution/types.d.ts +6 -42
  82. package/dist/features/execution/types.js +0 -17
  83. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  84. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  85. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  86. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  87. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  88. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  89. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  90. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  91. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  92. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  93. package/dist/features/html-editor/css.js +76 -69
  94. package/dist/features/html-editor/editor-blocks.js +47 -33
  95. package/dist/features/html-editor/editor-serialization.js +6 -5
  96. package/dist/features/html-editor/protected-elements.js +39 -28
  97. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  98. package/dist/features/html-editor/source-splice.js +51 -30
  99. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  100. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  101. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  102. package/dist/features/i18n/locale.js +20 -16
  103. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  104. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  105. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  106. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  107. package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +6 -6
  108. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  109. package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +6 -6
  110. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  111. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  112. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  113. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  114. package/dist/features/mcp-ui/secret-card.js +111 -61
  115. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  116. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  117. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  118. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  119. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  120. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  121. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  122. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  123. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  124. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  125. package/dist/features/media-providers/constants.d.ts +1 -0
  126. package/dist/features/media-providers/constants.js +2 -81
  127. package/dist/features/media-providers/index.d.ts +1 -1
  128. package/dist/features/media-providers/ports.d.ts +2 -0
  129. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  130. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  131. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  132. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  133. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  134. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  135. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  136. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  137. package/dist/features/media-providers/rules.js +17 -10
  138. package/dist/features/media-providers/types.d.ts +4 -22
  139. package/dist/features/media-providers/types.js +3 -8
  140. package/dist/features/memory/formatters.js +127 -92
  141. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  142. package/dist/features/memory/react/components/MemoryList.js +37 -18
  143. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  144. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  145. package/dist/features/observability/long-task.d.ts +0 -12
  146. package/dist/features/observability/long-task.js +15 -19
  147. package/dist/features/observability/resource-error.d.ts +0 -10
  148. package/dist/features/observability/resource-error.js +18 -13
  149. package/dist/features/observability/stuck-run.js +10 -6
  150. package/dist/features/observability/telemetry-url.d.ts +6 -0
  151. package/dist/features/observability/telemetry-url.js +16 -0
  152. package/dist/features/observability/white-screen.js +12 -24
  153. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  154. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  155. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  156. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  157. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  158. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  159. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  160. package/dist/features/progress-card/reference-adapters.js +59 -54
  161. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  162. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  163. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  164. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  165. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  166. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  167. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  168. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  170. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  171. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  172. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  174. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  175. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  176. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  177. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  178. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  179. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  180. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  181. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  182. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  183. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  184. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  185. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  186. package/dist/features/resource-dashboard/rules.js +2 -8
  187. package/dist/features/sketch-editor/dom.js +150 -99
  188. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  189. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  190. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  191. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  192. package/dist/features/skills/react/components/SkillRow.js +38 -18
  193. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  194. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  195. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  196. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  197. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  198. package/dist/features/skills/rules.js +32 -23
  199. package/dist/features/source-config-list/agent-handles.js +2 -1
  200. package/dist/features/source-config-list/dependencies.js +53 -49
  201. package/dist/features/source-config-list/index.d.ts +3 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  203. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  204. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  205. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  206. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  207. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  208. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  209. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  210. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  211. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  212. package/dist/features/source-config-list/types.d.ts +14 -0
  213. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  214. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  215. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  217. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  218. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  219. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  220. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  221. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  222. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  223. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  224. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  225. package/dist/features/version-manager/dependencies.d.ts +3 -1
  226. package/dist/features/version-manager/dependencies.js +3 -2
  227. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  228. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  229. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  230. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  231. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  232. package/dist/features/viewer-shell/rules.js +19 -23
  233. package/dist/react/components/CustomSelect.d.ts +38 -1
  234. package/dist/react/components/CustomSelect.js +94 -38
  235. package/dist/react/components/OnboardingDropdown.js +20 -8
  236. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  237. package/dist/react/components/Toast.js +13 -2
  238. package/dist/react/components/TooltipLayer.js +5 -14
  239. package/dist/react/components/WorkingDirPicker.js +78 -50
  240. package/dist/react/components/remixicon-font-recovery.js +38 -30
  241. package/dist/react/components/select-popover.d.ts +8 -6
  242. package/dist/react/components/select-popover.js +33 -29
  243. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  244. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  245. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  246. package/dist/react/mcp-ui/host-message-source.js +15 -5
  247. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  248. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  249. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  250. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  251. package/dist/renderers/preview-modal-shell/react/components/icons.js +4 -1
  252. package/dist/renderers/react/components/ArtifactView.js +9 -6
  253. package/dist/renderers/srcdoc/build.d.ts +4 -1
  254. package/dist/renderers/srcdoc/build.js +62 -48
  255. package/dist/styles/admin.css +29 -14
  256. package/dist/styles/lightbox.css +142 -0
  257. package/dist/styles/variables.css +128 -0
  258. package/dist/theme/apply.js +9 -5
  259. package/dist/utils/auto-open-file.d.ts +2 -1
  260. package/dist/utils/auto-open-file.js +69 -85
  261. package/dist/utils/markdown-scroll-sync.js +108 -87
  262. package/dist/utils/polygon-selection.js +25 -26
  263. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  264. package/dist/utils/smooth-scroll-to-top.js +2 -32
  265. package/dist/utils/sse.js +26 -26
  266. package/dist/utils/unit-bezier.d.ts +7 -0
  267. package/dist/utils/unit-bezier.js +38 -0
  268. package/package.json +7 -10
  269. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  270. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -157,13 +157,50 @@ export interface UseCustomSelectResult {
157
157
  * consumer.
158
158
  */
159
159
  export declare function useCustomSelect({ value, options, onChange, portal, placeholder, onOpenChange, }: UseCustomSelectParams, _optional?: Record<string, never>): UseCustomSelectResult;
160
+ export interface CustomSelectOriginTheme {
161
+ 'data-admin-theme'?: string;
162
+ 'data-color-scheme'?: string;
163
+ 'data-theme'?: string;
164
+ }
165
+ /** Resolve theme identity and the nearest scheme separately, so a local light pin beats a dark root.
166
+ * @complexity O(d) time, O(1) space for d DOM ancestors. No host-specific theme ids are needed. */
167
+ export declare function resolveCustomSelectOriginTheme({ trigger }: {
168
+ trigger: HTMLElement | null;
169
+ }, _optional?: Record<string, never>): CustomSelectOriginTheme;
170
+ /**
171
+ * The theme of the place this menu was opened FROM.
172
+ *
173
+ * A portalled menu is a child of `document.body`, so it escapes any `data-theme` set on an ancestor
174
+ * of the trigger — and this package's theming works precisely by declaring `--jini-*` tokens on
175
+ * `[data-theme='light']` / `[data-theme='dark']`. A host that pins a theme on a WRAPPER rather than
176
+ * on `document.documentElement` therefore gets a correctly-themed control and a menu themed by
177
+ * whatever the document happens to be.
178
+ *
179
+ * Measured in a host admin that is light-only and pins `data-theme="light"` on its panel wrapper:
180
+ * on a machine set to dark, the trigger rendered light and its own dropdown rendered dark — one
181
+ * control, two themes, four pixels apart. The host cannot fix that from CSS either, because the
182
+ * tokens resolve on the portal node's ancestors, which it has no way to select.
183
+ *
184
+ * So the menu carries its origin's theme with it. The nearest scheme boundary wins, with
185
+ * canonical `data-color-scheme` taking precedence over legacy `data-theme` on the same node.
186
+ * Theme identity is independent of scheme. Observe the trigger's ancestors while open so a
187
+ * host's live theme/scheme change updates an existing portal too. With neither attribute, leave
188
+ * document-level theming untouched. Only applied when portalling — an inline menu is still
189
+ * inside the subtree and inherits correctly on its own.
190
+ * @complexity O(d) time/observer storage for d ancestors; disposed on close or unmount.
191
+ */
192
+ export declare function useCustomSelectOriginTheme({ buttonRef, open, portal }: {
193
+ buttonRef: MutableRefObject<HTMLButtonElement | null>;
194
+ open: boolean;
195
+ portal: boolean;
196
+ }, _optional?: Record<string, never>): CustomSelectOriginTheme;
160
197
  /**
161
198
  * Accessible custom `<select>` replacement with grouped options, full keyboard
162
199
  * navigation, and an optional portal-rendered menu. All behavior lives in
163
200
  * {@link useCustomSelect}; this component only wires the hook's state to the
164
201
  * trigger button and the listbox menu.
165
202
  */
166
- export declare function CustomSelect({ value, options, onChange, ariaLabel, labelledBy, className, triggerClassName, menuClassName, disabled, placeholder, portal, portalContainer, title, onFocus, menuHeader, onOpenChange, useCustomSelect: useCustomSelectHook, }: CustomSelectProps, _optional?: Record<string, never>): import("react").JSX.Element;
203
+ export declare function CustomSelect({ value, options, onChange, ariaLabel, labelledBy, className, triggerClassName, menuClassName, disabled, placeholder, portal, portalContainer, title, onFocus, menuHeader, onOpenChange, useCustomSelect: useCustomSelectHook, }: CustomSelectProps): import("react").JSX.Element;
167
204
  /** One row in the listbox: a selectable option button that reports hover
168
205
  * (activation) and click (choice) back to {@link CustomSelect}. */
169
206
  export declare function CustomSelectOptionButton({ option, selected, active, id, onChoose, onActive, }: {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // NOTE: verified zero real consumers in the vendored source snapshot before
3
3
  // porting (see packages/ui/source-map.md) — shipped anyway since it's small,
4
4
  // self-contained, and correct; a host may still find it useful as a select
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  // pattern (see TooltipLayer.tsx). Every seam is exported: pure helpers, the
9
9
  // `useCustomSelect` hook that owns all state/refs/effects/handlers, the
10
10
  // `CustomSelectOptionButton` leaf, and the dumb `CustomSelect` render.
11
- import { useEffect, useId, useMemo, useRef, useState } from 'react';
11
+ import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
12
12
  import { createPortal } from 'react-dom';
13
13
  import { useSelectPopover, isSelectEventInside, openSelectOnKey, applySelectNavigationKey, resolveSelectPortalContainer } from './select-popover.js';
14
14
  import { Icon } from './Icon.js';
@@ -185,52 +185,108 @@ export function useCustomSelect({ value, options, onChange, portal, placeholder,
185
185
  onMenuKeyDown,
186
186
  };
187
187
  }
188
+ /** Scheme attributes from one boundary node: canonical `data-color-scheme` wins; a legacy
189
+ * `data-theme` supplies the scheme only when it is `light`/`dark`, but is always mirrored. */
190
+ function resolveCustomSelectSchemeAttributes({ canonical, legacy }) {
191
+ const scheme = canonical ?? (legacy === 'light' || legacy === 'dark' ? legacy : undefined);
192
+ return {
193
+ ...(scheme ? { 'data-color-scheme': scheme } : {}),
194
+ ...(canonical || legacy ? { 'data-theme': canonical ?? legacy } : {}),
195
+ };
196
+ }
197
+ /** Resolve theme identity and the nearest scheme separately, so a local light pin beats a dark root.
198
+ * @complexity O(d) time, O(1) space for d DOM ancestors. No host-specific theme ids are needed. */
199
+ export function resolveCustomSelectOriginTheme({ trigger }, _optional = {}) {
200
+ const identity = trigger?.closest('[data-admin-theme]')?.getAttribute('data-admin-theme');
201
+ const boundary = trigger?.closest('[data-color-scheme], [data-theme]');
202
+ return {
203
+ ...(identity ? { 'data-admin-theme': identity } : {}),
204
+ ...resolveCustomSelectSchemeAttributes({
205
+ canonical: boundary?.getAttribute('data-color-scheme'),
206
+ legacy: boundary?.getAttribute('data-theme'),
207
+ }),
208
+ };
209
+ }
210
+ /**
211
+ * The theme of the place this menu was opened FROM.
212
+ *
213
+ * A portalled menu is a child of `document.body`, so it escapes any `data-theme` set on an ancestor
214
+ * of the trigger — and this package's theming works precisely by declaring `--jini-*` tokens on
215
+ * `[data-theme='light']` / `[data-theme='dark']`. A host that pins a theme on a WRAPPER rather than
216
+ * on `document.documentElement` therefore gets a correctly-themed control and a menu themed by
217
+ * whatever the document happens to be.
218
+ *
219
+ * Measured in a host admin that is light-only and pins `data-theme="light"` on its panel wrapper:
220
+ * on a machine set to dark, the trigger rendered light and its own dropdown rendered dark — one
221
+ * control, two themes, four pixels apart. The host cannot fix that from CSS either, because the
222
+ * tokens resolve on the portal node's ancestors, which it has no way to select.
223
+ *
224
+ * So the menu carries its origin's theme with it. The nearest scheme boundary wins, with
225
+ * canonical `data-color-scheme` taking precedence over legacy `data-theme` on the same node.
226
+ * Theme identity is independent of scheme. Observe the trigger's ancestors while open so a
227
+ * host's live theme/scheme change updates an existing portal too. With neither attribute, leave
228
+ * document-level theming untouched. Only applied when portalling — an inline menu is still
229
+ * inside the subtree and inherits correctly on its own.
230
+ * @complexity O(d) time/observer storage for d ancestors; disposed on close or unmount.
231
+ */
232
+ export function useCustomSelectOriginTheme({ buttonRef, open, portal }, _optional = {}) {
233
+ const [attributes, setAttributes] = useState({});
234
+ useLayoutEffect(() => {
235
+ if (!open || !portal)
236
+ return;
237
+ const update = () => {
238
+ const next = resolveCustomSelectOriginTheme({ trigger: buttonRef.current });
239
+ setAttributes((current) => current['data-admin-theme'] === next['data-admin-theme']
240
+ && current['data-color-scheme'] === next['data-color-scheme']
241
+ && current['data-theme'] === next['data-theme'] ? current : next);
242
+ };
243
+ update();
244
+ const observer = new MutationObserver(update);
245
+ // Observe only the origin chain, not portal nodes: copying attributes must not retrigger itself.
246
+ for (let node = buttonRef.current; node; node = node.parentElement) {
247
+ observer.observe(node, { attributes: true, attributeFilter: ['data-admin-theme', 'data-color-scheme', 'data-theme'] });
248
+ }
249
+ return () => observer.disconnect();
250
+ }, [buttonRef, open, portal]);
251
+ return portal ? attributes : {};
252
+ }
253
+ /** Inline geometry for a portalled menu once it has been measured; an inline menu is laid out by CSS. */
254
+ function customSelectMenuStyle({ portal, position }) {
255
+ return portal && position
256
+ ? {
257
+ top: position.top,
258
+ left: position.left,
259
+ width: position.width,
260
+ maxHeight: position.maxHeight,
261
+ }
262
+ : undefined;
263
+ }
264
+ /** Where an open menu renders: inline, or portalled only after its first measurement. */
265
+ function mountCustomSelectMenu({ menu, portal, position, trigger }, { container } = {}) {
266
+ if (!portal)
267
+ return menu;
268
+ return position ? createPortal(menu, resolveSelectPortalContainer({ trigger }, { container })) : null;
269
+ }
270
+ /** The listbox rows: groups render a labelled block of option buttons, flat options render directly. */
271
+ function CustomSelectMenuOptions({ options, value, activeValue, optionIdByValue, onChoose, onActive }) {
272
+ const renderOption = (option) => (_jsx(CustomSelectOptionButton, { option: option, selected: option.value === value, active: option.value === activeValue, id: optionIdByValue.get(option.value), onChoose: onChoose, onActive: onActive }, option.value));
273
+ return (_jsx(_Fragment, { children: options.map((item) => (isCustomSelectGroup(item) ? (_jsxs("div", { className: "jini-select-group", children: [_jsx("div", { className: "jini-select-group-label", children: item.label }), item.options.map(renderOption)] }, `group:${item.label}`)) : renderOption(item))) }));
274
+ }
188
275
  /**
189
276
  * Accessible custom `<select>` replacement with grouped options, full keyboard
190
277
  * navigation, and an optional portal-rendered menu. All behavior lives in
191
278
  * {@link useCustomSelect}; this component only wires the hook's state to the
192
279
  * trigger button and the listbox menu.
193
280
  */
194
- export function CustomSelect({ value, options, onChange, ariaLabel, labelledBy, className, triggerClassName, menuClassName, disabled = false, placeholder, portal = true, portalContainer, title, onFocus, menuHeader, onOpenChange, useCustomSelect: useCustomSelectHook = useCustomSelect, }, _optional = {}) {
281
+ export function CustomSelect({ value, options, onChange, ariaLabel, labelledBy, className, triggerClassName, menuClassName, disabled = false, placeholder, portal = true, portalContainer, title, onFocus, menuHeader, onOpenChange, useCustomSelect: useCustomSelectHook = useCustomSelect, }) {
195
282
  const { idBase, buttonRef, menuRef, open, activeValue, position, selectedLabel, optionIdByValue, activeOptionId, choose, setActiveValue, toggleOpen, onButtonKeyDown, onMenuKeyDown, } = useCustomSelectHook({ value, options, onChange, portal, placeholder, onOpenChange });
196
- /**
197
- * The theme of the place this menu was opened FROM.
198
- *
199
- * A portalled menu is a child of `document.body`, so it escapes any `data-theme` set on an ancestor
200
- * of the trigger — and this package's theming works precisely by declaring `--jini-*` tokens on
201
- * `[data-theme='light']` / `[data-theme='dark']`. A host that pins a theme on a WRAPPER rather than
202
- * on `document.documentElement` therefore gets a correctly-themed control and a menu themed by
203
- * whatever the document happens to be.
204
- *
205
- * Measured in a host admin that is light-only and pins `data-theme="light"` on its panel wrapper:
206
- * on a machine set to dark, the trigger rendered light and its own dropdown rendered dark — one
207
- * control, two themes, four pixels apart. The host cannot fix that from CSS either, because the
208
- * tokens resolve on the portal node's ancestors, which it has no way to select.
209
- *
210
- * So the menu carries its origin's theme with it. Read from the trigger's nearest `[data-theme]`
211
- * ancestor; `null` when there is none, which leaves document-level theming untouched and every
212
- * existing caller byte-identical. Only applied when portalling — an inline menu is still inside the
213
- * subtree and inherits correctly on its own.
214
- */
215
- const originTheme = buttonRef.current?.closest('[data-theme]')?.getAttribute('data-theme') ?? null;
216
- const menu = (_jsxs("div", { ref: menuRef, id: `${idBase}-menu`, onKeyDown: onMenuKeyDown, ...(portal && originTheme ? { 'data-theme': originTheme } : {}), className: [
283
+ const originTheme = useCustomSelectOriginTheme({ buttonRef, open, portal });
284
+ const menu = (_jsxs("div", { ref: menuRef, id: `${idBase}-menu`, onKeyDown: onMenuKeyDown, ...originTheme, className: [
217
285
  'jini-select-menu',
218
286
  portal ? 'portal' : 'inline',
219
287
  menuClassName,
220
- ].filter(Boolean).join(' '), role: "listbox", "aria-label": ariaLabel, style: portal && position
221
- ? {
222
- top: position.top,
223
- left: position.left,
224
- width: position.width,
225
- maxHeight: position.maxHeight,
226
- }
227
- : undefined, children: [menuHeader, options.map((item) => {
228
- if (isCustomSelectGroup(item)) {
229
- return (_jsxs("div", { className: "jini-select-group", children: [_jsx("div", { className: "jini-select-group-label", children: item.label }), item.options.map((option) => (_jsx(CustomSelectOptionButton, { option: option, selected: option.value === value, active: option.value === activeValue, id: optionIdByValue.get(option.value), onChoose: choose, onActive: setActiveValue }, option.value)))] }, `group:${item.label}`));
230
- }
231
- return (_jsx(CustomSelectOptionButton, { option: item, selected: item.value === value, active: item.value === activeValue, id: optionIdByValue.get(item.value), onChoose: choose, onActive: setActiveValue }, item.value));
232
- })] }));
233
- return (_jsxs("div", { className: ['jini-select', className].filter(Boolean).join(' '), children: [_jsxs("button", { ref: buttonRef, type: "button", className: ['jini-select-trigger', triggerClassName].filter(Boolean).join(' '), role: "combobox", value: value, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": `${idBase}-menu`, "aria-activedescendant": activeOptionId, "aria-describedby": labelledBy, "aria-label": `${ariaLabel}: ${selectedLabel}`, disabled: disabled, title: title, onClick: toggleOpen, onKeyDown: onButtonKeyDown, onFocus: onFocus, children: [_jsx("span", { id: `${idBase}-value`, className: "jini-select-value", children: selectedLabel }), _jsx(Icon, { name: "chevron-down", size: 14 })] }), open ? (portal ? (position ? createPortal(menu, resolveSelectPortalContainer({ trigger: buttonRef.current }, { container: portalContainer })) : null) : menu) : null] }));
288
+ ].filter(Boolean).join(' '), role: "listbox", "aria-label": ariaLabel, style: customSelectMenuStyle({ portal, position }), children: [menuHeader, _jsx(CustomSelectMenuOptions, { options: options, value: value, activeValue: activeValue, optionIdByValue: optionIdByValue, onChoose: choose, onActive: setActiveValue })] }));
289
+ return (_jsxs("div", { className: ['jini-select', className].filter(Boolean).join(' '), children: [_jsxs("button", { ref: buttonRef, type: "button", className: ['jini-select-trigger', triggerClassName].filter(Boolean).join(' '), role: "combobox", value: value, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": `${idBase}-menu`, "aria-activedescendant": activeOptionId, "aria-describedby": labelledBy, "aria-label": `${ariaLabel}: ${selectedLabel}`, disabled: disabled, title: title, onClick: toggleOpen, onKeyDown: onButtonKeyDown, onFocus: onFocus, children: [_jsx("span", { id: `${idBase}-value`, className: "jini-select-value", children: selectedLabel }), _jsx(Icon, { name: "chevron-down", size: 14 })] }), open ? mountCustomSelectMenu({ menu, portal, position, trigger: buttonRef.current }, { container: portalContainer }) : null] }));
234
290
  }
235
291
  /** One row in the listbox: a selectable option button that reports hover
236
292
  * (activation) and click (choice) back to {@link CustomSelect}. */
@@ -237,6 +237,25 @@ export function useOnboardingDropdown(props) {
237
237
  // ---------------------------------------------------------------------------
238
238
  // Dumb component
239
239
  // ---------------------------------------------------------------------------
240
+ function OnboardingDropdownSearch({ query, setQuery, placeholder, label }) {
241
+ return (_jsxs("label", { className: "onboarding-view__select-search", onClick: (event) => event.stopPropagation(), children: [_jsx(Icon, { name: "search", size: 14 }), _jsx("input", { type: "search", value: query, placeholder: placeholder, "aria-label": label, autoFocus: true, onChange: (event) => setQuery(event.target.value), onKeyDown: (event) => {
242
+ if (event.key !== 'Escape') {
243
+ event.stopPropagation();
244
+ }
245
+ } })] }));
246
+ }
247
+ function OnboardingDropdownOptionButton({ option, selected, selectOption }) {
248
+ return (_jsxs("button", { type: "button", className: `onboarding-view__select-option${selected ? ' is-selected' : ''}`, role: "option", "aria-selected": selected, onClick: () => selectOption(option, selected), children: [_jsx("span", { children: option.label }), selected ? _jsx(Icon, { name: "check", size: 15 }) : null] }));
249
+ }
250
+ function OnboardingDropdownOptions({ label, multiple, visibleOptions, selectedValues, emptyMessage, selectOption }) {
251
+ return (_jsxs("div", { className: "onboarding-view__select-options", role: "listbox", "aria-label": label, "aria-multiselectable": multiple || undefined, children: [visibleOptions.map((option) => (_jsx(OnboardingDropdownOptionButton, { option: option, selected: selectedValues.includes(option.value), selectOption: selectOption }, option.value))), visibleOptions.length === 0 ? (_jsx("div", { className: "onboarding-view__select-empty", children: emptyMessage })) : null] }));
252
+ }
253
+ function OnboardingDropdownTrigger({ open, hasValue, triggerLabel, toggleOpen }) {
254
+ return (_jsxs("button", { type: "button", className: `onboarding-view__select-trigger${open ? ' is-open' : ''}${hasValue ? ' has-value' : ''}`, "aria-haspopup": "listbox", "aria-expanded": open, title: triggerLabel, onClick: toggleOpen, children: [_jsx("span", { children: triggerLabel }), _jsx(Icon, { name: "chevron-down", size: 16 })] }));
255
+ }
256
+ function OnboardingDropdownMenu({ label, placeholder, multiple, searchable, searchPlaceholder, query, setQuery, menuMaxHeight, ...listProps }) {
257
+ return (_jsxs("div", { className: "onboarding-view__select-menu", "data-searchable": searchable || undefined, style: { '--onboarding-select-menu-max-height': `${menuMaxHeight}px` }, children: [searchable ? (_jsx(OnboardingDropdownSearch, { query: query, setQuery: setQuery, placeholder: searchPlaceholder || placeholder, label: searchPlaceholder || label })) : null, _jsx(OnboardingDropdownOptions, { label: label, multiple: multiple, ...listProps })] }));
258
+ }
240
259
  /**
241
260
  * Searchable/multi-select onboarding dropdown. All behavior lives in
242
261
  * {@link useOnboardingDropdown}; this component only renders the field,
@@ -245,13 +264,6 @@ export function useOnboardingDropdown(props) {
245
264
  export function OnboardingDropdown(props) {
246
265
  const { label, placeholder, multiple = false, searchable = false, searchPlaceholder, sourceTone, className, useOnboardingDropdown: useOnboardingDropdownHook = useOnboardingDropdown, } = props;
247
266
  const { open, query, setQuery, toggleOpen, rootRef, resolvedPlacement, menuMaxHeight, selectedValues, hasValue, triggerLabel, visibleOptions, emptyMessage, selectOption, } = useOnboardingDropdownHook(props);
248
- return (_jsxs("div", { className: ['onboarding-view__select-field', className].filter(Boolean).join(' '), "data-placement": resolvedPlacement, "data-open": open || undefined, ref: rootRef, children: [_jsx("span", { className: "onboarding-view__select-label", "data-source-tone": sourceTone || undefined, children: label }), _jsxs("button", { type: "button", className: `onboarding-view__select-trigger${open ? ' is-open' : ''}${hasValue ? ' has-value' : ''}`, "aria-haspopup": "listbox", "aria-expanded": open, title: triggerLabel, onClick: toggleOpen, children: [_jsx("span", { children: triggerLabel }), _jsx(Icon, { name: "chevron-down", size: 16 })] }), open ? (_jsxs("div", { className: "onboarding-view__select-menu", "data-searchable": searchable || undefined, style: { '--onboarding-select-menu-max-height': `${menuMaxHeight}px` }, children: [searchable ? (_jsxs("label", { className: "onboarding-view__select-search", onClick: (event) => event.stopPropagation(), children: [_jsx(Icon, { name: "search", size: 14 }), _jsx("input", { type: "search", value: query, placeholder: searchPlaceholder || placeholder, "aria-label": searchPlaceholder || label, autoFocus: true, onChange: (event) => setQuery(event.target.value), onKeyDown: (event) => {
249
- if (event.key !== 'Escape') {
250
- event.stopPropagation();
251
- }
252
- } })] })) : null, _jsxs("div", { className: "onboarding-view__select-options", role: "listbox", "aria-label": label, "aria-multiselectable": multiple || undefined, children: [visibleOptions.map((option) => {
253
- const selected = selectedValues.includes(option.value);
254
- return (_jsxs("button", { type: "button", className: `onboarding-view__select-option${selected ? ' is-selected' : ''}`, role: "option", "aria-selected": selected, onClick: () => selectOption(option, selected), children: [_jsx("span", { children: option.label }), selected ? _jsx(Icon, { name: "check", size: 15 }) : null] }, option.value));
255
- }), visibleOptions.length === 0 ? (_jsx("div", { className: "onboarding-view__select-empty", children: emptyMessage })) : null] })] })) : null] }));
267
+ return (_jsxs("div", { className: ['onboarding-view__select-field', className].filter(Boolean).join(' '), "data-placement": resolvedPlacement, "data-open": open || undefined, ref: rootRef, children: [_jsx("span", { className: "onboarding-view__select-label", "data-source-tone": sourceTone || undefined, children: label }), _jsx(OnboardingDropdownTrigger, { open: open, hasValue: hasValue, triggerLabel: triggerLabel, toggleOpen: toggleOpen }), open ? (_jsx(OnboardingDropdownMenu, { label: label, placeholder: placeholder, multiple: multiple, searchable: searchable, searchPlaceholder: searchPlaceholder, query: query, setQuery: setQuery, menuMaxHeight: menuMaxHeight, visibleOptions: visibleOptions, selectedValues: selectedValues, emptyMessage: emptyMessage, selectOption: selectOption })) : null] }));
256
268
  }
257
269
  //# sourceMappingURL=OnboardingDropdown.js.map
@@ -13,7 +13,9 @@ function recoverFont({ stylesheet }) {
13
13
  createFontFace: ({ family, source, descriptors }) => new FontFace(family, source, descriptors),
14
14
  timers: {
15
15
  setTimeout: (run, delay) => globalThis.setTimeout(run, delay),
16
- clearTimeout: (handle) => globalThis.clearTimeout(handle),
16
+ // Only the recovery's stop handle clears a timer, and this module never stops a recovery (it
17
+ // lives as long as the page), so this is a plain binding rather than a wrapper of its own.
18
+ clearTimeout: globalThis.clearTimeout.bind(globalThis),
17
19
  },
18
20
  });
19
21
  if (sheet.sheet) {
@@ -116,6 +116,18 @@ export function useToastAutoDismiss(options) {
116
116
  return { leaving, fireDismiss };
117
117
  }
118
118
  // --- Dumb component --------------------------------------------------------
119
+ function ToastIcon({ iconName }) {
120
+ return iconName ? (_jsx("span", { className: "jini-toast-icon", "aria-hidden": true, children: _jsx(Icon, { name: iconName, size: 14 }) })) : null;
121
+ }
122
+ function ToastActionButton({ actionLabel, actionAriaLabel, onAction }) {
123
+ return actionLabel && onAction ? (_jsx("button", { type: "button", className: "jini-toast-action", onClick: onAction, "aria-label": actionAriaLabel ?? actionLabel, children: actionLabel })) : null;
124
+ }
125
+ /** A code toast is pinned open, so it gets a labelled "Dismiss" button instead of the close glyph. */
126
+ function ToastDismissButton({ code, onDismiss }) {
127
+ if (!onDismiss)
128
+ return null;
129
+ return code ? (_jsx("button", { type: "button", className: "jini-toast-dismiss", onClick: onDismiss, "aria-label": "Dismiss", children: "Dismiss" })) : (_jsx("button", { type: "button", className: "jini-toast-close", onClick: onDismiss, "aria-label": "Dismiss", children: _jsx(Icon, { name: "close", size: 13 }) }));
130
+ }
119
131
  /**
120
132
  * Pure render of a transient toast. All timing/state lives in
121
133
  * {@link useToastAutoDismiss}; all derivations in the module-level helpers.
@@ -125,7 +137,6 @@ export function useToastAutoDismiss(options) {
125
137
  export function Toast({ message, className, details, actionLabel, actionAriaLabel, onAction, code, ttlMs = TOAST_DEFAULT_TTL, onDismiss, role = 'status', tone = 'default', placement = 'bottom', useToastAutoDismiss: useToastAutoDismissHook = useToastAutoDismiss, }) {
126
138
  const effectiveTtl = toastEffectiveTtl(code, ttlMs);
127
139
  const { leaving } = useToastAutoDismissHook({ message, details, code, effectiveTtl, onDismiss });
128
- const iconName = toastToneIcon(tone);
129
- return (_jsxs("div", { className: toastClassName({ tone, placement, className, leaving }), role: role, "aria-live": toastAriaLive(role), children: [_jsxs("div", { className: "jini-toast-body", children: [iconName ? (_jsx("span", { className: "jini-toast-icon", "aria-hidden": true, children: _jsx(Icon, { name: iconName, size: 14 }) })) : null, _jsx("div", { className: "jini-toast-message", children: message })] }), details ? _jsx("div", { className: "jini-toast-details", children: details }) : null, code ? (_jsx("pre", { className: "jini-toast-code", children: code })) : null, actionLabel && onAction ? (_jsx("button", { type: "button", className: "jini-toast-action", onClick: onAction, "aria-label": actionAriaLabel ?? actionLabel, children: actionLabel })) : null, !code && onDismiss ? (_jsx("button", { type: "button", className: "jini-toast-close", onClick: onDismiss, "aria-label": "Dismiss", children: _jsx(Icon, { name: "close", size: 13 }) })) : null, code && onDismiss ? (_jsx("button", { type: "button", className: "jini-toast-dismiss", onClick: onDismiss, "aria-label": "Dismiss", children: "Dismiss" })) : null] }));
140
+ return (_jsxs("div", { className: toastClassName({ tone, placement, className, leaving }), role: role, "aria-live": toastAriaLive(role), children: [_jsxs("div", { className: "jini-toast-body", children: [_jsx(ToastIcon, { iconName: toastToneIcon(tone) }), _jsx("div", { className: "jini-toast-message", children: message })] }), details ? _jsx("div", { className: "jini-toast-details", children: details }) : null, code ? (_jsx("pre", { className: "jini-toast-code", children: code })) : null, _jsx(ToastActionButton, { actionLabel: actionLabel, actionAriaLabel: actionAriaLabel, onAction: onAction }), _jsx(ToastDismissButton, { code: code, onDismiss: onDismiss })] }));
130
141
  }
131
142
  //# sourceMappingURL=Toast.js.map
@@ -210,7 +210,7 @@ export function useTooltipLayer() {
210
210
  if (target)
211
211
  showTooltip(target);
212
212
  };
213
- const onPointerOut = (event) => {
213
+ const onTargetExit = (event) => {
214
214
  const target = readTooltipTarget(event.target);
215
215
  if (!target)
216
216
  return;
@@ -240,15 +240,6 @@ export function useTooltipLayer() {
240
240
  }
241
241
  suppressNativeTitle(target);
242
242
  };
243
- const onFocusOut = (event) => {
244
- const target = readTooltipTarget(event.target);
245
- if (!target)
246
- return;
247
- const next = event.relatedTarget;
248
- if (next instanceof Node && target.contains(next))
249
- return;
250
- hideTooltip();
251
- };
252
243
  const onKeyDown = (event) => {
253
244
  lastInputRef.current = 'keyboard';
254
245
  if (event.key === 'Escape')
@@ -258,23 +249,23 @@ export function useTooltipLayer() {
258
249
  }
259
250
  };
260
251
  document.addEventListener('pointerover', onPointerOver);
261
- document.addEventListener('pointerout', onPointerOut);
252
+ document.addEventListener('pointerout', onTargetExit);
262
253
  document.addEventListener('pointerdown', onPointerDown);
263
254
  document.addEventListener('pointercancel', onPointerCancel);
264
255
  document.addEventListener('click', onClick);
265
256
  document.addEventListener('focusin', onFocusIn);
266
- document.addEventListener('focusout', onFocusOut);
257
+ document.addEventListener('focusout', onTargetExit);
267
258
  document.addEventListener('keydown', onKeyDown);
268
259
  window.addEventListener('resize', scheduleUpdatePosition);
269
260
  window.addEventListener('scroll', scheduleUpdatePosition, true);
270
261
  return () => {
271
262
  document.removeEventListener('pointerover', onPointerOver);
272
- document.removeEventListener('pointerout', onPointerOut);
263
+ document.removeEventListener('pointerout', onTargetExit);
273
264
  document.removeEventListener('pointerdown', onPointerDown);
274
265
  document.removeEventListener('pointercancel', onPointerCancel);
275
266
  document.removeEventListener('click', onClick);
276
267
  document.removeEventListener('focusin', onFocusIn);
277
- document.removeEventListener('focusout', onFocusOut);
268
+ document.removeEventListener('focusout', onTargetExit);
278
269
  document.removeEventListener('keydown', onKeyDown);
279
270
  window.removeEventListener('resize', scheduleUpdatePosition);
280
271
  window.removeEventListener('scroll', scheduleUpdatePosition, true);
@@ -119,22 +119,62 @@ export function useWorkingDirPicker(input) {
119
119
  toggleRecent: flyout.toggle,
120
120
  };
121
121
  }
122
+ const MENU_ITEM_SELECTOR = '[role="menuitem"]:not(:disabled)';
123
+ const RECENT_ITEM_SELECTOR = '[data-testid="working-dir-recent"]';
124
+ const RECENT_LIST_SELECTOR = '[data-testid="working-dir-recent-list"]';
122
125
  /**
123
- * Working-directory picker: a borderless trigger that opens a panel with
124
- * "Choose folder" and a "Recent folders" submenu. Layout is left to the host
125
- * via `className`; all open/close/keyboard state lives in
126
- * {@link useWorkingDirPicker}. This component is a dumb render.
126
+ * The menu item a navigation key moves focus to, or `null` for any other key. `count` is never 0:
127
+ * the "Choose folder" item is always rendered and never disabled. ArrowUp from `currentIndex` -1
128
+ * (focus on no menu item) yields `count - 2`, pinned as-is by the component tests.
127
129
  */
128
- export function WorkingDirPicker({ workingDir, recentDirs, onPickDirectory, onSelectRecent, onClear, className, placement = 'down', invalid = false, onOpen, labels, useWorkingDirPicker: useWorkingDirPickerHook = useWorkingDirPicker, }) {
129
- const panelId = useId();
130
- const triggerRef = useRef(null);
131
- const panelRef = useRef(null);
130
+ function resolveMenuNavigationIndex({ key, currentIndex, count }) {
131
+ if (key === 'Home')
132
+ return 0;
133
+ if (key === 'End')
134
+ return count - 1;
135
+ if (key === 'ArrowDown')
136
+ return (currentIndex + 1 + count) % count;
137
+ if (key === 'ArrowUp')
138
+ return (currentIndex - 1 + count) % count;
139
+ return null;
140
+ }
141
+ /** Arrow/Home/End roving focus across the panel's enabled menu items; true when the key was handled. */
142
+ function handleMenuNavigationKey({ event }) {
143
+ const menuItems = [...event.currentTarget.querySelectorAll(MENU_ITEM_SELECTOR)];
144
+ const currentIndex = menuItems.indexOf(document.activeElement);
145
+ const nextIndex = resolveMenuNavigationIndex({ key: event.key, currentIndex, count: menuItems.length });
146
+ if (nextIndex === null)
147
+ return false;
148
+ event.preventDefault();
149
+ menuItems[nextIndex]?.focus();
150
+ return true;
151
+ }
152
+ /** ArrowRight on "Recent folders" opens its flyout; ArrowLeft inside the flyout closes it again. */
153
+ function handleRecentFlyoutKey({ event, showRecent, hideRecent }) {
154
+ const panel = event.currentTarget;
155
+ if (event.key === 'ArrowRight' && event.target === panel.querySelector(RECENT_ITEM_SELECTOR)) {
156
+ event.preventDefault();
157
+ showRecent();
158
+ window.setTimeout(() => {
159
+ panel.querySelector(`${RECENT_LIST_SELECTOR} [role="menuitem"]`)?.focus();
160
+ }, 0);
161
+ return;
162
+ }
163
+ if (event.key === 'ArrowLeft'
164
+ && event.target instanceof HTMLElement
165
+ && event.target.closest(RECENT_LIST_SELECTOR)) {
166
+ event.preventDefault();
167
+ hideRecent();
168
+ panel.querySelector(RECENT_ITEM_SELECTOR)?.focus();
169
+ }
170
+ }
171
+ /** Focus the first menu item on open; return focus to the trigger after the panel closes. */
172
+ function useWorkingDirFocusHandoff({ open, triggerRef, panelRef }) {
132
173
  const wasOpenRef = useRef(false);
133
- const { open, recentOpen, wrapRef, labels: t, toggle, pick, selectRecent, clear, showRecent, hideRecent, toggleRecent, } = useWorkingDirPickerHook({ onPickDirectory, onSelectRecent, onClear, onOpen, labels });
134
174
  useEffect(() => {
135
175
  if (open) {
136
176
  wasOpenRef.current = true;
137
- panelRef.current?.querySelector('[role="menuitem"]:not(:disabled)')?.focus();
177
+ panelRef.current?.querySelector(MENU_ITEM_SELECTOR)?.focus();
138
178
  return;
139
179
  }
140
180
  if (!wasOpenRef.current)
@@ -142,48 +182,36 @@ export function WorkingDirPicker({ workingDir, recentDirs, onPickDirectory, onSe
142
182
  wasOpenRef.current = false;
143
183
  const timeout = window.setTimeout(() => triggerRef.current?.focus(), 0);
144
184
  return () => window.clearTimeout(timeout);
145
- }, [open]);
185
+ }, [open, panelRef, triggerRef]);
186
+ }
187
+ function WorkingDirTrigger({ triggerRef, workingDir, invalid, open, panelId, t, toggle }) {
188
+ return (_jsx("div", { className: "jini-working-dir-trigger-row", children: _jsxs("button", { ref: triggerRef, type: "button", className: `jini-working-dir-trigger${invalid ? ' invalid' : ''}`, "data-testid": "working-dir-trigger", "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": panelId, title: invalid ? t.missing : (workingDir ?? t.hint), onClick: toggle, children: [_jsx(Icon, { name: "folder", size: 13, className: "jini-working-dir-trigger-icon" }), _jsx("span", { className: "jini-working-dir-trigger-label", children: workingDir ? basename(workingDir) : t.trigger }), _jsx(Icon, { name: "chevron-down", size: 11, className: "jini-working-dir-trigger-chevron" })] }) }));
189
+ }
190
+ function WorkingDirRecentFlyout({ recentDirs, placement, t, selectRecent }) {
191
+ return (_jsx("div", { className: `jini-working-dir-flyout${placement === 'up' ? ' up' : ''}`, role: "menu", "data-testid": "working-dir-recent-list", children: recentDirs.length === 0 ? (_jsx("div", { className: "jini-working-dir-empty", children: t.recentEmpty })) : (recentDirs.map((dir) => (_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-recent-item", title: dir, onClick: () => selectRecent(dir), children: [_jsx(Icon, { name: "folder", size: 13, className: "jini-working-dir-item-icon" }), _jsx("span", { className: "jini-working-dir-recent-name", children: basename(dir) }), _jsx("span", { className: "jini-working-dir-recent-path", children: dir })] }, dir)))) }));
192
+ }
193
+ function WorkingDirPanel({ panelRef, panelId, placement, workingDir, recentDirs, canClear, t, picker, onKeyDown }) {
194
+ const { recentOpen, pick, selectRecent, clear, showRecent, hideRecent, toggleRecent } = picker;
195
+ return (_jsxs("div", { ref: panelRef, id: panelId, className: `jini-working-dir-panel${placement === 'up' ? ' up' : ''}`, role: "menu", "data-testid": "working-dir-panel", onKeyDown: onKeyDown, children: [_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "data-testid": "working-dir-pick", onClick: pick, children: [_jsx(Icon, { name: "folder", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: workingDir ? t.replace : t.pick })] }), _jsxs("div", { className: "jini-working-dir-submenu-row", onMouseEnter: showRecent, onMouseLeave: hideRecent, children: [_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "aria-haspopup": "menu", "aria-expanded": recentOpen, "data-testid": "working-dir-recent", onClick: toggleRecent, children: [_jsx(Icon, { name: "history", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: t.recent }), _jsx(Icon, { name: "chevron-right", size: 12, className: "jini-working-dir-item-chevron" })] }), recentOpen ? (_jsx(WorkingDirRecentFlyout, { recentDirs: recentDirs, placement: placement, t: t, selectRecent: selectRecent })) : null] }), canClear ? (_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "data-testid": "working-dir-clear", onClick: clear, children: [_jsx(Icon, { name: "close", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: t.clear })] })) : null] }));
196
+ }
197
+ /**
198
+ * Working-directory picker: a borderless trigger that opens a panel with
199
+ * "Choose folder" and a "Recent folders" submenu. Layout is left to the host
200
+ * via `className`; all open/close/keyboard state lives in
201
+ * {@link useWorkingDirPicker}. This component is a dumb render.
202
+ */
203
+ export function WorkingDirPicker({ workingDir, recentDirs, onPickDirectory, onSelectRecent, onClear, className, placement = 'down', invalid = false, onOpen, labels, useWorkingDirPicker: useWorkingDirPickerHook = useWorkingDirPicker, }) {
204
+ const panelId = useId();
205
+ const triggerRef = useRef(null);
206
+ const panelRef = useRef(null);
207
+ const picker = useWorkingDirPickerHook({ onPickDirectory, onSelectRecent, onClear, onOpen, labels });
208
+ const { open, wrapRef, labels: t, toggle, showRecent, hideRecent } = picker;
209
+ useWorkingDirFocusHandoff({ open, triggerRef, panelRef });
146
210
  const handlePanelKeyDown = useCallback((event) => {
147
- const menuItems = [...(panelRef.current?.querySelectorAll('[role="menuitem"]:not(:disabled)') ?? [])];
148
- const currentIndex = menuItems.indexOf(document.activeElement);
149
- if (event.key === 'ArrowDown'
150
- || event.key === 'ArrowUp'
151
- || event.key === 'Home'
152
- || event.key === 'End') {
153
- if (menuItems.length === 0)
154
- return;
155
- event.preventDefault();
156
- const nextIndex = event.key === 'Home'
157
- ? 0
158
- : event.key === 'End'
159
- ? menuItems.length - 1
160
- : event.key === 'ArrowDown'
161
- ? (currentIndex + 1 + menuItems.length) % menuItems.length
162
- : (currentIndex - 1 + menuItems.length) % menuItems.length;
163
- menuItems[nextIndex]?.focus();
211
+ if (handleMenuNavigationKey({ event }))
164
212
  return;
165
- }
166
- if (event.key === 'ArrowRight'
167
- && event.target === panelRef.current?.querySelector('[data-testid="working-dir-recent"]')) {
168
- event.preventDefault();
169
- showRecent();
170
- window.setTimeout(() => {
171
- panelRef.current
172
- ?.querySelector('[data-testid="working-dir-recent-list"] [role="menuitem"]')
173
- ?.focus();
174
- }, 0);
175
- return;
176
- }
177
- if (event.key === 'ArrowLeft'
178
- && event.target instanceof HTMLElement
179
- && event.target.closest('[data-testid="working-dir-recent-list"]')) {
180
- event.preventDefault();
181
- hideRecent();
182
- panelRef.current
183
- ?.querySelector('[data-testid="working-dir-recent"]')
184
- ?.focus();
185
- }
213
+ handleRecentFlyoutKey({ event, showRecent, hideRecent });
186
214
  }, [hideRecent, showRecent]);
187
- return (_jsxs("div", { ref: wrapRef, className: `jini-working-dir-picker${className ? ` ${className}` : ''}`, "data-testid": "working-dir-picker", children: [_jsx("div", { className: "jini-working-dir-trigger-row", children: _jsxs("button", { ref: triggerRef, type: "button", className: `jini-working-dir-trigger${invalid ? ' invalid' : ''}`, "data-testid": "working-dir-trigger", "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": panelId, title: invalid ? t.missing : (workingDir ?? t.hint), onClick: toggle, children: [_jsx(Icon, { name: "folder", size: 13, className: "jini-working-dir-trigger-icon" }), _jsx("span", { className: "jini-working-dir-trigger-label", children: workingDir ? basename(workingDir) : t.trigger }), _jsx(Icon, { name: "chevron-down", size: 11, className: "jini-working-dir-trigger-chevron" })] }) }), open ? (_jsxs("div", { ref: panelRef, id: panelId, className: `jini-working-dir-panel${placement === 'up' ? ' up' : ''}`, role: "menu", "data-testid": "working-dir-panel", onKeyDown: handlePanelKeyDown, children: [_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "data-testid": "working-dir-pick", onClick: pick, children: [_jsx(Icon, { name: "folder", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: workingDir ? t.replace : t.pick })] }), _jsxs("div", { className: "jini-working-dir-submenu-row", onMouseEnter: showRecent, onMouseLeave: hideRecent, children: [_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "aria-haspopup": "menu", "aria-expanded": recentOpen, "data-testid": "working-dir-recent", onClick: toggleRecent, children: [_jsx(Icon, { name: "history", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: t.recent }), _jsx(Icon, { name: "chevron-right", size: 12, className: "jini-working-dir-item-chevron" })] }), recentOpen ? (_jsx("div", { className: `jini-working-dir-flyout${placement === 'up' ? ' up' : ''}`, role: "menu", "data-testid": "working-dir-recent-list", children: recentDirs.length === 0 ? (_jsx("div", { className: "jini-working-dir-empty", children: t.recentEmpty })) : (recentDirs.map((dir) => (_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-recent-item", title: dir, onClick: () => selectRecent(dir), children: [_jsx(Icon, { name: "folder", size: 13, className: "jini-working-dir-item-icon" }), _jsx("span", { className: "jini-working-dir-recent-name", children: basename(dir) }), _jsx("span", { className: "jini-working-dir-recent-path", children: dir })] }, dir)))) })) : null] }), workingDir && onClear ? (_jsxs("button", { type: "button", role: "menuitem", className: "jini-working-dir-item", "data-testid": "working-dir-clear", onClick: clear, children: [_jsx(Icon, { name: "close", size: 14, className: "jini-working-dir-item-icon" }), _jsx("span", { children: t.clear })] })) : null] })) : null] }));
215
+ return (_jsxs("div", { ref: wrapRef, className: `jini-working-dir-picker${className ? ` ${className}` : ''}`, "data-testid": "working-dir-picker", children: [_jsx(WorkingDirTrigger, { triggerRef: triggerRef, workingDir: workingDir, invalid: invalid, open: open, panelId: panelId, t: t, toggle: toggle }), open ? (_jsx(WorkingDirPanel, { panelRef: panelRef, panelId: panelId, placement: placement, workingDir: workingDir, recentDirs: recentDirs, canClear: Boolean(workingDir && onClear), t: t, picker: picker, onKeyDown: handlePanelKeyDown })) : null] }));
188
216
  }
189
217
  //# sourceMappingURL=WorkingDirPicker.js.map
@@ -66,6 +66,36 @@ export function startRemixIconFontRecovery({ fonts, timers, createFace }, { retr
66
66
  timers.clearTimeout(pending);
67
67
  };
68
68
  }
69
+ /** The sheet's rules, or `undefined` when it has none yet or they are cross-origin inaccessible. */
70
+ function readStylesheetRules({ stylesheet }) {
71
+ try {
72
+ return stylesheet.sheet?.cssRules;
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ }
78
+ function isRemixIconFaceRule(rule) {
79
+ if (rule.type !== 5)
80
+ return false; // CSSRule.FONT_FACE_RULE (no global constructor in SSR/fakes).
81
+ const family = rule.style.getPropertyValue('font-family').replace(/["']/g, '').trim();
82
+ return family === 'remixicon';
83
+ }
84
+ /** The first RemixIcon face's url(...) source, or `undefined` when there is no such face or it has no url(). */
85
+ function findRemixIconFontSource({ rules }) {
86
+ const face = Array.from(rules).find(isRemixIconFaceRule);
87
+ // This package owns one url(...) WOFF2 source, not a general CSS font-source parser.
88
+ return face?.style.getPropertyValue('src').match(/url\(\s*["']?([^"')]+)["']?\s*\)/)?.[1];
89
+ }
90
+ /** The source URL tagged with the attempt number; only http(s) URLs accept a query parameter. */
91
+ function retryFontUrl({ url, attempt }) {
92
+ // A distinct URL also bypasses the browser's cached failed font resource after a restart.
93
+ const freshUrl = new URL(url.href);
94
+ if (freshUrl.protocol === 'http:' || freshUrl.protocol === 'https:') {
95
+ freshUrl.searchParams.set('jini-font-retry', String(attempt));
96
+ }
97
+ return freshUrl;
98
+ }
69
99
  /**
70
100
  * Recover this package's single WOFF2 face using the owning stylesheet's resolved URL.
71
101
  * CSSOM, rather than a bundled-module URL, preserves host asset rewriting and linked CSS bases.
@@ -75,36 +105,14 @@ export function startRemixIconFontRecovery({ fonts, timers, createFace }, { retr
75
105
  * @example startRemixIconStylesheetRecovery({ stylesheet, fonts, timers, baseUrl, createFontFace });
76
106
  */
77
107
  export function startRemixIconStylesheetRecovery({ stylesheet, fonts, timers, baseUrl, createFontFace }, options = {}) {
78
- let rules;
79
- try {
80
- rules = stylesheet.sheet?.cssRules;
81
- }
82
- catch {
108
+ const rules = readStylesheetRules({ stylesheet });
109
+ const source = rules && findRemixIconFontSource({ rules });
110
+ if (!source)
83
111
  return () => { };
84
- }
85
- if (!rules)
86
- return () => { };
87
- for (const rule of Array.from(rules)) {
88
- if (rule.type !== 5)
89
- continue; // CSSRule.FONT_FACE_RULE (no global constructor in SSR/fakes).
90
- const style = rule.style;
91
- const family = style.getPropertyValue('font-family').replace(/["']/g, '').trim();
92
- if (family !== 'remixicon')
93
- continue;
94
- // This package owns one url(...) WOFF2 source, not a general CSS font-source parser.
95
- const source = style.getPropertyValue('src').match(/url\(\s*["']?([^"')]+)["']?\s*\)/)?.[1];
96
- if (!source)
97
- return () => { };
98
- const url = new URL(source.trim(), stylesheet.sheet?.href ?? baseUrl);
99
- return startRemixIconFontRecovery({ fonts, timers, createFace({ attempt }) {
100
- // A distinct URL also bypasses the browser's cached failed font resource after a restart.
101
- const freshUrl = new URL(url.href);
102
- if (freshUrl.protocol === 'http:' || freshUrl.protocol === 'https:') {
103
- freshUrl.searchParams.set('jini-font-retry', String(attempt));
104
- }
105
- return createFontFace({ family: 'remixicon', source: `url("${freshUrl.href}") format("woff2")`, descriptors: { display: 'swap' } });
106
- } }, options);
107
- }
108
- return () => { };
112
+ const url = new URL(source.trim(), stylesheet.sheet?.href ?? baseUrl);
113
+ return startRemixIconFontRecovery({ fonts, timers, createFace({ attempt }) {
114
+ const freshUrl = retryFontUrl({ url, attempt });
115
+ return createFontFace({ family: 'remixicon', source: `url("${freshUrl.href}") format("woff2")`, descriptors: { display: 'swap' } });
116
+ } }, options);
109
117
  }
110
118
  //# sourceMappingURL=remixicon-font-recovery.js.map