@itwin/itwinui-react 5.0.0-alpha.10 → 5.0.0-alpha.12

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 (69) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +13 -2
  3. package/dist/DEV/bricks/Checkbox.js +7 -14
  4. package/dist/DEV/bricks/Description.js +7 -14
  5. package/dist/DEV/bricks/DropdownMenu.js +58 -20
  6. package/dist/DEV/bricks/Field.internal.js +47 -0
  7. package/dist/DEV/bricks/Field.js +116 -85
  8. package/dist/DEV/bricks/Icon.js +144 -7
  9. package/dist/DEV/bricks/Label.js +4 -10
  10. package/dist/DEV/bricks/Radio.js +7 -14
  11. package/dist/DEV/bricks/Root.internal.js +17 -0
  12. package/dist/DEV/bricks/Root.js +73 -27
  13. package/dist/DEV/bricks/Select.js +9 -15
  14. package/dist/DEV/bricks/Spinner.js +23 -8
  15. package/dist/DEV/bricks/Switch.js +8 -15
  16. package/dist/DEV/bricks/Table.js +71 -37
  17. package/dist/DEV/bricks/Tabs.js +4 -29
  18. package/dist/DEV/bricks/TextBox.js +23 -37
  19. package/dist/DEV/bricks/Tooltip.js +1 -1
  20. package/dist/DEV/bricks/TreeItem.js +92 -10
  21. package/dist/DEV/bricks/index.js +3 -1
  22. package/dist/DEV/bricks/styles.css.js +1 -1
  23. package/dist/DEV/bricks/~hooks.js +3 -1
  24. package/dist/DEV/bricks/~utils.js +17 -0
  25. package/dist/DEV/foundations/styles.css.js +1 -1
  26. package/dist/bricks/Badge.d.ts +1 -1
  27. package/dist/bricks/Checkbox.d.ts +13 -5
  28. package/dist/bricks/Checkbox.js +7 -14
  29. package/dist/bricks/Description.d.ts +2 -6
  30. package/dist/bricks/Description.js +7 -14
  31. package/dist/bricks/DropdownMenu.d.ts +9 -9
  32. package/dist/bricks/DropdownMenu.js +57 -19
  33. package/dist/bricks/Field.d.ts +63 -27
  34. package/dist/bricks/Field.internal.d.ts +33 -0
  35. package/dist/bricks/Field.internal.js +47 -0
  36. package/dist/bricks/Field.js +111 -84
  37. package/dist/bricks/Icon.d.ts +12 -1
  38. package/dist/bricks/Icon.js +142 -7
  39. package/dist/bricks/Label.d.ts +5 -12
  40. package/dist/bricks/Label.js +4 -10
  41. package/dist/bricks/Radio.d.ts +14 -5
  42. package/dist/bricks/Radio.js +7 -14
  43. package/dist/bricks/Root.d.ts +12 -0
  44. package/dist/bricks/Root.internal.d.ts +6 -0
  45. package/dist/bricks/Root.internal.js +17 -0
  46. package/dist/bricks/Root.js +73 -27
  47. package/dist/bricks/Select.d.ts +29 -12
  48. package/dist/bricks/Select.js +9 -15
  49. package/dist/bricks/Spinner.js +23 -8
  50. package/dist/bricks/Switch.d.ts +12 -5
  51. package/dist/bricks/Switch.js +8 -15
  52. package/dist/bricks/Table.d.ts +94 -37
  53. package/dist/bricks/Table.js +69 -36
  54. package/dist/bricks/Tabs.d.ts +3 -4
  55. package/dist/bricks/Tabs.js +4 -29
  56. package/dist/bricks/TextBox.d.ts +42 -19
  57. package/dist/bricks/TextBox.js +23 -37
  58. package/dist/bricks/Tooltip.js +1 -1
  59. package/dist/bricks/TreeItem.d.ts +53 -9
  60. package/dist/bricks/TreeItem.js +81 -10
  61. package/dist/bricks/index.d.ts +2 -1
  62. package/dist/bricks/index.js +3 -1
  63. package/dist/bricks/styles.css.js +1 -1
  64. package/dist/bricks/~hooks.d.ts +8 -0
  65. package/dist/bricks/~hooks.js +3 -1
  66. package/dist/bricks/~utils.d.ts +8 -0
  67. package/dist/bricks/~utils.js +17 -0
  68. package/dist/foundations/styles.css.js +1 -1
  69. package/package.json +1 -1
