@jini-ai/ui 0.4.3 → 0.4.5

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 (169) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +20 -0
  3. package/dist/core/theme/default.d.ts +4 -0
  4. package/dist/core/theme/default.js +11 -0
  5. package/dist/core/theme/stylesheet.d.ts +8 -0
  6. package/dist/core/theme/stylesheet.js +14 -0
  7. package/dist/core/theme/types.d.ts +39 -0
  8. package/dist/core/theme/types.js +2 -0
  9. package/dist/core/theme/validation.d.ts +15 -0
  10. package/dist/core/theme/validation.js +55 -0
  11. package/dist/features/admin-widgets/components/Select/Select.d.ts +11 -3
  12. package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +8 -13
  13. package/dist/features/admin-widgets/components/Select/Select.hooks.js +37 -215
  14. package/dist/features/admin-widgets/components/Select/Select.js +4 -3
  15. package/dist/features/appearance/constants.js +1 -1
  16. package/dist/features/connectors/rules.d.ts +1 -6
  17. package/dist/features/connectors/types.d.ts +1 -6
  18. package/dist/features/connectors/types.js +1 -6
  19. package/dist/features/execution/http-adapter.d.ts +111 -0
  20. package/dist/features/execution/http-adapter.js +160 -0
  21. package/dist/features/execution/index.d.ts +4 -0
  22. package/dist/features/execution/index.js +2 -0
  23. package/dist/features/execution/react/hooks/use-execution-credential.hooks.d.ts +232 -0
  24. package/dist/features/execution/react/hooks/use-execution-credential.hooks.js +217 -0
  25. package/dist/features/html-editor/canvas-style.d.ts +1 -1
  26. package/dist/features/html-editor/canvas-style.js +2 -2
  27. package/dist/features/i18n/dictionaries/settings-dialog.ar.js +2 -0
  28. package/dist/features/i18n/dictionaries/settings-dialog.bn.js +2 -0
  29. package/dist/features/i18n/dictionaries/settings-dialog.de.js +2 -0
  30. package/dist/features/i18n/dictionaries/settings-dialog.en.js +2 -0
  31. package/dist/features/i18n/dictionaries/settings-dialog.es.js +2 -0
  32. package/dist/features/i18n/dictionaries/settings-dialog.fa.js +2 -0
  33. package/dist/features/i18n/dictionaries/settings-dialog.fr.js +2 -0
  34. package/dist/features/i18n/dictionaries/settings-dialog.hi.js +2 -0
  35. package/dist/features/i18n/dictionaries/settings-dialog.hu.js +2 -0
  36. package/dist/features/i18n/dictionaries/settings-dialog.id.js +2 -0
  37. package/dist/features/i18n/dictionaries/settings-dialog.it.js +2 -0
  38. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +2 -0
  39. package/dist/features/i18n/dictionaries/settings-dialog.ko.js +2 -0
  40. package/dist/features/i18n/dictionaries/settings-dialog.pl.js +2 -0
  41. package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +2 -0
  42. package/dist/features/i18n/dictionaries/settings-dialog.ru.js +2 -0
  43. package/dist/features/i18n/dictionaries/settings-dialog.th.js +2 -0
  44. package/dist/features/i18n/dictionaries/settings-dialog.tr.js +2 -0
  45. package/dist/features/i18n/dictionaries/settings-dialog.uk.js +2 -0
  46. package/dist/features/i18n/dictionaries/settings-dialog.ur.js +2 -0
  47. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +2 -0
  48. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +2 -0
  49. package/dist/features/i18n/dictionary-translator.d.ts +5 -2
  50. package/dist/features/i18n/dictionary-translator.js +1 -1
  51. package/dist/features/i18n/index.d.ts +1 -1
  52. package/dist/features/i18n/index.js +1 -1
  53. package/dist/features/i18n/template-i18n.d.ts +11 -0
  54. package/dist/features/i18n/template-i18n.js +12 -0
  55. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  56. package/dist/features/interactive-ui/providers/recharts/bar-chart.js +3 -2
  57. package/dist/features/interactive-ui/providers/recharts/chart-tokens.d.ts +43 -0
  58. package/dist/features/interactive-ui/providers/recharts/chart-tokens.js +44 -0
  59. package/dist/features/interactive-ui/providers/recharts/line-chart.js +3 -2
  60. package/dist/features/interactive-ui/providers/recharts/pie-chart.js +2 -2
  61. package/dist/features/lexical-rich-text-editor/deserialize.d.ts +0 -5
  62. package/dist/features/lexical-rich-text-editor/deserialize.js +0 -5
  63. package/dist/features/lexical-rich-text-editor/mention-node.d.ts +5 -17
  64. package/dist/features/lexical-rich-text-editor/mention-node.js +5 -17
  65. package/dist/features/lexical-rich-text-editor/mention-parser.d.ts +2 -4
  66. package/dist/features/lexical-rich-text-editor/serialize.d.ts +0 -4
  67. package/dist/features/lexical-rich-text-editor/serialize.js +0 -4
  68. package/dist/features/lexical-rich-text-editor/types.d.ts +3 -13
  69. package/dist/features/lexical-rich-text-editor/types.js +3 -13
  70. package/dist/features/mcp-ui/early-message-buffer.d.ts +1 -2
  71. package/dist/features/mcp-ui/early-message-buffer.js +1 -2
  72. package/dist/features/mcp-ui/escape.d.ts +4 -0
  73. package/dist/features/mcp-ui/escape.js +2 -9
  74. package/dist/features/mcp-ui/index.d.ts +1 -0
  75. package/dist/features/mcp-ui/index.js +1 -0
  76. package/dist/features/mcp-ui/protocol.d.ts +2 -5
  77. package/dist/features/mcp-ui/protocol.js +2 -5
  78. package/dist/features/mcp-ui/resource.d.ts +47 -7
  79. package/dist/features/mcp-ui/resource.js +5 -8
  80. package/dist/features/mcp-ui/secret-card.d.ts +115 -0
  81. package/dist/features/mcp-ui/secret-card.js +137 -0
  82. package/dist/features/mcp-ui/surfaces/confirmation.js +5 -0
  83. package/dist/features/mcp-ui/surfaces/fields.d.ts +5 -2
  84. package/dist/features/mcp-ui/surfaces/fields.js +3 -1
  85. package/dist/features/mcp-ui/surfaces/form.d.ts +4 -1
  86. package/dist/features/mcp-ui/surfaces/form.js +65 -3
  87. package/dist/features/mcp-ui/surfaces/outcome.d.ts +2 -0
  88. package/dist/features/mcp-ui/surfaces/text-input.d.ts +14 -6
  89. package/dist/features/mcp-ui/surfaces/text-input.js +16 -2
  90. package/dist/features/mcp-ui/surfaces/tokens.js +1 -1
  91. package/dist/features/memory/react/components/MemoryConnectedPanel.d.ts +1 -1
  92. package/dist/features/memory/react/components/MemoryConnectedPanel.js +0 -1
  93. package/dist/features/panel-kit/helpers/format-timestamp.d.ts +5 -3
  94. package/dist/features/panel-kit/helpers/format-timestamp.js +13 -4
  95. package/dist/features/panel-kit/helpers/permissions.js +2 -0
  96. package/dist/features/panel-kit/helpers/resolve-active-tab-id.js +2 -0
  97. package/dist/features/panel-kit/helpers/standing-draft-local-backup.d.ts +51 -0
  98. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +77 -0
  99. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +8 -0
  100. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +10 -0
  101. package/dist/features/panel-kit/hooks/use-async-action.hooks.d.ts +1 -0
  102. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.d.ts +1 -0
  103. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +1 -0
  104. package/dist/features/panel-kit/hooks/use-serial-writes.hooks.d.ts +8 -0
  105. package/dist/features/panel-kit/hooks/use-serial-writes.hooks.js +3 -0
  106. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.d.ts +211 -0
  107. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +382 -0
  108. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.d.ts +179 -0
  109. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.js +300 -0
  110. package/dist/features/panel-kit/index.d.ts +7 -1
  111. package/dist/features/panel-kit/index.js +5 -1
  112. package/dist/features/resource-dashboard/types.d.ts +8 -41
  113. package/dist/features/resource-dashboard/types.js +3 -26
  114. package/dist/features/tab-strip/constants.d.ts +3 -4
  115. package/dist/features/tab-strip/constants.js +3 -4
  116. package/dist/features/tab-strip/dependencies.d.ts +1 -6
  117. package/dist/features/tab-strip/dependencies.js +1 -6
  118. package/dist/features/tab-strip/react/components/TabBar.d.ts +33 -1
  119. package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +57 -5
  120. package/dist/features/tab-strip/react/components/TabBar.hooks.js +63 -5
  121. package/dist/features/tab-strip/react/components/TabBar.js +12 -1
  122. package/dist/features/tab-strip/rules.d.ts +4 -17
  123. package/dist/features/tab-strip/rules.js +4 -17
  124. package/dist/features/tab-strip/types.d.ts +2 -13
  125. package/dist/icon-name.d.ts +1 -1
  126. package/dist/icon-name.js +1 -0
  127. package/dist/index.d.ts +42 -417
  128. package/dist/index.js +42 -430
  129. package/dist/react/components/CustomSelect.d.ts +6 -2
  130. package/dist/react/components/CustomSelect.js +20 -91
  131. package/dist/react/components/Icon.d.ts +6 -0
  132. package/dist/react/components/Icon.js +12 -0
  133. package/dist/react/components/select-popover.d.ts +106 -0
  134. package/dist/react/components/select-popover.js +210 -0
  135. package/dist/renderers/sandbox-scripts.d.ts +29 -0
  136. package/dist/renderers/sandbox-scripts.js +127 -0
  137. package/dist/renderers/sandboxed-document.js +3 -86
  138. package/dist/renderers/srcdoc/build.d.ts +10 -17
  139. package/dist/renderers/srcdoc/build.js +31 -234
  140. package/dist/styles/variables.css +30 -0
  141. package/dist/theme/apply.d.ts +1 -1
  142. package/dist/theme/apply.js +2 -2
  143. package/dist/theme/color-scheme.d.ts +1 -1
  144. package/dist/theme/default.d.ts +1 -3
  145. package/dist/theme/default.js +2 -10
  146. package/dist/theme/index.d.ts +3 -3
  147. package/dist/theme/index.js +2 -2
  148. package/dist/theme/stylesheet.d.ts +1 -7
  149. package/dist/theme/stylesheet.js +2 -13
  150. package/dist/theme/types.d.ts +1 -38
  151. package/dist/theme/types.js +2 -1
  152. package/dist/theme/validation.d.ts +1 -14
  153. package/dist/theme/validation.js +2 -54
  154. package/dist/utils/endpoint-policy.d.ts +21 -39
  155. package/dist/utils/endpoint-policy.js +19 -107
  156. package/dist/utils/file-system-errors.d.ts +1 -2
  157. package/dist/utils/file-system-errors.js +1 -2
  158. package/dist/utils/ime-composing.d.ts +0 -3
  159. package/dist/utils/ime-composing.js +0 -3
  160. package/dist/utils/platform.d.ts +1 -4
  161. package/dist/utils/platform.js +1 -4
  162. package/dist/utils/smooth-scroll-to-top.d.ts +1 -3
  163. package/dist/utils/smooth-scroll-to-top.js +3 -13
  164. package/dist/utils/timezone.js +1 -5
  165. package/dist/utils/uuid.d.ts +0 -3
  166. package/dist/utils/uuid.js +0 -3
  167. package/dist/utils/visual-stability.d.ts +1 -3
  168. package/dist/utils/visual-stability.js +1 -3
  169. package/package.json +74 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @jini-ai/ui
2
2
 
3
+ ## 0.4.5 — pending release
4
+
5
+ - Add universal `./mcp-ui/secret-card`: `defineSecretCardTool` owns fail-closed emission, cancellation, blank-secret policy, exact-byte submission and safe save-failure reporting. Specs retain domain validation, copy, dynamic fields, persistence and result projections.
6
+ - Inject the daemon's existing `askThenReport` alongside the store; core owns the shared exchange ABI. No daemon or React runtime dependency in this entry.
7
+ - Requires core 0.4.2; build and publish the release set before consumer adoption.
8
+
9
+ ## 0.4.4 — 2026-10-06
10
+
11
+ - Recharts charts paint with host chart tokens `--jini-chart-1..6` / `-grid` / `-axis` / `-cursor` / `-surface` / `-text`, each falling back to a base token; a host that sets no `--jini-chart-N` gets the new warm orange `--jini-chart-default-1..6` palette (light and dark) instead of `--jini-primary`.
12
+ - Behavior change: grid and axis use `--jini-border`/`--jini-muted`, no vertical grid lines, bars get a 4px rounded top and 64px max width, lines are 2px, and the tooltip uses theme surface/ink with a soft accent hover band.
13
+
3
14
  ## 0.4.3 — 2026-10-05
4
15
 