@@ -2,10 +2,21 @@ import { jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import cx from "classnames";
4
4
  import { Role } from "@ariakit/react/role";
5
- import { forwardRef } from "./~utils.js";
5
+ import {
6
+ forwardRef,
7
+ getOwnerDocument,
8
+ parseDOM
9
+ } from "./~utils.js";
10
+ import {
11
+ HtmlSanitizerContext,
12
+ spriteSheetId,
13
+ useRootNode
14
+ } from "./Root.internal.js";
15
+ import { useLatestRef, useSafeContext } from "./~hooks.js";
6
16
  const Icon = forwardRef((props, forwardedRef) => {
7
- const { href, size, alt, ...rest } = props;
17
+ const { href: hrefProp, size, alt, ...rest } = props;
8
18
  const isDecorative = !alt;
19
+ const hrefBase = useNormalizedHrefBase(hrefProp);
9
20
  return /* @__PURE__ */ jsx(
10
21
  Role.svg,
11
22
  {
@@ -16,19 +27,81 @@ const Icon = forwardRef((props, forwardedRef) => {
16
27
  "data-kiwi-size": size,
17
28
  className: cx("\u{1F95D}-icon", props.className),
18
29
  ref: forwardedRef,
19
- children: href ? /* @__PURE__ */ jsx("use", { href: toIconHref(href, size) }) : null
30
+ children: hrefBase ? /* @__PURE__ */ jsx("use", { href: toIconHref(hrefBase, size) }) : null
20
31
  }
21
32
  );
22
33
  });
23
- function toIconHref(href, size) {
24
- const separator = href.includes("#") ? "--" : "#";
34
+ function toIconHref(hrefBase, size) {
35
+ const separator = hrefBase.includes("#") ? "--" : "#";
25
36
  const suffix = toIconId(size);
26
- return `${href}${separator}${suffix}`;
37
+ return `${hrefBase}${separator}${suffix}`;
27
38
  }
28
39
  function toIconId(size) {
29
40
  if (size === "large") return "icon-large";
30
41
  return "icon";
31
42
  }
43
+ function useNormalizedHrefBase(rawHref) {
44
+ const generatedId = React.useId();
45
+ const sanitizeHtml = useLatestRef(useSafeContext(HtmlSanitizerContext));
46
+ const rootNode = useRootNode();
47
+ const inlineHref = React.useRef(void 0);
48
+ const getClientSnapshot = () => {
49
+ const ownerDocument = getOwnerDocument(rootNode);
50
+ if (!rawHref || !ownerDocument) return void 0;
51
+ if (isHttpProtocol(rawHref, ownerDocument)) return rawHref;
52
+ return inlineHref.current;
53
+ };
54
+ const subscribe = React.useCallback(
55
+ (notify) => {
56
+ const ownerDocument = getOwnerDocument(rootNode);
57
+ const spriteSheet = ownerDocument?.getElementById(spriteSheetId);
58
+ if (!rawHref || !ownerDocument || !spriteSheet) return () => {
59
+ };
60
+ if (isHttpProtocol(rawHref, ownerDocument)) return () => {
61
+ };
62
+ const cache = spriteSheet[Symbol.for("\u{1F95D}")]?.icons;
63
+ if (!cache) return () => {
64
+ };
65
+ const prefix = `\u{1F95D}${generatedId}`;
66
+ if (cache.has(rawHref)) {
67
+ inlineHref.current = cache.get(rawHref);
68
+ notify();
69
+ return () => {
70
+ };
71
+ }
72
+ const abortController = new AbortController();
73
+ const { signal } = abortController;
74
+ (async () => {
75
+ const response = await fetch(rawHref, { signal });
76
+ if (!response.ok) throw new Error(`Failed to fetch ${rawHref}`);
77
+ const fetchedSvgString = sanitizeHtml.current(await response.text());
78
+ const parsedSvgContent = parseDOM(fetchedSvgString, {
79
+ ownerDocument
80
+ });
81
+ const symbols = parsedSvgContent.querySelectorAll("symbol");
82
+ for (const symbol of symbols) {
83
+ symbol.id = `${prefix}--${symbol.id}`;
84
+ if (ownerDocument.getElementById(symbol.id)) continue;
85
+ spriteSheet.appendChild(symbol.cloneNode(true));
86
+ }
87
+ inlineHref.current = `#${prefix}`;
88
+ cache.set(rawHref, inlineHref.current);
89
+ if (!signal.aborted) notify();
90
+ })();
91
+ return () => abortController.abort();
92
+ },
93
+ [rawHref, rootNode, sanitizeHtml, generatedId]
94
+ );
95
+ return React.useSyncExternalStore(
96
+ subscribe,
97
+ getClientSnapshot,
98
+ () => rawHref
99
+ );
100
+ }
101
+ function isHttpProtocol(url, ownerDocument) {
102
+ const { protocol } = new URL(url, ownerDocument.baseURI);
103
+ return ["http:", "https:"].includes(protocol);
104
+ }
32
105
  const DisclosureArrow = forwardRef(
33
106
  (props, forwardedRef) => {
34
107
  const { direction = "down", ...rest } = props;
@@ -112,9 +185,71 @@ const Dismiss = forwardRef(
112
185
  );
113
186
  }
114
187
  );
188
+ const StatusWarning = forwardRef(
189
+ (props, forwardedRef) => {
190
+ return /* @__PURE__ */ jsx(
191
+ Icon,
192
+ {
193
+ ...props,
194
+ render: /* @__PURE__ */ jsx(
195
+ Role.svg,
196
+ {
197
+ width: "16",
198
+ height: "16",
199
+ fill: "currentColor",
200
+ viewBox: "0 0 16 16",
201
+ render: props.render,
202
+ children: /* @__PURE__ */ jsx(
203
+ "path",
204
+ {
205
+ fill: "currentColor",
206
+ fillRule: "evenodd",
207
+ d: "M8.354 2.06a.5.5 0 0 0-.708 0L2.061 7.647a.5.5 0 0 0 0 .707l5.585 5.586a.5.5 0 0 0 .708 0l5.585-5.586a.5.5 0 0 0 0-.707L8.354 2.061Zm-1.415-.707a1.5 1.5 0 0 1 2.122 0l5.585 5.586a1.5 1.5 0 0 1 0 2.122l-5.585 5.585a1.5 1.5 0 0 1-2.122 0L1.354 9.061a1.5 1.5 0 0 1 0-2.122l5.585-5.586ZM8.75 10.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM8.5 8.5v-3a.5.5 0 0 0-1 0v3a.5.5 0 0 0 1 0Z",
208
+ clipRule: "evenodd"
209
+ }
210
+ )
211
+ }
212
+ ),
213
+ ref: forwardedRef
214
+ }
215
+ );
216
+ }
217
+ );
218
+ const MoreHorizontal = forwardRef(
219
+ (props, forwardedRef) => {
220
+ return /* @__PURE__ */ jsx(
221
+ Icon,
222
+ {
223
+ ...props,
224
+ render: /* @__PURE__ */ jsx(
225
+ Role.svg,
226
+ {
227
+ width: "16",
228
+ height: "16",
229
+ viewBox: "0 0 16 16",
230
+ fill: "none",
231
+ render: props.render,
232
+ children: /* @__PURE__ */ jsx(
233
+ "path",
234
+ {
235
+ fill: "currentColor",
236
+ fillRule: "evenodd",
237
+ d: "M3 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm6-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0Zm5 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z",
238
+ clipRule: "evenodd"
239
+ }
240
+ )
241
+ }
242
+ ),
243
+ ref: forwardedRef
244
+ }
245
+ );
246
+ }
247
+ );
115
248
  export {
116
249
  Checkmark,
117
250
  DisclosureArrow,
118
251
  Dismiss,
119
- Icon
252
+ Icon,
253
+ MoreHorizontal,
254
+ StatusWarning
120
255
  };
@@ -2,24 +2,17 @@ import { type BaseProps } from "./~utils.js";
2
2
  interface LabelProps extends BaseProps<"label"> {
3
3
  }
4
4
  /**
5
- * A styled wrapper over the HTML `<label>` element, used for labelling form controls.
5
+ * A styled wrapper over the HTML `<label>` element, used for labelling form
6
+ * controls.
6
7
  *
7
- * Can be used standalone:
8
-
8
+ * Example usage:
9
9
  * ```tsx
10
10
  * <Label htmlFor="my-input">Label</Label>
11
11
  * <TextBox.Input id="my-input" />
12
12
  * ```
13
13
  *
14
- * Or within a `Field` component to automatically manage ID associations:
15
- *
16
- * ```tsx
17
- * <Field>
18
- * <Label>Label</Label>
19
- * <TextBox.Input />
20
- * </Field>
21
- * ```
22
- *
14
+ * See `Field.Label` for convenient usage with form controls (e.g. automatic
15
+ * association with adjacent form control).
23
16
  */
24
17
  export declare const Label: import("react").ForwardRefExoticComponent<LabelProps & import("react").RefAttributes<HTMLElement | HTMLLabelElement>>;
25
18
  export {};
@@ -2,19 +2,13 @@ import { jsx } from "react/jsx-runtime";
2
2
  import cx from "classnames";
3
3
  import { Role } from "@ariakit/react/role";
4
4
  import { forwardRef } from "./~utils.js";
5
- import { FieldLabel } from "./Field.js";
6
5
  const Label = forwardRef((props, forwardedRef) => {
7
6
  return /* @__PURE__ */ jsx(
8
- FieldLabel,
7
+ Role.label,
9
8
  {
10
- render: /* @__PURE__ */ jsx(
11
- Role.label,
12
- {
13
- ...props,
14
- className: cx("\u{1F95D}-label", props.className),
15
- ref: forwardedRef
16
- }
17
- )
9
+ ...props,
10
+ className: cx("\u{1F95D}-label", props.className),
11
+ ref: forwardedRef
18
12
  }
19
13
  );
20
14
  });
@@ -7,13 +7,22 @@ interface RadioProps extends InputBaseProps, RadioOwnProps {
7
7
  /**
8
8
  * A styled radio input element, typically used for selecting a single option from a list.
9
9
  *
10
- * Works well with the `Field` and `Label` components.
10
+ * Use with the `Field` components to automatically handle ID associations for
11
+ * labels and descriptions:
12
+ * ```tsx
13
+ * <Field.Root>
14
+ * <Field.Label>Choose one</Field.Label>
15
+ * <Field.Control render={<Radio />} />
16
+ * </Field.Root>
17
+ * ```
11
18
  *
19
+ * Without the `Field` components you will need to manually associate labels,
20
+ * descriptions, etc.:
12
21
  * ```tsx
13
- * <Field>
14
- * <Label>Choose one</Label>
15
- * <Radio />
16
- * </Field>
22
+ * <Radio id="editor-vim" name="editor" value="vim" />
23
+ * <Label htmlFor="editor-vim">Vim</Label>
24
+ * <Radio id="editor-emacs" name="editor" value="emacs" />
25
+ * <Label htmlFor="editor-emacs">Emacs</Label>
17
26
  * ```
18
27
  *
19
28
  * Underneath, it's an HTML radio input, i.e. `<input type="radio">`, so it supports the same props,
@@ -3,24 +3,17 @@ import cx from "classnames";
3
3
  import {
4
4
  Radio as AkRadio
5
5
  } from "@ariakit/react/radio";
6
- import { FieldControl } from "./Field.js";
7
6
  import { forwardRef } from "./~utils.js";
7
+ import { useFieldControlType } from "./Field.internal.js";
8
8
  const Radio = forwardRef((props, forwardedRef) => {
9
- const { id, ...rest } = props;
9
+ useFieldControlType("checkable");
10
10
  return /* @__PURE__ */ jsx(
11
- FieldControl,
11
+ AkRadio,
12
12
  {
13
- type: "checkable",
14
- id,
15
- render: /* @__PURE__ */ jsx(
16
- AkRadio,
17
- {
18
- accessibleWhenDisabled: true,
19
- ...rest,
20
- className: cx("\u{1F95D}-checkbox", "\u{1F95D}-radio", props.className),
21
- ref: forwardedRef
22
- }
23
- )
13
+ accessibleWhenDisabled: true,
14
+ ...props,
15
+ className: cx("\u{1F95D}-checkbox", "\u{1F95D}-radio", props.className),
16
+ ref: forwardedRef
24
17
  }
25
18
  );
26
19
  });
@@ -17,6 +17,18 @@ interface RootProps extends BaseProps {
17
17
  * The density to use for all components under the Root.
18
18
  */
19
19
  density: "dense";
20
+ /**
21
+ * An HTML sanitizer function that will be used across all components wherever DOM elements
22
+ * are created from HTML strings.
23
+ *
24
+ * When this prop is not passed, sanitization will be skipped.
25
+ *
26
+ * Example:
27
+ * ```tsx
28
+ * unstablized_htmlSanitizer={DOMPurify.sanitize}
29
+ * ```
30
+ */
31
+ unstable_htmlSanitizer?: (html: string) => string;
20
32
  }
21
33
  /**
22
34
  * Component to be used at the root of your application. It ensures that kiwi styles and fonts are loaded
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ export declare const RootNodeContext: React.Context<Document | ShadowRoot | null>;
3
+ /** Returns the closest [rootNode](https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode). */
4
+ export declare function useRootNode(): Document | ShadowRoot | null | undefined;
5
+ export declare const spriteSheetId = "\uD83E\uDD5D-inline-sprites";
6
+ export declare const HtmlSanitizerContext: React.Context<((html: string) => string) | undefined>;
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import { useIsClient } from "./~hooks.js";
3
+ const RootNodeContext = React.createContext(null);
4
+ function useRootNode() {
5
+ const maybeRootNode = React.useContext(RootNodeContext);
6
+ const isClient = useIsClient();
7
+ if (!isClient) return void 0;
8
+ return maybeRootNode;
9
+ }
10
+ const spriteSheetId = "\u{1F95D}-inline-sprites";
11
+ const HtmlSanitizerContext = React.createContext(void 0);
12
+ export {
13
+ HtmlSanitizerContext,
14
+ RootNodeContext,
15
+ spriteSheetId,
16
+ useRootNode
17
+ };
@@ -6,15 +6,33 @@ import { PortalContext } from "@ariakit/react/portal";
6
6
  import cx from "classnames";
7
7
  import foundationsCss from "../foundations/styles.css.js";
8
8
  import bricksCss from "./styles.css.js";
9
- import { forwardRef, isBrowser } from "./~utils.js";
10
- import { useIsClient, useMergedRefs } from "./~hooks.js";
9
+ import {
10
+ forwardRef,
11
+ getOwnerDocument,
12
+ identity,
13
+ isBrowser,
14
+ isDocument
15
+ } from "./~utils.js";
16
+ import { useLayoutEffect, useMergedRefs } from "./~hooks.js";
17
+ import {
18
+ HtmlSanitizerContext,
19
+ RootNodeContext,
20
+ spriteSheetId,
21
+ useRootNode
22
+ } from "./Root.internal.js";
11
23
  const css = foundationsCss + bricksCss;
12
24
  const Root = forwardRef((props, forwardedRef) => {
13
- const { children, synchronizeColorScheme = false, ...rest } = props;
25
+ const {
26
+ children,
27
+ synchronizeColorScheme = false,
28
+ unstable_htmlSanitizer = identity,
29
+ ...rest
30
+ } = props;
14
31
  const [portalContainer, setPortalContainer] = React.useState(null);
15
32
  return /* @__PURE__ */ jsxs(RootInternal, { ...rest, ref: forwardedRef, children: [
16
33
  /* @__PURE__ */ jsx(Styles, {}),
17
34
  /* @__PURE__ */ jsx(Fonts, {}),
35
+ /* @__PURE__ */ jsx(InlineSpriteSheet, {}),
18
36
  synchronizeColorScheme ? /* @__PURE__ */ jsx(SynchronizeColorScheme, { colorScheme: props.colorScheme }) : null,
19
37
  /* @__PURE__ */ jsx(
20
38
  PortalContainer,
@@ -24,13 +42,9 @@ const Root = forwardRef((props, forwardedRef) => {
24
42
  ref: setPortalContainer
25
43
  }
26
44
  ),
27
- /* @__PURE__ */ jsx(PortalContext.Provider, { value: portalContainer, children })
45
+ /* @__PURE__ */ jsx(PortalContext.Provider, { value: portalContainer, children: /* @__PURE__ */ jsx(HtmlSanitizerContext.Provider, { value: unstable_htmlSanitizer, children }) })
28
46
  ] });
29
47
  });
30
- const RootNodeContext = React.createContext(null);
31
- function useRootNode() {
32
- return React.useContext(RootNodeContext);
33
- }
34
48
  const RootInternal = forwardRef(
35
49
  (props, forwardedRef) => {
36
50
  const { children, colorScheme, density, ...rest } = props;
@@ -71,10 +85,9 @@ function SynchronizeColorScheme({
71
85
  return null;
72
86
  }
73
87
  const PortalContainer = forwardRef((props, forwardedRef) => {
74
- const isClient = useIsClient();
75
88
  const rootNode = useRootNode();
76
- if (!isClient) return null;
77
- const destination = rootNode && isDocument(rootNode) ? rootNode.body : rootNode;
89
+ if (!rootNode) return null;
90
+ const destination = isDocument(rootNode) ? rootNode.body : rootNode;
78
91
  if (!destination) return null;
79
92
  return ReactDOM.createPortal(
80
93
  /* @__PURE__ */ jsx(
@@ -94,35 +107,46 @@ function Styles() {
94
107
  const rootNode = useRootNode();
95
108
  useLayoutEffect(() => {
96
109
  if (!rootNode) return;
97
- loadStyles(rootNode, { css });
110
+ const { cleanup } = loadStyles(rootNode, { css });
111
+ return cleanup;
98
112
  }, [rootNode]);
99
113
  return null;
100
114
  }
101
- const styleSheets = /* @__PURE__ */ new WeakMap();
102
- function loadStyles(rootNode, { css: css2 }) {
115
+ const styleSheets = new Map(
116
+ Object.entries({ default: /* @__PURE__ */ new WeakMap() })
117
+ );
118
+ function loadStyles(rootNode, { css: css2, key = "default" }) {
119
+ let cleanup = () => {
120
+ };
103
121
  const loaded = (() => {
104
122
  if (!isBrowser) return false;
105
123
  const ownerDocument = getOwnerDocument(rootNode);
106
124
  const _window = getWindow(rootNode);
107
125
  if (!ownerDocument || !_window) return false;
108
- if (!supportsAdoptedStylesheets && !rootNode.querySelector("style[data-kiwi]")) {
126
+ if (!supportsAdoptedStylesheets && !rootNode.querySelector(`style[data-kiwi="${key}"]`)) {
109
127
  const styleElement = ownerDocument.createElement("style");
110
- styleElement.dataset.kiwi = "true";
128
+ styleElement.dataset.kiwi = key;
111
129
  styleElement.textContent = css2;
112
130
  (rootNode.head || rootNode).appendChild(styleElement);
131
+ cleanup = () => styleElement.remove();
113
132
  return true;
114
133
  }
115
- const styleSheet = styleSheets.get(_window) || new _window.CSSStyleSheet();
116
- if (!styleSheets.has(_window)) {
117
- styleSheets.set(_window, styleSheet);
134
+ const styleSheet = styleSheets.get(key)?.get(_window) || new _window.CSSStyleSheet();
135
+ if (!styleSheets.get(key)?.has(_window)) {
136
+ styleSheets.get(key)?.set(_window, styleSheet);
118
137
  }
119
138
  styleSheet.replaceSync(css2);
120
139
  if (!rootNode.adoptedStyleSheets.includes(styleSheet)) {
121
140
  rootNode.adoptedStyleSheets.push(styleSheet);
141
+ cleanup = () => {
142
+ rootNode.adoptedStyleSheets = rootNode.adoptedStyleSheets.filter(
143
+ (sheet) => sheet !== styleSheet
144
+ );
145
+ };
122
146
  }
123
147
  return true;
124
148
  })();
125
- return { loaded };
149
+ return { loaded, cleanup };
126
150
  }
127
151
  function Fonts() {
128
152
  const rootNode = useRootNode();
@@ -132,6 +156,35 @@ function Fonts() {
132
156
  }, [rootNode]);
133
157
  return null;
134
158
  }
159
+ function InlineSpriteSheet() {
160
+ const rootNode = useRootNode();
161
+ React.useEffect(
162
+ function maybeCreateSpriteSheet() {
163
+ const ownerDocument = getOwnerDocument(rootNode);
164
+ if (!ownerDocument) return;
165
+ const spriteSheet = ownerDocument?.getElementById(spriteSheetId);
166
+ if (spriteSheet) return;
167
+ const svg = ownerDocument.createElementNS(
168
+ "http://www.w3.org/2000/svg",
169
+ "svg"
170
+ );
171
+ svg.id = spriteSheetId;
172
+ svg.style.display = "none";
173
+ Object.defineProperty(svg, Symbol.for("\u{1F95D}"), {
174
+ value: { icons: /* @__PURE__ */ new Map() }
175
+ // Map of icon URLs that have already been inlined.
176
+ });
177
+ ownerDocument.body.appendChild(svg);
178
+ return () => {
179
+ if (svg.isConnected) {
180
+ ownerDocument.body.removeChild(svg);
181
+ }
182
+ };
183
+ },
184
+ [rootNode]
185
+ );
186
+ return null;
187
+ }
135
188
  function loadFonts(rootNode) {
136
189
  const ownerWindow = getWindow(rootNode);
137
190
  if (!ownerWindow || Array.from(ownerWindow.document.fonts).some(
@@ -160,17 +213,10 @@ const supportsAdoptedStylesheets = isBrowser && "adoptedStyleSheets" in Document
160
213
  function isShadow(node) {
161
214
  return node instanceof ShadowRoot || node?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !!node?.host;
162
215
  }
163
- function isDocument(node) {
164
- return node?.nodeType === Node.DOCUMENT_NODE;
165
- }
166
- function getOwnerDocument(node) {
167
- return (isDocument(node) ? node : node.ownerDocument) || null;
168
- }
169
216
  function getWindow(node) {
170
217
  const ownerDocument = getOwnerDocument(node);
171
218
  return ownerDocument?.defaultView || null;
172
219
  }
173
- const useLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect;
174
220
  export {
175
221
  Root
176
222
  };
@@ -3,20 +3,37 @@ import { type FocusableProps } from "./~utils.js";
3
3
  /**
4
4
  * Compound component for a select element, which allows the user to select a value from a list of options.
5
5
  *
6
- * Works well with the `Field` and `Label` components.
6
+ * Use with the `Field` components to automatically handle ID associations for
7
+ * labels and descriptions:
8
+ * ```tsx
9
+ * <Field.Root>
10
+ * <Field.Label>Fruit</Field.Label>
11
+ * <Field.Control
12
+ * render={(controlProps) => (
13
+ * <Select.Root>
14
+ * <Select.HtmlSelect name="fruit" {...controlProps}>
15
+ * <option value="kiwi">Kiwi</option>
16
+ * <option value="mango">Mango</option>
17
+ * <option value="papaya">Papaya</option>
18
+ * </Select.HtmlSelect>
19
+ * </Select.Root>
20
+ * )}
21
+ * />
22
+ * </Field.Root>
23
+ * ```
7
24
  *
8
- * Example usage:
25
+ * Without the `Field` components you will need to manually associate labels,
26
+ * descriptions, etc.:
9
27
  * ```tsx
10
- * <Field>
11
- * <Label>Select an option</Label>
12
- * <Select.Root>
13
- * <Select.HtmlSelect>
14
- * <option value="1">Option 1</option>
15
- * <option value="2">Option 2</option>
16
- * <option value="3">Option 3</option>
17
- * </Select.HtmlSelect>
18
- * </Select.Root>
19
- * </Field>
28
+ * <Label htmlFor="fruit">Fruit</Label>
29
+ * <Description id="fruit-description">Something to include in a fruit salad.</Description>
30
+ * <Select.Root>
31
+ * <Select.HtmlSelect id="fruit" aria-labelledby="fruit-description">
32
+ * <option value="kiwi">Kiwi</option>
33
+ * <option value="mango">Mango</option>
34
+ * <option value="papaya">Papaya</option>
35
+ * </Select.HtmlSelect>
36
+ * </Select.Root>
20
37
  * ```
21
38
  */
22
39
  declare const SelectRoot: React.ForwardRefExoticComponent<Pick<import("@ariakit/react/role").RoleProps, "render"> & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "render"> & React.RefAttributes<HTMLElement | HTMLDivElement>>;
@@ -7,11 +7,12 @@ import {
7
7
  isBrowser
8
8
  } from "./~utils.js";
9
9
  import { DisclosureArrow } from "./Icon.js";
10
- import { FieldControl } from "./Field.js";
10
+ import { useFieldControlType } from "./Field.internal.js";
11
11
  const supportsHas = isBrowser && CSS?.supports?.("selector(:has(+ *))");
12
12
  const HtmlSelectContext = React.createContext(() => {
13
13
  });
14
14
  const SelectRoot = forwardRef((props, forwardedRef) => {
15
+ useFieldControlType("textlike");
15
16
  const [isHtmlSelect, setIsHtmlSelect] = React.useState(false);
16
17
  return /* @__PURE__ */ jsx(HtmlSelectContext.Provider, { value: setIsHtmlSelect, children: /* @__PURE__ */ jsx(
17
18
  Role.div,
@@ -25,7 +26,7 @@ const SelectRoot = forwardRef((props, forwardedRef) => {
25
26
  });
26
27
  const HtmlSelect = forwardRef(
27
28
  (props, forwardedRef) => {
28
- const { id, variant = "solid", ...rest } = props;
29
+ const { variant = "solid", ...rest } = props;
29
30
  const setIsHtmlSelect = React.useContext(HtmlSelectContext);
30
31
  React.useEffect(
31
32
  function updateContext() {
@@ -35,20 +36,13 @@ const HtmlSelect = forwardRef(
35
36
  );
36
37
  return /* @__PURE__ */ jsxs(Fragment, { children: [
37
38
  /* @__PURE__ */ jsx(
38
- FieldControl,
39
+ Role.select,
39
40
  {
40
- type: "textlike",
41
- id,
42
- render: /* @__PURE__ */ jsx(
43
- Role.select,
44
- {
45
- ...rest,
46
- className: cx("\u{1F95D}-button", "\u{1F95D}-select", props.className),
47
- "data-kiwi-tone": "neutral",
48
- "data-kiwi-variant": variant,
49
- ref: forwardedRef
50
- }
51
- )
41
+ ...rest,
42
+ className: cx("\u{1F95D}-button", "\u{1F95D}-select", props.className),
43
+ "data-kiwi-tone": "neutral",
44
+ "data-kiwi-variant": variant,
45
+ ref: forwardedRef
52
46
  }
53
47
  ),
54
48
  /* @__PURE__ */ jsx(DisclosureArrow, { className: "\u{1F95D}-select-arrow" })
@@ -17,17 +17,32 @@ const Spinner = forwardRef(
17
17
  ...rest,
18
18
  "data-kiwi-size": size,
19
19
  "data-kiwi-tone": tone,
20
+ "data-kiwi-variant": "indeterminate",
20
21
  className: cx("\u{1F95D}-spinner", props.className),
21
22
  ref: forwardedRef,
22
23
  children: [
23
- /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", className: "\u{1F95D}-spinner-svg", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx(
24
- "path",
25
- {
26
- stroke: "currentColor",
27
- strokeLinecap: "round",
28
- d: "M9.5 1.674a6.503 6.503 0 0 1 0 12.652m-3-12.652a6.503 6.503 0 0 0 0 12.652"
29
- }
30
- ) }),
24
+ /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", className: "\u{1F95D}-spinner-svg", viewBox: "0 0 16 16", children: [
25
+ /* @__PURE__ */ jsx(
26
+ "circle",
27
+ {
28
+ pathLength: "100",
29
+ className: "\u{1F95D}-spinner-svg-track",
30
+ cx: "8",
31
+ cy: "8",
32
+ r: "6.5"
33
+ }
34
+ ),
35
+ /* @__PURE__ */ jsx(
36
+ "circle",
37
+ {
38
+ pathLength: "100",
39
+ className: "\u{1F95D}-spinner-svg-fill",
40
+ cx: "8",
41
+ cy: "8",
42
+ r: "6.5"
43
+ }
44
+ )
45
+ ] }),
31
46
  /* @__PURE__ */ jsx(VisuallyHidden, { children: alt })
32
47
  ]
33
48
  }