5
16
  - MCP-UI resources can carry an answer deadline: `createUIResource`/`buildConfirmationSurface` take `expiresAtMs`, written to `MCP_UI_EXPIRES_AT_META_KEY`; `readExpiresAt` reads it back (undefined when absent or not a finite number).
package/README.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # `@jini-ai/ui` — generic, product-neutral UI primitives
2
2
 
3
+ `@jini-ai/ui/mcp-ui/secret-card` exports the framework-free `defineSecretCardTool` lifecycle.
4
+ Provide a spec with `prepare`, `form`, `save`, `outcome` and `result`, then register
5
+ `tool.handler({ surfaceExchanges, askThenReport })`; both dependencies come from
6
+ `@jini-ai/daemon/surface-exchanges`. The UI entry has no daemon or React runtime import.
7
+ Transport ports, including `emitSurface`, remain in the handler's optional second object.
8
+
9
+ Secret fields use `{ kind: 'string', name, label, secret: true }` and cannot have `value`.
10
+ Use `allowBlank: true` for optional credentials or a host's preserve-on-update policy; the
11
+ engine sends the blank to `save`, where the host implements that policy. Domain save ports
12
+ receive only declared field names, exact secret bytes and the execution's abort signal.
13
+ Whitespace-only input is refused even with `allowBlank`; nonblank secrets are never trimmed.
14
+ Return safe summaries only; result and outcome projections never receive submitted values.
15
+ `form` retains the builders' translation, locale, details, warning and app options, with an
16
+ optional `cancelLabel`. `outcome` may return undefined for locally reported dismiss/expiry.
17
+ Use `safeError` only to map known error kinds to fixed text and `logFailure` for fixed metadata.
18
+
19
+ The store ABI binds actor, tool and channel. The host must also enforce run scope when routing
20
+ human answers (or inject a store scoped to the run); the exchange ABI does not carry a run ID.
21
+ After coordinator verification, publish core 0.4.2, daemon 0.5.4 and UI 0.4.5 before adoption.
22
+
3
23
  Renamed from `@jini-ai/components` in this session (2026-07-16) once it became clear
4
24
  one "components" bucket undersold the scope: this package is meant to hold more
5
25
  than flat presentational components — pure business logic, hooks, and injectable
@@ -0,0 +1,4 @@
1
+ import type { AdminTheme } from './types.js';
2
+ /** Neutral chrome. Products supply their own brand theme instead of forking CSS. */
3
+ export declare const defaultAdminTheme: AdminTheme;
4
+ //# sourceMappingURL=default.d.ts.map
@@ -0,0 +1,11 @@
1
+ /** Neutral chrome. Products supply their own brand theme instead of forking CSS. */
2
+ export const defaultAdminTheme = Object.freeze({
3
+ name: 'neutral',
4
+ fonts: Object.freeze({ body: 'system-ui, sans-serif', heading: 'system-ui, sans-serif', mono: 'ui-monospace, monospace' }),
5
+ light: Object.freeze({ primary: '#363636', primaryInk: '#ffffff', bg: '#ffffff', surface: '#f5f5f5',
6
+ text: '#202020', muted: '#666666', border: '#d9d9d9', danger: '#b42318', success: '#18763b', warning: '#845800' }),
7
+ dark: Object.freeze({ primary: '#e0e0e0', primaryInk: '#202020', bg: '#171717', surface: '#242424',
8
+ text: '#f0f0f0', muted: '#b3b3b3', border: '#454545', danger: '#ff8b80', success: '#79d69b', warning: '#ebc36b' }),
9
+ radius: '8px', density: 1,
10
+ });
11
+ //# sourceMappingURL=default.js.map
@@ -0,0 +1,8 @@
1
+ import type { AdminTheme, ColorScheme } from './types.js';
2
+ /** Internal document adapter: isolated previews cannot inherit the admin's stylesheet. */
3
+ export declare function renderAdminThemeVariables({ theme }: {
4
+ readonly theme: AdminTheme;
5
+ }, { colorScheme }?: {
6
+ readonly colorScheme?: ColorScheme;
7
+ }): string;
8
+ //# sourceMappingURL=stylesheet.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { adminThemeColorKeys, validateAdminTheme } from './validation.js';
2
+ import { defaultAdminTheme } from './default.js';
3
+ /** Internal document adapter: isolated previews cannot inherit the admin's stylesheet. */
4
+ export function renderAdminThemeVariables({ theme }, { colorScheme = 'light' } = {}) {
5
+ if (colorScheme !== 'light' && colorScheme !== 'dark')
6
+ throw new TypeError('Invalid resolved color scheme');
7
+ const checked = validateAdminTheme({ theme });
8
+ if (!checked.valid)
9
+ throw new TypeError(checked.errors.join('; '));
10
+ const values = adminThemeColorKeys.map((key) => `--jini-${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}:${theme[colorScheme][key]};`);
11
+ values.push(`--jini-font-body:${theme.fonts.body};`, `--jini-font-heading:${theme.fonts.heading};`, `--jini-font-mono:${theme.fonts.mono ?? defaultAdminTheme.fonts.mono};`, `--jini-radius:${theme.radius ?? defaultAdminTheme.radius};`);
12
+ return `:root{color-scheme:${colorScheme};${values.join('')}}`;
13
+ }
14
+ //# sourceMappingURL=stylesheet.js.map
@@ -0,0 +1,39 @@
1
+ /** The intentionally small palette every admin theme supplies in both schemes. */
2
+ export interface AdminThemeColors {
3
+ readonly primary: string;
4
+ readonly primaryInk: string;
5
+ readonly bg: string;
6
+ readonly surface: string;
7
+ readonly text: string;
8
+ readonly muted: string;
9
+ readonly border: string;
10
+ readonly danger: string;
11
+ readonly success: string;
12
+ readonly warning: string;
13
+ }
14
+ /** Product fonts and palette are data. Font loading URLs are stylesheet URLs. */
15
+ export interface AdminTheme {
16
+ readonly name: string;
17
+ readonly fonts: {
18
+ readonly body: string;
19
+ readonly heading: string;
20
+ readonly mono?: string;
21
+ readonly load?: readonly string[];
22
+ };
23
+ readonly light: AdminThemeColors;
24
+ readonly dark: AdminThemeColors;
25
+ readonly radius?: string;
26
+ /** Unitless spacing multiplier, from 0.5 to 2. */
27
+ readonly density?: number;
28
+ }
29
+ export type ColorScheme = 'light' | 'dark';
30
+ export type ColorSchemePreference = ColorScheme | 'system';
31
+ export type MatchMediaPort = (query: string) => {
32
+ readonly matches: boolean;
33
+ addEventListener?: (type: 'change', listener: () => void) => void;
34
+ removeEventListener?: (type: 'change', listener: () => void) => void;
35
+ };
36
+ /** Only these DOM capabilities are needed; no ambient document is consulted. */
37
+ export type AdminThemeTarget = Pick<HTMLElement, 'style' | 'getAttribute' | 'setAttribute' | 'removeAttribute'>;
38
+ export type AdminThemeDocument = Pick<Document, 'head' | 'createElement' | 'baseURI'>;
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,15 @@
1
+ import type { AdminTheme } from './types.js';
2
+ export declare const adminThemeColorKeys: readonly ["primary", "primaryInk", "bg", "surface", "text", "muted", "border", "danger", "success", "warning"];
3
+ type Validation = {
4
+ readonly valid: true;
5
+ readonly theme: AdminTheme;
6
+ } | {
7
+ readonly valid: false;
8
+ readonly errors: readonly string[];
9
+ };
10
+ /** Validate serializable theme data before any DOM or font-loading effect. */
11
+ export declare function validateAdminTheme({ theme }: {
12
+ readonly theme: unknown;
13
+ }): Validation;
14
+ export {};
15
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1,55 @@
1
+ export const adminThemeColorKeys = ['primary', 'primaryInk', 'bg', 'surface', 'text', 'muted', 'border', 'danger', 'success', 'warning'];
2
+ const record = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ const safeValue = (value) => typeof value === 'string' && value.trim().length > 0
4
+ && value.length <= 512 && !/[;{}\x00-\x1f<>\\]/.test(value) && !/url\s*\(|\/\*|\*\//i.test(value);
5
+ // Concrete palette values, deliberately excluding dependent var()/url() expressions.
6
+ const component = '(?:[+-]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:%|deg|rad|grad|turn)?|none)';
7
+ const components = new RegExp(`^(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch)\\(\\s*${component}(?:\\s*,\\s*|\\s+)${component}(?:\\s*,\\s*|\\s+)${component}(?:(?:\\s*[,/]\\s*)${component})?\\s*\\)$`, 'i');
8
+ const colorValue = (value) => safeValue(value)
9
+ && (/^#[\da-f]{3}(?:[\da-f]{1}|[\da-f]{3}|[\da-f]{5})?$/i.test(value)
10
+ || components.test(value)
11
+ || /^(?:transparent|black|white|red|green|blue|gray|grey)$/i.test(value));
12
+ /** Validate serializable theme data before any DOM or font-loading effect. */
13
+ export function validateAdminTheme({ theme }) {
14
+ const errors = [];
15
+ if (!record(theme))
16
+ return { valid: false, errors: ['Theme must be an object'] };
17
+ if (!safeValue(theme.name))
18
+ errors.push('name must be a nonempty safe string');
19
+ const fonts = theme.fonts;
20
+ if (!record(fonts) || !safeValue(fonts.body) || !safeValue(fonts.heading) || (fonts.mono !== undefined && !safeValue(fonts.mono))) {
21
+ errors.push('fonts must provide body and heading font stacks');
22
+ }
23
+ if (record(fonts) && fonts.load !== undefined) {
24
+ if (!Array.isArray(fonts.load) || fonts.load.length > 16 || !fonts.load.every((url) => {
25
+ if (typeof url !== 'string' || url.length > 4096 || /[\x00-\x20<>]/.test(url))
26
+ return false;
27
+ try {
28
+ return new URL(url, 'https://font-base.invalid').protocol === 'https:';
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }))
34
+ errors.push('fonts.load must contain relative or HTTPS stylesheet URLs');
35
+ }
36
+ for (const scheme of ['light', 'dark']) {
37
+ const colors = theme[scheme];
38
+ if (!record(colors)) {
39
+ errors.push(`${scheme} must be a complete palette`);
40
+ continue;
41
+ }
42
+ for (const key of adminThemeColorKeys)
43
+ if (!Object.hasOwn(colors, key) || !colorValue(colors[key]))
44
+ errors.push(`${scheme}.${key} must be a concrete CSS color`);
45
+ for (const key of Object.keys(colors))
46
+ if (!adminThemeColorKeys.includes(key))
47
+ errors.push(`${scheme}.${key} is not a theme color`);
48
+ }
49
+ if (theme.radius !== undefined && (typeof theme.radius !== 'string' || !/^(?:0|\d+(?:\.\d+)?(?:px|rem|em))$/.test(theme.radius)))
50
+ errors.push('radius must be a nonnegative CSS length');
51
+ if (theme.density !== undefined && (typeof theme.density !== 'number' || !Number.isFinite(theme.density) || theme.density < 0.5 || theme.density > 2))
52
+ errors.push('density must be between 0.5 and 2');
53
+ return errors.length ? { valid: false, errors } : { valid: true, theme: theme };
54
+ }
55
+ //# sourceMappingURL=validation.js.map
@@ -4,7 +4,9 @@ export type { SelectOption };
4
4
  /** Use this custom listbox when in-panel search is needed: a native OS option list cannot host it.
5
5
  * Native selects otherwise avoid the accessibility/behavior tradeoffs of a simulated listbox.
6
6
  * JSX stays separate from dropdown state/DOM effects so injected useDropdown can render against a
7
- * fake without geometry or listeners. The body portal escapes clipping/stacking ancestors.
7
+ * fake without geometry or listeners. The body portal escapes clipping/stacking ancestors;
8
+ * inside a native dialog, a dialog-local portal also keeps options in its top layer and out of
9
+ * the inert page. Hosts can supply a container outside their own clipping regions.
8
10
  * Agent selection uses trigger/option clicks rather than native select_option; the -option namespace
9
11
  * keeps caller values from colliding with literal segments. Explicit base handles are not silently
10
12
  * sanitized: invalid caller choices should fail rather than publish a different handle. */
@@ -20,6 +22,12 @@ export interface SelectProps {
20
22
  /** Injectable state/effect seam; rendering tests need not drive portal positioning or listeners. */
21
23
  useDropdown?: typeof useSelectDropdown | undefined;
22
24
  agentHandle?: string | undefined;
25
+ /** Host-owned portal destination. Defaults to the trigger's nearest dialog, then document.body.
26
+ * Within a modal dialog, supply a descendant of that dialog so the panel remains interactive.
27
+ * Keep this container outside scrolling/clipping regions and free of transforms that establish
28
+ * a fixed-position containing block; panel coordinates are viewport-relative.
29
+ * @example <Select {...selectionProps} portalContainer={dialogOverlayElement} /> */
30
+ portalContainer?: Element | DocumentFragment | null | undefined;
23
31
  /** Caller-bound translation function; source copy is the default. */
24
32
  translate?: Translate | undefined;
25
33
  /** Legacy alias for callers porting the original select. */
@@ -34,6 +42,6 @@ export declare function resolveSelectTriggerLabel({ selectedOption, placeholder
34
42
  text: string;
35
43
  className: string;
36
44
  };
37
- /** Controlled searchable listbox with portal positioning and keyboard navigation. */
38
- export declare function Select(props: SelectProps): import("react").JSX.Element;
45
+ /** Controlled searchable listbox with viewport positioning, dialog-local portals and keyboard navigation. */
46
+ export declare function Select(props: SelectProps, _optional?: Record<string, never>): import("react").JSX.Element;
39
47
  //# sourceMappingURL=Select.d.ts.map
@@ -1,10 +1,8 @@
1
+ export { focusableInDomOrder, resolveTabTarget } from '../../../../react/components/select-popover.js';
1
2
  export interface SelectOption {
2
3
  value: string;
3
4
  label: string;
4
5
  }
5
- export declare function focusableInDomOrder({ exclude }: {
6
- exclude: HTMLElement | null;
7
- }): HTMLElement[];
8
6
  export interface PanelPosition {
9
7
  top?: number | undefined;
10
8
  bottom?: number | undefined;
@@ -35,20 +33,17 @@ export declare function usePanelPosition({ open, triggerRef, panelRef, searchInp
35
33
  searchInputRef: React.RefObject<HTMLInputElement | null>;
36
34
  showSearch: boolean;
37
35
  onOutOfView: () => void;
38
- }): {
36
+ }, _optional?: Record<string, never>): {
39
37
  position: PanelPosition | null;
40
38
  setPosition: import("react").Dispatch<import("react").SetStateAction<PanelPosition | null>>;
39
+ updatePosition: () => void;
41
40
  };
41
+ /** Compatibility geometry seam; lifecycle decisions are owned by the shared popover. */
42
42
  export declare function repositionOrClose({ trigger, onOutOfView, setPosition }: {
43
43
  trigger: HTMLElement;
44
44
  onOutOfView: () => void;
45
45
  setPosition: (position: PanelPosition) => void;
46
- }): void;
47
- export declare function resolveTabTarget({ panel, trigger, shiftKey }: {
48
- panel: HTMLElement | null;
49
- trigger: HTMLElement | null;
50
- shiftKey: boolean;
51
- }): HTMLElement | null;
46
+ }, _optional?: Record<string, never>): void;
52
47
  export declare function edgeHighlightIndex({ filteredLength, edge }: {
53
48
  filteredLength: number;
54
49
  edge: "first" | "last";
@@ -94,12 +89,12 @@ export declare function useSelectDropdown({ value, onChange, options, }: {
94
89
  filtered: SelectOption[];
95
90
  selectedOption: SelectOption | null;
96
91
  openPanel: () => void;
97
- closePanel: (opts: {
92
+ closePanel: ({ refocusTrigger }: {
98
93
  refocusTrigger: boolean;
99
94
  }) => void;
100
95
  selectOption: (option: SelectOption) => void;
101
- handleTriggerKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
102
- handlePanelKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
96
+ handleTriggerKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
97
+ handlePanelKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
103
98
  optionId: (index: number) => string;
104
99
  };
105
100
  //# sourceMappingURL=Select.hooks.d.ts.map
@@ -5,18 +5,14 @@
5
5
  * React hook ordering. Extracting the live DOM key handling from pure highlight decisions
6
6
  * reduces the review burden without changing focus or event-order contracts.
7
7
  */
8
- import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
8
+ import { useSelectPopover, useSelectPopoverPosition, repositionSelectPopover, openSelectOnKey, applySelectNavigationKey } from '../../../../react/components/select-popover.js';
9
+ export { focusableInDomOrder, resolveTabTarget } from '../../../../react/components/select-popover.js';
10
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
9
11
  // Below eight options, a search field adds clutter to a list that is already easy to scan.
10
12
  const SEARCH_VISIBILITY_THRESHOLD = 8;
11
13
  // 280px is only a placement heuristic; the panel actual maxHeight and scroll limit use
12
14
  // the remaining viewport space so a low trigger does not open a mostly off-screen panel.
13
15
  const ESTIMATED_PANEL_HEIGHT = 280;
14
- // The panel is portaled to the end of document.body. Native Tab would follow that DOM
15
- // position toward browser chrome; walk the trigger real DOM-order neighbours instead.
16
- const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
17
- export function focusableInDomOrder({ exclude }) {
18
- return Array.from(document.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !exclude || !exclude.contains(el));
19
- }
20
16
  /**
21
17
  * Use fixed viewport-relative coordinates to escape scrolling/overflow-hidden ancestors
22
18
  * without coupling to their layout. maxHeight uses actual remaining space, not the estimate,
@@ -41,97 +37,14 @@ export function buildOptionId({ listboxId, index }) {
41
37
  * Positioning reports a clipped or obscured trigger through onOutOfView; deciding whether
42
38
  * to close belongs to the dropdown lifecycle rather than this reusable measurement hook.
43
39
  */
44
- export function usePanelPosition({ open, triggerRef, panelRef, searchInputRef, showSearch, onOutOfView, }) {
45
- const [position, setPosition] = useState(null);
46
- // Two phases, deliberately in one effect rather than two: (1) `position` starts `null` on every
47
- // open (`closePanel` resets it), so the panel/`createPortal` call below isn't rendered at all yet
48
- // — `panelRef`/`searchInputRef` are still unattached, so focusing them here would silently no-op.
49
- // Computing `position` schedules a state update; because this is a `useLayoutEffect`, React
50
- // re-renders and re-runs layout effects synchronously before the browser paints, rather than
51
- // waiting a frame. (2) On that second pass `position` is non-null, the portal now exists in the
52
- // DOM, and the refs are populated — only now is it correct to move focus into it. Found live via
53
- // this component's own tests: every keyboard-driven test failed until this was split out, because
54
- // `panelRef.current`/`searchInputRef.current` were both still `null` when focus was attempted in
55
- // the same pass that first computed the position.
56
- // biome-ignore lint/correctness/useExhaustiveDependencies: two-phase focus effect — see comment above; refs/showSearch intentionally excluded, only open/position redrive it.
57
- useLayoutEffect(() => {
58
- if (!open)
59
- return;
60
- if (!position) {
61
- if (triggerRef.current)
62
- setPosition(computePosition({ trigger: triggerRef.current }));
63
- return;
64
- }
65
- if (showSearch) {
66
- searchInputRef.current?.focus();
67
- }
68
- else {
69
- panelRef.current?.focus();
70
- }
71
- }, [open, position]);
72
- // Keeps the panel anchored to the trigger if the surrounding page/dialog scrolls or resizes while
73
- // it's open — `.widget-picker-body`'s own `overflow-y: auto` made this a real, live scenario
74
- // rather than a hypothetical one. `scroll` needs the capture phase: an inner element's own scroll
75
- // does not bubble to `window` in the bubble phase. The decision logic itself is
76
- // `repositionOrClose`, above; this effect is only the DOM listener wiring.
77
- //
78
- // Found live in a real browser, not by this component's own test suite: scrolling a host's
79
- // scrolling ancestor far enough that the trigger scrolls out of its own visible clip used to leave
80
- // this effect happily recomputing a mathematically-correct `position` for it — the panel just
81
- // ended up following the trigger to an off-screen (or behind-the-header, obscured) spot, floating
82
- // there indefinitely, nominally still "open" with no visible way to tell.
83
- // biome-ignore lint/correctness/useExhaustiveDependencies: scroll/resize listener wiring — see comment above; triggerRef/onOutOfView intentionally excluded, only `open` re-arms it.
84
- useEffect(() => {
85
- if (!open)
86
- return;
87
- function reposition() {
88
- const el = triggerRef.current;
89
- if (!el)
90
- return;
91
- repositionOrClose({ trigger: el, onOutOfView: onOutOfView, setPosition: setPosition });
92
- }
93
- window.addEventListener("scroll", reposition, true);
94
- window.addEventListener("resize", reposition);
95
- return () => {
96
- window.removeEventListener("scroll", reposition, true);
97
- window.removeEventListener("resize", reposition);
98
- };
99
- }, [open]);
100
- return { position, setPosition };
101
- }
102
- export function repositionOrClose({ trigger, onOutOfView, setPosition }) {
103
- const rect = trigger.getBoundingClientRect();
104
- const outOfViewport = rect.bottom <= 0 || rect.top >= window.innerHeight || rect.right <= 0 || rect.left >= window.innerWidth;
105
- if (outOfViewport) {
106
- onOutOfView();
107
- return;
108
- }
109
- if (typeof document.elementFromPoint === "function") {
110
- const topmostAtCenter = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
111
- const obscured = !topmostAtCenter || !(trigger.contains(topmostAtCenter) || topmostAtCenter.contains(trigger));
112
- if (obscured) {
113
- onOutOfView();
114
- return;
115
- }
116
- }
117
- setPosition(computePosition({ trigger: trigger }));
40
+ export function usePanelPosition({ open, triggerRef, panelRef, searchInputRef, showSearch, onOutOfView, }, _optional = {}) {
41
+ return useSelectPopoverPosition({ open, triggerRef, panelRef, measurePosition: computePosition }, {
42
+ focusInitial: ({ panel }) => (showSearch ? searchInputRef.current : panel)?.focus(), onOutOfView,
43
+ });
118
44
  }
119
- function useCloseOnOutsideClick({ open, triggerRef, panelRef, onOutside, }) {
120
- useEffect(() => {
121
- if (!open)
122
- return;
123
- function onDocMouseDown(e) {
124
- const target = e.target;
125
- if (triggerRef.current?.contains(target))
126
- return;
127
- if (panelRef.current?.contains(target))
128
- return;
129
- onOutside();
130
- }
131
- document.addEventListener("mousedown", onDocMouseDown);
132
- return () => document.removeEventListener("mousedown", onDocMouseDown);
133
- // eslint-disable-next-line react-hooks/exhaustive-deps
134
- }, [open]);
45
+ /** Compatibility geometry seam; lifecycle decisions are owned by the shared popover. */
46
+ export function repositionOrClose({ trigger, onOutOfView, setPosition }, _optional = {}) {
47
+ repositionSelectPopover({ trigger, measurePosition: computePosition, setPosition }, { onOutOfView });
135
48
  }
136
49
  // A search can remove the previously highlighted option; re-anchor to the first match
137
50
  // rather than keep a stale index outside the narrowed list.
@@ -160,13 +73,6 @@ function useScrollHighlightedIntoView({ open, highlightedIndex, optionRefs, }) {
160
73
  el.scrollIntoView({ block: "nearest" });
161
74
  }, [open, highlightedIndex]);
162
75
  }
163
- export function resolveTabTarget({ panel, trigger, shiftKey }) {
164
- const nodes = focusableInDomOrder({ exclude: panel });
165
- const triggerIndex = trigger ? nodes.indexOf(trigger) : -1;
166
- if (triggerIndex < 0)
167
- return null;
168
- return nodes[triggerIndex + (shiftKey ? -1 : 1)] ?? null;
169
- }
170
76
  export function edgeHighlightIndex({ filteredLength, edge }) {
171
77
  if (filteredLength === 0)
172
78
  return -1;
@@ -176,93 +82,25 @@ export function highlightedOptionOrNull({ filtered, highlightedIndex }) {
176
82
  return highlightedIndex >= 0 ? (filtered[highlightedIndex] ?? null) : null;
177
83
  }
178
84
  export function applyHighlightKey({ key, filtered, highlightedIndex, actions }) {
179
- switch (key) {
180
- case "ArrowDown":
181
- actions.preventDefault();
182
- actions.moveHighlight(1);
183
- return true;
184
- case "ArrowUp":
185
- actions.preventDefault();
186
- actions.moveHighlight(-1);
187
- return true;
188
- case "Home":
189
- actions.preventDefault();
190
- actions.setHighlightedIndex(edgeHighlightIndex({ filteredLength: filtered.length, edge: "first" }));
191
- return true;
192
- case "End":
193
- actions.preventDefault();
194
- actions.setHighlightedIndex(edgeHighlightIndex({ filteredLength: filtered.length, edge: "last" }));
195
- return true;
196
- case "Enter": {
197
- actions.preventDefault();
198
- const option = highlightedOptionOrNull({ filtered: filtered, highlightedIndex: highlightedIndex });
199
- if (option)
200
- actions.selectOption(option);
201
- return true;
202
- }
203
- default:
204
- return false;
205
- }
85
+ return applySelectNavigationKey({ key, actions: {
86
+ preventDefault: actions.preventDefault, move: actions.moveHighlight,
87
+ edge: (edge) => actions.setHighlightedIndex(edgeHighlightIndex({ filteredLength: filtered.length, edge })),
88
+ choose: () => {
89
+ const option = highlightedOptionOrNull({ filtered, highlightedIndex });
90
+ if (option)
91
+ actions.selectOption(option);
92
+ },
93
+ } });
206
94
  }
207
95
  // Keep live event/focus effects in the key handler; move the trigger-neighbour calculation
208
96
  // out so changing the DOM effects does not bury the independently testable focus decision.
209
- function useSelectKeyboardHandlers({ disabled, open, openPanel, closePanel, moveHighlight, selectOption, highlightedIndex, setHighlightedIndex, filtered, triggerRef, panelRef, }) {
210
- function handleTriggerKeyDown(e) {
211
- if (disabled || open)
212
- return;
213
- switch (e.key) {
214
- case "Enter":
215
- case " ":
216
- case "ArrowDown":
217
- case "ArrowUp":
218
- e.preventDefault();
219
- openPanel();
220
- break;
221
- default:
222
- break;
223
- }
224
- }
225
- // Escape/Tab need live propagation and focus effects. Highlight/selection keys delegate
226
- // to the pure helper; preventDefault is injected to preserve its order before each action.
227
- function handlePanelKeyDown(e) {
228
- if (e.key === "Escape") {
229
- // Must not bubble: a host dialog (`WidgetPickerDialog`) listens for Escape on `document`
230
- // to cancel the whole modal. Without stopping propagation here, closing just this dropdown
231
- // would also close the dialog underneath it — the regression `Select.unit.test.tsx` pins.
232
- e.preventDefault();
233
- e.stopPropagation();
234
- closePanel({ refocusTrigger: true });
235
- return;
236
- }
237
- if (e.key === "Tab") {
238
- // See `resolveTabTarget`'s own comment: walk the trigger's real DOM-order neighbours rather
239
- // than let native Tab handling run, since this event is bubbling from a panel portaled to
240
- // the end of `document.body`, not sitting next to the trigger in the DOM.
241
- const target = resolveTabTarget({ panel: panelRef.current, trigger: triggerRef.current, shiftKey: e.shiftKey });
242
- e.preventDefault();
243
- closePanel({ refocusTrigger: false });
244
- target?.focus();
245
- return;
246
- }
247
- applyHighlightKey({ key: e.key, filtered: filtered, highlightedIndex: highlightedIndex, actions: {
248
- preventDefault: () => e.preventDefault(),
249
- moveHighlight,
250
- setHighlightedIndex,
251
- selectOption,
252
- } });
253
- }
254
- return { handleTriggerKeyDown, handlePanelKeyDown };
255
- }
256
97
  /**
257
98
  * Separate hook state from rendering so search visibility, wraparound, placement and
258
99
  * out-of-view closing can be asserted directly without coupling every case to portal DOM.
259
100
  */
260
101
  export function useSelectDropdown({ value, onChange, options, }, { disabled } = {}) {
261
- const [open, setOpen] = useState(false);
262
102
  const [query, setQuery] = useState("");
263
103
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
264
- const triggerRef = useRef(null);
265
- const panelRef = useRef(null);
266
104
  const searchInputRef = useRef(null);
267
105
  // Index -> `<li>` node, so the highlight-follow effect below can scroll the right row into view
268
106
  // without an id-based `querySelector` (this component's ids come from `useId()`, which can
@@ -271,6 +109,11 @@ export function useSelectDropdown({ value, onChange, options, }, { disabled } =
271
109
  const optionRefs = useRef(new Map());
272
110
  const listboxId = useId();
273
111
  const showSearch = options.length >= SEARCH_VISIBILITY_THRESHOLD;
112
+ const popover = useSelectPopover({ measurePosition: computePosition }, {
113
+ disabled, dismissWhenHidden: true,
114
+ focusInitial: ({ panel }) => (showSearch ? searchInputRef.current : panel)?.focus(),
115
+ });
116
+ const { open, position, triggerRef, panelRef, closePanel } = popover;
274
117
  const filtered = useMemo(() => {
275
118
  if (!showSearch || !query.trim())
276
119
  return options;
@@ -283,15 +126,9 @@ export function useSelectDropdown({ value, onChange, options, }, { disabled } =
283
126
  return;
284
127
  const initialIndex = options.findIndex((o) => o.value === value);
285
128
  setQuery("");
286
- setOpen(true);
129
+ popover.openPanel();
287
130
  setHighlightedIndex(initialIndex >= 0 ? initialIndex : options.length ? 0 : -1);
288
131
  }
289
- function closePanel(opts) {
290
- setOpen(false);
291
- setPosition(null); // forces the effect below through its "measure, then focus" two-phase sequence again on the next open, rather than focusing at a stale, pre-close position
292
- if (opts.refocusTrigger)
293
- triggerRef.current?.focus();
294
- }
295
132
  function selectOption(option) {
296
133
  onChange(option.value);
297
134
  closePanel({ refocusTrigger: true });
@@ -303,35 +140,20 @@ export function useSelectDropdown({ value, onChange, options, }, { disabled } =
303
140
  return (current + delta + filtered.length) % filtered.length;
304
141
  });
305
142
  }
306
- const { position, setPosition } = usePanelPosition({
307
- open,
308
- triggerRef,
309
- panelRef,
310
- searchInputRef,
311
- showSearch,
312
- onOutOfView: () => closePanel({ refocusTrigger: false }),
313
- });
314
- useCloseOnOutsideClick({
315
- open,
316
- triggerRef,
317
- panelRef,
318
- onOutside: () => closePanel({ refocusTrigger: false }),
319
- });
320
143
  useResetHighlightOnQueryChange({ open, query, hasMatches: filtered.length > 0, setHighlightedIndex });
321
144
  useScrollHighlightedIntoView({ open, highlightedIndex, optionRefs });
322
- const { handleTriggerKeyDown, handlePanelKeyDown } = useSelectKeyboardHandlers({
323
- disabled,
324
- open,
325
- openPanel,
326
- closePanel,
327
- moveHighlight,
328
- selectOption,
329
- highlightedIndex,
330
- setHighlightedIndex,
331
- filtered,
332
- triggerRef,
333
- panelRef,
334
- });
145
+ function handleTriggerKeyDown(event) {
146
+ openSelectOnKey({ event, open, openPanel }, { disabled });
147
+ }
148
+ // Escape/Tab need live propagation and focus effects. Highlight/selection keys delegate
149
+ // to the pure helper; preventDefault is injected to preserve its order before each action.
150
+ function handlePanelKeyDown(event) {
151
+ if (popover.handleDismissKey(event))
152
+ return;
153
+ applyHighlightKey({ key: event.key, filtered, highlightedIndex, actions: {
154
+ preventDefault: () => event.preventDefault(), moveHighlight, setHighlightedIndex, selectOption,
155
+ } });
156
+ }
335
157
  const optionId = (index) => buildOptionId({ listboxId: listboxId, index: index });
336
158
  return {
337
159
  open,
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { resolveSelectPortalContainer } from '../../../../react/components/select-popover.js';
2
3
  import { createPortal } from "react-dom";
3
4
  import { agentHandle } from "@jini-ai/agentic";
4
5
  import { buildAgentListHandles } from "@jini-ai/agentic";
@@ -20,8 +21,8 @@ function resolveSelectTriggerActiveDescendant(open, highlightedIndex, optionId)
20
21
  function resolveSelectTriggerHandleProps(base, ariaLabel, placeholder) {
21
22
  return base ? agentHandle({ handle: base }, { role: "button", label: ariaLabel ?? placeholder ?? "Select an option" }) : {};
22
23
  }
23
- /** Controlled searchable listbox with portal positioning and keyboard navigation. */
24
- export function Select(props) {
24
+ /** Controlled searchable listbox with viewport positioning, dialog-local portals and keyboard navigation. */
25
+ export function Select(props, _optional = {}) {
25
26
  const { value, onChange, options, placeholder, id, disabled, useDropdown = useSelectDropdown, agentHandle: base, translate, t = translate ?? ((key) => key) } = props;
26
27
  const ariaLabel = props["aria-label"];
27
28
  const ariaLabelledBy = props["aria-labelledby"];
@@ -40,7 +41,7 @@ export function Select(props) {
40
41
  // when `base` itself is unset, so `SelectPanel` can tell "opted out" apart from "no options match".
41
42
  const optionHandles = base ? buildAgentListHandles({ prefix: `${base}-option`, ids: filtered.map((option) => option.value) }) : undefined;
42
43
  return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", ref: triggerRef, id: id, className: "select-trigger", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeDescendant, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, onClick: () => (open ? closePanel({ refocusTrigger: false }) : openPanel()), onKeyDown: handleTriggerKeyDown, ...resolveSelectTriggerHandleProps(base, ariaLabel, placeholder), children: [_jsx("span", { className: triggerLabel.className, children: triggerLabel.text }), _jsx("span", { className: "select-trigger-chevron", "aria-hidden": "true" })] }), open && position
43
- ? createPortal(_jsx(SelectPanel, { panelRef: panelRef, position: position, showSearch: showSearch, searchInputRef: searchInputRef, query: query, setQuery: setQuery, filtered: filtered, value: value, highlightedIndex: highlightedIndex, listboxId: listboxId, optionId: optionId, setOptionRef: setOptionRef, onHighlight: setHighlightedIndex, onSelect: selectOption, onKeyDown: handlePanelKeyDown, searchHandle: base ? `${base}-search` : undefined, optionHandles: optionHandles, t: t }), document.body)
44
+ ? createPortal(_jsx(SelectPanel, { panelRef: panelRef, position: position, showSearch: showSearch, searchInputRef: searchInputRef, query: query, setQuery: setQuery, filtered: filtered, value: value, highlightedIndex: highlightedIndex, listboxId: listboxId, optionId: optionId, setOptionRef: setOptionRef, onHighlight: setHighlightedIndex, onSelect: selectOption, onKeyDown: handlePanelKeyDown, searchHandle: base ? `${base}-search` : undefined, optionHandles: optionHandles, t: t }), resolveSelectPortalContainer({ trigger: triggerRef.current }, { container: props.portalContainer }))
44
45
  : null] }));
45
46
  }
46
47
  //# sourceMappingURL=Select.js.map