@mortenbrudvik/waveui 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -40
- package/dist/components/data-display/List.cjs +1 -1
- package/dist/components/data-display/List.cjs.map +1 -1
- package/dist/components/data-display/List.mjs +405 -219
- package/dist/components/data-display/List.mjs.map +1 -1
- package/dist/components/data-display/List.registry.cjs +1 -1
- package/dist/components/data-display/List.registry.cjs.map +1 -1
- package/dist/components/data-display/List.registry.mjs +12 -6
- package/dist/components/data-display/List.registry.mjs.map +1 -1
- package/dist/components/input/Combobox.cjs +1 -1
- package/dist/components/input/Combobox.cjs.map +1 -1
- package/dist/components/input/Combobox.mjs +163 -148
- package/dist/components/input/Combobox.mjs.map +1 -1
- package/dist/components/input/Dropdown.cjs +1 -1
- package/dist/components/input/Dropdown.cjs.map +1 -1
- package/dist/components/input/Dropdown.mjs +132 -117
- package/dist/components/input/Dropdown.mjs.map +1 -1
- package/dist/components/input/Label.cjs +1 -1
- package/dist/components/input/Label.cjs.map +1 -1
- package/dist/components/input/Label.mjs +1 -0
- package/dist/components/input/Label.mjs.map +1 -1
- package/dist/components/input/Listbox.cjs +1 -1
- package/dist/components/input/Listbox.cjs.map +1 -1
- package/dist/components/input/Listbox.mjs +90 -78
- package/dist/components/input/Listbox.mjs.map +1 -1
- package/dist/components/input/Option.cjs +1 -1
- package/dist/components/input/Option.cjs.map +1 -1
- package/dist/components/input/Option.data.cjs +2 -0
- package/dist/components/input/Option.data.cjs.map +1 -0
- package/dist/components/input/Option.data.mjs +114 -0
- package/dist/components/input/Option.data.mjs.map +1 -0
- package/dist/components/input/Option.mjs +21 -17
- package/dist/components/input/Option.mjs.map +1 -1
- package/dist/components/layout/FlatTree.cjs +2 -0
- package/dist/components/layout/FlatTree.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.cjs +2 -0
- package/dist/components/layout/FlatTree.context.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.mjs +20 -0
- package/dist/components/layout/FlatTree.context.mjs.map +1 -0
- package/dist/components/layout/FlatTree.item.cjs +2 -0
- package/dist/components/layout/FlatTree.item.cjs.map +1 -0
- package/dist/components/layout/FlatTree.item.mjs +90 -0
- package/dist/components/layout/FlatTree.item.mjs.map +1 -0
- package/dist/components/layout/FlatTree.mjs +276 -0
- package/dist/components/layout/FlatTree.mjs.map +1 -0
- package/dist/components/layout/Tree.core.cjs +1 -1
- package/dist/components/layout/Tree.core.cjs.map +1 -1
- package/dist/components/layout/Tree.core.mjs +166 -165
- package/dist/components/layout/Tree.core.mjs.map +1 -1
- package/dist/components/layout/Tree.selection.cjs +1 -1
- package/dist/components/layout/Tree.selection.cjs.map +1 -1
- package/dist/components/layout/Tree.selection.mjs +11 -57
- package/dist/components/layout/Tree.selection.mjs.map +1 -1
- package/dist/components/layout/useFlatTree.cjs +2 -0
- package/dist/components/layout/useFlatTree.cjs.map +1 -0
- package/dist/components/layout/useFlatTree.mjs +83 -0
- package/dist/components/layout/useFlatTree.mjs.map +1 -0
- package/dist/components/portal/Portal.cjs +1 -1
- package/dist/components/portal/Portal.cjs.map +1 -1
- package/dist/components/portal/Portal.mjs +12 -11
- package/dist/components/portal/Portal.mjs.map +1 -1
- package/dist/components/provider/WaveProvider.cjs +1 -1
- package/dist/components/provider/WaveProvider.cjs.map +1 -1
- package/dist/components/provider/WaveProvider.mjs +59 -35
- package/dist/components/provider/WaveProvider.mjs.map +1 -1
- package/dist/components/table/DataGrid.cjs +1 -1
- package/dist/components/table/DataGrid.cjs.map +1 -1
- package/dist/components/table/DataGrid.context.cjs +2 -0
- package/dist/components/table/DataGrid.context.cjs.map +1 -0
- package/dist/components/table/DataGrid.context.mjs +74 -0
- package/dist/components/table/DataGrid.context.mjs.map +1 -0
- package/dist/components/table/DataGrid.header.cjs +2 -0
- package/dist/components/table/DataGrid.header.cjs.map +1 -0
- package/dist/components/table/DataGrid.header.mjs +168 -0
- package/dist/components/table/DataGrid.header.mjs.map +1 -0
- package/dist/components/table/DataGrid.mjs +488 -591
- package/dist/components/table/DataGrid.mjs.map +1 -1
- package/dist/components/table/DataGrid.sizing.cjs +2 -0
- package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
- package/dist/components/table/DataGrid.sizing.mjs +187 -0
- package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
- package/dist/components/table/DataGrid.store.cjs +2 -0
- package/dist/components/table/DataGrid.store.cjs.map +1 -0
- package/dist/components/table/DataGrid.store.mjs +108 -0
- package/dist/components/table/DataGrid.store.mjs.map +1 -0
- package/dist/components/table/DataGrid.window.cjs +2 -0
- package/dist/components/table/DataGrid.window.cjs.map +1 -0
- package/dist/components/table/DataGrid.window.mjs +165 -0
- package/dist/components/table/DataGrid.window.mjs.map +1 -0
- package/dist/components/table/Table.cjs +1 -1
- package/dist/components/table/Table.cjs.map +1 -1
- package/dist/components/table/Table.mjs +74 -72
- package/dist/components/table/Table.mjs.map +1 -1
- package/dist/components/table/Table.resize.cjs +2 -0
- package/dist/components/table/Table.resize.cjs.map +1 -0
- package/dist/components/table/Table.resize.mjs +98 -0
- package/dist/components/table/Table.resize.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +1 -1
- package/dist/components/table/useGridNavigation.cjs.map +1 -1
- package/dist/components/table/useGridNavigation.mjs +343 -140
- package/dist/components/table/useGridNavigation.mjs.map +1 -1
- package/dist/components/table/useTableColumnSizing.cjs +2 -0
- package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
- package/dist/components/table/useTableColumnSizing.mjs +76 -0
- package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
- package/dist/components/table/useTableFeatures.cjs +2 -0
- package/dist/components/table/useTableFeatures.cjs.map +1 -0
- package/dist/components/table/useTableFeatures.mjs +110 -0
- package/dist/components/table/useTableFeatures.mjs.map +1 -0
- package/dist/components/table/useTableSelection.cjs +2 -0
- package/dist/components/table/useTableSelection.cjs.map +1 -0
- package/dist/components/table/useTableSelection.mjs +39 -0
- package/dist/components/table/useTableSelection.mjs.map +1 -0
- package/dist/components/table/useTableSort.cjs +2 -0
- package/dist/components/table/useTableSort.cjs.map +1 -0
- package/dist/components/table/useTableSort.mjs +58 -0
- package/dist/components/table/useTableSort.mjs.map +1 -0
- package/dist/components/typography/Text.cjs +1 -1
- package/dist/components/typography/Text.cjs.map +1 -1
- package/dist/components/typography/Text.mjs +11 -7
- package/dist/components/typography/Text.mjs.map +1 -1
- package/dist/hooks/useActiveDescendant.cjs +1 -1
- package/dist/hooks/useActiveDescendant.cjs.map +1 -1
- package/dist/hooks/useActiveDescendant.mjs +33 -25
- package/dist/hooks/useActiveDescendant.mjs.map +1 -1
- package/dist/hooks/useListbox.cjs +1 -1
- package/dist/hooks/useListbox.cjs.map +1 -1
- package/dist/hooks/useListbox.mjs +140 -130
- package/dist/hooks/useListbox.mjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.cjs +1 -1
- package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.mjs +1 -1
- package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
- package/dist/hooks/useSelection.cjs +2 -0
- package/dist/hooks/useSelection.cjs.map +1 -0
- package/dist/hooks/useSelection.mjs +57 -0
- package/dist/hooks/useSelection.mjs.map +1 -0
- package/dist/hooks/useVirtualWindow.cjs +2 -0
- package/dist/hooks/useVirtualWindow.cjs.map +1 -0
- package/dist/hooks/useVirtualWindow.mjs +102 -0
- package/dist/hooks/useVirtualWindow.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1799 -63
- package/dist/index.d.ts +1799 -63
- package/dist/index.mjs +150 -136
- package/dist/lib/brand.cjs +2 -0
- package/dist/lib/brand.cjs.map +1 -0
- package/dist/lib/brand.mjs +358 -0
- package/dist/lib/brand.mjs.map +1 -0
- package/dist/lib/classNames.cjs +1 -1
- package/dist/lib/classNames.cjs.map +1 -1
- package/dist/lib/classNames.mjs +68 -49
- package/dist/lib/classNames.mjs.map +1 -1
- package/dist/lib/cn.cjs +1 -1
- package/dist/lib/cn.cjs.map +1 -1
- package/dist/lib/cn.mjs +5 -1
- package/dist/lib/cn.mjs.map +1 -1
- package/dist/lib/columnSizing.cjs +2 -0
- package/dist/lib/columnSizing.cjs.map +1 -0
- package/dist/lib/columnSizing.mjs +27 -0
- package/dist/lib/columnSizing.mjs.map +1 -0
- package/dist/lib/flatTree.cjs +2 -0
- package/dist/lib/flatTree.cjs.map +1 -0
- package/dist/lib/flatTree.mjs +118 -0
- package/dist/lib/flatTree.mjs.map +1 -0
- package/dist/lib/selection.cjs +2 -0
- package/dist/lib/selection.cjs.map +1 -0
- package/dist/lib/selection.mjs +28 -0
- package/dist/lib/selection.mjs.map +1 -0
- package/dist/lib/tokens.cjs +2 -0
- package/dist/lib/tokens.cjs.map +1 -0
- package/dist/lib/tokens.mjs +43 -0
- package/dist/lib/tokens.mjs.map +1 -0
- package/dist/lib/treeCascade.cjs +2 -0
- package/dist/lib/treeCascade.cjs.map +1 -0
- package/dist/lib/treeCascade.mjs +52 -0
- package/dist/lib/treeCascade.mjs.map +1 -0
- package/dist/lib/virtual.cjs +2 -0
- package/dist/lib/virtual.cjs.map +1 -0
- package/dist/lib/virtual.mjs +54 -0
- package/dist/lib/virtual.mjs.map +1 -0
- package/dist/lib/waveTokens.cjs +2 -0
- package/dist/lib/waveTokens.cjs.map +1 -0
- package/dist/lib/waveTokens.mjs +24 -0
- package/dist/lib/waveTokens.mjs.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +3 -1
- package/src/styles/tokens.css +210 -10
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { warnOnce as e } from "../../lib/dev.mjs";
|
|
3
|
+
import { useEventCallback as t } from "../../hooks/useEventCallback.mjs";
|
|
4
|
+
import { useControllable as n } from "../../hooks/useControllable.mjs";
|
|
5
|
+
import { computeCheckStates as r, toggleCheck as i } from "../../lib/treeCascade.mjs";
|
|
6
|
+
import { buildFlatTreeIndex as a, flatTreeForest as o, visibleFlatItems as s } from "../../lib/flatTree.mjs";
|
|
7
|
+
import * as c from "react";
|
|
8
|
+
//#region src/components/layout/useFlatTree.ts
|
|
9
|
+
var l = [];
|
|
10
|
+
function u(e) {
|
|
11
|
+
switch (e.kind) {
|
|
12
|
+
case "unknown-parent": return `useFlatTree: the item "${e.value}" has the parentValue "${e.parentValue}", which matches no item; it is shown as a root.`;
|
|
13
|
+
case "cycle": return `useFlatTree: the parentValue links form a cycle at "${e.value}"; the link of that item to its parent is ignored and it is shown as a root.`;
|
|
14
|
+
case "duplicate-value": return `useFlatTree: duplicate item value "${e.value}" (item ${e.index}); only the first item with a value is used.`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function d(d, f = {}) {
|
|
18
|
+
let { expandedItems: p, defaultExpandedItems: m, onExpandedItemsChange: h, checkedItems: g, defaultCheckedItems: _, onCheckedItemsChange: v, selectionMode: y = "none" } = f, b = c.useMemo(() => a(d), [d]);
|
|
19
|
+
c.useEffect(() => {
|
|
20
|
+
let t = /* @__PURE__ */ new Set();
|
|
21
|
+
for (let n of b.issues) t.has(n.kind) || (t.add(n.kind), e(`useFlatTree:${n.kind}`, u(n)));
|
|
22
|
+
}, [b]);
|
|
23
|
+
let [x] = c.useState(() => m ?? l), [S, C] = n(p, x, (e) => h?.([...e])), w = c.useMemo(() => new Set(S), [S]), T = c.useCallback((e) => {
|
|
24
|
+
let t = typeof e == "string" ? [e] : e;
|
|
25
|
+
C((e) => {
|
|
26
|
+
let n = new Set(e), r = [];
|
|
27
|
+
for (let e of t) n.has(e) || (n.add(e), r.push(e));
|
|
28
|
+
return r.length > 0 ? [...e, ...r] : e;
|
|
29
|
+
});
|
|
30
|
+
}, [C]), E = c.useCallback((e) => {
|
|
31
|
+
C((t) => t.includes(e) ? t.filter((t) => t !== e) : t);
|
|
32
|
+
}, [C]), D = c.useCallback((e) => {
|
|
33
|
+
C((t) => t.includes(e) ? t.filter((t) => t !== e) : [...t, e]);
|
|
34
|
+
}, [C]), O = c.useCallback((e) => (b.byValue.get(e)?.isBranch ?? !1) && w.has(e), [b, w]), [k] = c.useState(() => _ ?? l), A = c.useRef(void 0), [j, M] = n(g, k, (e) => v?.([...e], A.current)), N = y === "multiple", P = c.useMemo(() => N ? o(b) : null, [b, N]), F = c.useMemo(() => P ? r(P, new Set(j)) : null, [P, j]), I = t((e, t, n) => {
|
|
35
|
+
let a = P ?? o(b);
|
|
36
|
+
M((o) => {
|
|
37
|
+
if ((r(a, new Set(o)).get(e) ?? (o.includes(e) ? "checked" : "unchecked")) === (t ? "checked" : "unchecked")) return o;
|
|
38
|
+
let s = i(a, o, e);
|
|
39
|
+
return s.includes(e) !== t && (s = i(a, s, e)), A.current = n ? {
|
|
40
|
+
value: e,
|
|
41
|
+
checked: s.includes(e),
|
|
42
|
+
event: n
|
|
43
|
+
} : void 0, s;
|
|
44
|
+
}), A.current = void 0;
|
|
45
|
+
}), L = c.useMemo(() => {
|
|
46
|
+
let e = s(b, w);
|
|
47
|
+
return F ? e.map((e) => ({
|
|
48
|
+
...e,
|
|
49
|
+
checked: F.get(e.item.value) ?? "unchecked"
|
|
50
|
+
})) : e;
|
|
51
|
+
}, [
|
|
52
|
+
b,
|
|
53
|
+
w,
|
|
54
|
+
F
|
|
55
|
+
]), R = c.useMemo(() => {
|
|
56
|
+
let e = /* @__PURE__ */ new Map();
|
|
57
|
+
for (let t of L) e.set(t.item.value, t);
|
|
58
|
+
return e;
|
|
59
|
+
}, [L]), z = c.useCallback((e) => R.get(e), [R]), B = c.useMemo(() => ({ selectionMode: y }), [y]), V = c.useCallback(() => B, [B]);
|
|
60
|
+
return c.useMemo(() => ({
|
|
61
|
+
visibleItems: L,
|
|
62
|
+
getItem: z,
|
|
63
|
+
expand: T,
|
|
64
|
+
collapse: E,
|
|
65
|
+
toggle: D,
|
|
66
|
+
isExpanded: O,
|
|
67
|
+
setChecked: I,
|
|
68
|
+
getTreeProps: V
|
|
69
|
+
}), [
|
|
70
|
+
L,
|
|
71
|
+
z,
|
|
72
|
+
T,
|
|
73
|
+
E,
|
|
74
|
+
D,
|
|
75
|
+
O,
|
|
76
|
+
I,
|
|
77
|
+
V
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { d as useFlatTree };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=useFlatTree.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFlatTree.mjs","names":["React","EMPTY","issueMessage","issue","useFlatTree","options","index","buildFlatTreeIndex","items","reported","warnOnce","initialExpanded","defaultExpandedItems","expandedList","setExpandedList","useControllable","expandedItems","onExpandedItemsChange","next","expandedSet","expand","values","value","seen","prev","added","v","collapse","toggle","isExpanded","initialChecked","defaultCheckedItems","pendingDetails","checkedList","setCheckedList","checkedItems","onCheckedItemsChange","isMultiple","selectionMode","forest","flatTreeForest","checkStates","computeCheckStates","setChecked","useEventCallback","nodes","previous","checked","toggleCheck","event","visibleItems","rows","visibleFlatItems","row","rowsByValue","map","getItem","treeProps","getTreeProps"],"sources":["../../../src/components/layout/useFlatTree.ts"],"sourcesContent":["import * as React from 'react';\nimport { useControllable } from '../../hooks/useControllable';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { warnOnce } from '../../lib/dev';\nimport {\n buildFlatTreeIndex,\n flatTreeForest,\n visibleFlatItems,\n type FlatTreeItemData,\n type FlatTreeIssue,\n type VisibleFlatItem,\n} from '../../lib/flatTree';\nimport { computeCheckStates, toggleCheck, type CheckState } from '../../lib/treeCascade';\nimport type { SelectionMode } from '../../lib/types';\nimport type { TreeCheckedItemsChangeDetails } from './Tree.core';\n\nconst EMPTY: readonly string[] = [];\n\n/** Options of {@link useFlatTree}: the state names and shapes of the nested `Tree`. */\nexport interface UseFlatTreeOptions {\n /** Controlled list of expanded item values. */\n expandedItems?: readonly string[];\n /**\n * Item values that are expanded initially (uncontrolled).\n * @default []\n */\n defaultExpandedItems?: readonly string[];\n /** Called with the new list of expanded item values when an item expands or collapses. */\n onExpandedItemsChange?: (expandedItems: string[]) => void;\n /** Controlled checked items (`selectionMode=\"multiple\"`); the cascade rule is the nested `Tree`'s. */\n checkedItems?: readonly string[];\n /**\n * Initially checked items (uncontrolled).\n * @default []\n */\n defaultCheckedItems?: readonly string[];\n /**\n * Called with every checked item (in tree order) when a check changes. `details` is passed when\n * `setChecked` got an `event`; it is typed optional until 1.0.\n */\n onCheckedItemsChange?: (checkedItems: string[], details?: TreeCheckedItemsChangeDetails) => void;\n /**\n * How items are selected, as `Tree`. With `'multiple'` every visible row carries its `checked`\n * state; the model's `getTreeProps()` hands the mode on to `FlatTree`.\n * @default 'none'\n */\n selectionMode?: 'none' | SelectionMode;\n}\n\n/** The state and the visible rows of a flat tree, from {@link useFlatTree}. */\nexport interface FlatTreeModel {\n /**\n * The rows to show: the roots and the children of every expanded branch, in tree order. With\n * `selectionMode: 'multiple'` each row has its `checked` state. The array and its rows keep\n * their identity until the items, the expanded set or the checked set change.\n */\n visibleItems: readonly (VisibleFlatItem & { checked?: CheckState })[];\n /** The visible row of `value`; `undefined` for an item under a collapsed branch or an unknown value. */\n getItem(value: string): (VisibleFlatItem & { checked?: CheckState }) | undefined;\n /**\n * Expands `value`, or every value of a list in one change (one `onExpandedItemsChange` call, as\n * `*` on a tree item makes). Nothing happens, and nothing is called, if every value is already in\n * the expanded set.\n */\n expand(value: string | readonly string[]): void;\n /** Collapses `value`. Nothing happens, and nothing is called, if it is not in the expanded set. */\n collapse(value: string): void;\n /** Expands `value` when it is collapsed and collapses it when it is expanded. */\n toggle(value: string): void;\n /** Whether `value` is a branch (an item with child items, or typed `'branch'`) in the expanded set. */\n isExpanded(value: string): boolean;\n /**\n * Sets the check state of `value`, with the cascade of the nested `Tree`: the item and everything\n * nested in it, then its ancestors from their children. Nothing is called when the item is\n * already in that state; a mixed item is set to `checked` as requested. A value that matches no\n * item is added to, or removed from, the checked list. `event` becomes `details.event` of\n * `onCheckedItemsChange`; without it the callback gets no `details`. It works in any\n * `selectionMode` (the rows carry `checked` only with `'multiple'`), and on a disabled item too.\n */\n setChecked(value: string, checked: boolean, event?: Event): void;\n /** The props `FlatTree` takes from the model, for `<FlatTree model={model} {...model.getTreeProps()} />`. */\n getTreeProps(): { selectionMode: 'none' | SelectionMode };\n}\n\nfunction issueMessage(issue: FlatTreeIssue): string {\n switch (issue.kind) {\n case 'unknown-parent':\n return (\n `useFlatTree: the item \"${issue.value}\" has the parentValue \"${issue.parentValue}\", which ` +\n 'matches no item; it is shown as a root.'\n );\n case 'cycle':\n return (\n `useFlatTree: the parentValue links form a cycle at \"${issue.value}\"; the link of that ` +\n 'item to its parent is ignored and it is shown as a root.'\n );\n case 'duplicate-value':\n return (\n `useFlatTree: duplicate item value \"${issue.value}\" (item ${issue.index}); only the first ` +\n 'item with a value is used.'\n );\n }\n}\n\n/**\n * The model of a flat tree: items listed with a `parentValue` link, in any order, become the rows\n * a windowed tree shows (`FlatTree`), without building an element tree. Returns the visible rows\n * (with `level`, `position`, `setSize` and `expanded` for the tree ARIA), the expanded and\n * checked state, and the actions on them.\n *\n * Pass `items` as a stable reference or memoise it: the index of the items is rebuilt when its\n * identity changes. Expanded and checked state work as in `Tree`: pass `expandedItems` or\n * `checkedItems` to control it (a stable array, or the rows are rebuilt on every render), or\n * `defaultExpandedItems` and `defaultCheckedItems` to start uncontrolled; the change callbacks\n * fire once per change. When `items` is replaced, the expanded and checked values stay in the\n * state: a value that matches no item is ignored for the rows until an item with it returns, and\n * it is changed only by you.\n *\n * Development warnings, once each: an unknown `parentValue` (the item is a root), a cycle of\n * parents (cut) and a duplicate value (the first item wins).\n *\n * @example\n * const model = useFlatTree(items, { defaultExpandedItems: ['docs'] });\n * model.visibleItems.map((row) => row.item.label);\n */\nexport function useFlatTree(\n items: readonly FlatTreeItemData[],\n options: UseFlatTreeOptions = {},\n): FlatTreeModel {\n const {\n expandedItems,\n defaultExpandedItems,\n onExpandedItemsChange,\n checkedItems,\n defaultCheckedItems,\n onCheckedItemsChange,\n selectionMode = 'none',\n } = options;\n\n const index = React.useMemo(() => buildFlatTreeIndex(items), [items]);\n React.useEffect(() => {\n const reported = new Set<string>();\n for (const issue of index.issues) {\n if (reported.has(issue.kind)) continue;\n reported.add(issue.kind);\n warnOnce(`useFlatTree:${issue.kind}`, issueMessage(issue));\n }\n }, [index]);\n\n const [initialExpanded] = React.useState<readonly string[]>(() => defaultExpandedItems ?? EMPTY);\n const [expandedList, setExpandedList] = useControllable<readonly string[]>(\n expandedItems,\n initialExpanded,\n (next) => onExpandedItemsChange?.([...next]),\n );\n const expandedSet = React.useMemo(() => new Set(expandedList), [expandedList]);\n\n const expand = React.useCallback(\n (value: string | readonly string[]) => {\n const values = typeof value === 'string' ? [value] : value;\n setExpandedList((prev) => {\n // Sets, not array searches: `*` can expand thousands of siblings against a large set.\n const seen = new Set(prev);\n const added: string[] = [];\n for (const v of values) {\n if (seen.has(v)) continue;\n seen.add(v);\n added.push(v);\n }\n return added.length > 0 ? [...prev, ...added] : prev;\n });\n },\n [setExpandedList],\n );\n const collapse = React.useCallback(\n (value: string) => {\n setExpandedList((prev) => (prev.includes(value) ? prev.filter((v) => v !== value) : prev));\n },\n [setExpandedList],\n );\n const toggle = React.useCallback(\n (value: string) => {\n setExpandedList((prev) =>\n prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value],\n );\n },\n [setExpandedList],\n );\n const isExpanded = React.useCallback(\n (value: string) => (index.byValue.get(value)?.isBranch ?? false) && expandedSet.has(value),\n [index, expandedSet],\n );\n\n // useControllable passes the value only, so `details` reaches the change callback through a ref\n // written and cleared in the same event (the nested Tree does the same).\n const [initialChecked] = React.useState<readonly string[]>(() => defaultCheckedItems ?? EMPTY);\n const pendingDetails = React.useRef<TreeCheckedItemsChangeDetails | undefined>(undefined);\n const [checkedList, setCheckedList] = useControllable<readonly string[]>(\n checkedItems,\n initialChecked,\n (next) => onCheckedItemsChange?.([...next], pendingDetails.current),\n );\n\n const isMultiple = selectionMode === 'multiple';\n const forest = React.useMemo(\n () => (isMultiple ? flatTreeForest(index) : null),\n [index, isMultiple],\n );\n const checkStates = React.useMemo(\n () => (forest ? computeCheckStates(forest, new Set(checkedList)) : null),\n [forest, checkedList],\n );\n\n const setChecked = useEventCallback((value: string, checked: boolean, event?: Event) => {\n const nodes = forest ?? flatTreeForest(index);\n setCheckedList((previous) => {\n const current =\n computeCheckStates(nodes, new Set(previous)).get(value) ??\n (previous.includes(value) ? 'checked' : 'unchecked');\n if (current === (checked ? 'checked' : 'unchecked')) return previous;\n let next = toggleCheck(nodes, previous, value);\n // A mixed item turns checked on a toggle; asked to be unchecked it needs a second one.\n if (next.includes(value) !== checked) next = toggleCheck(nodes, next, value);\n pendingDetails.current = event ? { value, checked: next.includes(value), event } : undefined;\n return next;\n });\n pendingDetails.current = undefined;\n });\n\n const visibleItems = React.useMemo(() => {\n const rows = visibleFlatItems(index, expandedSet);\n if (!checkStates) return rows;\n return rows.map((row) => ({ ...row, checked: checkStates.get(row.item.value) ?? 'unchecked' }));\n }, [index, expandedSet, checkStates]);\n\n const rowsByValue = React.useMemo(() => {\n const map = new Map<string, (typeof visibleItems)[number]>();\n for (const row of visibleItems) map.set(row.item.value, row);\n return map;\n }, [visibleItems]);\n const getItem = React.useCallback((value: string) => rowsByValue.get(value), [rowsByValue]);\n\n const treeProps = React.useMemo(() => ({ selectionMode }), [selectionMode]);\n const getTreeProps = React.useCallback(() => treeProps, [treeProps]);\n\n return React.useMemo(\n () => ({\n visibleItems,\n getItem,\n expand,\n collapse,\n toggle,\n isExpanded,\n setChecked,\n getTreeProps,\n }),\n [visibleItems, getItem, expand, collapse, toggle, isExpanded, setChecked, getTreeProps],\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/classNames.cjs"),r=require("../../hooks/useMergedRefs.cjs"),i=require("../../hooks/useIsClient.cjs"),a=require("../provider/WaveProvider.cjs"),o=require("../../hooks/useDirection.cjs"),s=require("../../lib/layers.cjs"),c=require("../../hooks/useDismiss.cjs");let l=require("react");l=e.__toESM(l,1);let u=require("react/jsx-runtime"),d=require("react-dom");var f=l.createContext(0);f.displayName=`PortalDepthContext`;var p=({children:e,container:p,layer:m=`overlay`,layerId:h,disabled:g=!1,className:_,ref:v})=>{let y=i.useIsClient(),{themeClassName:b,portalContainer:x}=a.useWaveTheme(),
|
|
1
|
+
"use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/classNames.cjs"),r=require("../../hooks/useMergedRefs.cjs"),i=require("../../hooks/useIsClient.cjs"),a=require("../provider/WaveProvider.cjs"),o=require("../../hooks/useDirection.cjs"),s=require("../../lib/layers.cjs"),c=require("../../hooks/useDismiss.cjs");let l=require("react");l=e.__toESM(l,1);let u=require("react/jsx-runtime"),d=require("react-dom");var f=l.createContext(0);f.displayName=`PortalDepthContext`;var p=({children:e,container:p,layer:m=`overlay`,layerId:h,disabled:g=!1,className:_,ref:v})=>{let y=i.useIsClient(),{themeClassName:b,portalContainer:x,tokenStyle:S,applyStylesToPortals:C}=a.useWaveTheme(),w=o.useDirection(),T=l.useContext(f),E=l.useContext(c.DismissLayerContext),D=l.useCallback(e=>{if(e&&E!==null)return s.registerLayerElement(E,e)},[E]),O=r.useMergedRefs(v,D),k=h===void 0?e:(0,u.jsx)(c.DismissLayerProvider,{layerId:h,children:e});if(g)return(0,u.jsx)(u.Fragment,{children:k});if(!y)return null;let A=p??x??document.body;return(0,d.createPortal)((0,u.jsx)(`div`,{ref:O,className:t.cn(n.portalClassNames.root,b,_),dir:w,"data-wave-portal":``,"data-layer":m,style:{...C?S:void 0,position:`relative`,zIndex:`calc(${s.Z_INDEX[m]} + ${T})`},children:(0,u.jsx)(f.Provider,{value:T+1,children:k})}),A)};p.displayName=`Portal`,exports.Portal=p,exports.PortalDepthContext=f;
|
|
2
2
|
//# sourceMappingURL=Portal.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Portal.cjs","names":["React","useIsClient","useWaveTheme","useDirection","DismissLayerContext","registerLayerElement","useMergedRefs","DismissLayerProvider","_Fragment","createPortal","cn","portalClassNames","Z_INDEX","require_runtime","require_cn","require_classNames","require_useMergedRefs","require_useIsClient","require_WaveProvider","require_useDirection","require_layers","require_useDismiss","react","react_jsx_runtime","react_dom","PortalDepthContext","Portal","isClient","dir","depth","parentLayerId","registerWithParent","el","wrapperRef","ref","content","layerId","children","disabled","target","container","portalContainer","themeClassName","className","layer"],"sources":["../../../src/components/portal/Portal.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '../../lib/cn';\nimport { portalClassNames } from '../../lib/classNames';\nimport { Z_INDEX, registerLayerElement } from '../../lib/layers';\nimport { useIsClient } from '../../hooks/useIsClient';\nimport { useDirection } from '../../hooks/useDirection';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { DismissLayerContext, DismissLayerProvider } from '../../hooks/useDismiss';\nimport { useWaveTheme } from '../provider/WaveProvider';\n\n/** Props accepted by {@link Portal}. */\nexport interface PortalProps {\n /** Content rendered into the portal. */\n children: React.ReactNode;\n /** Element to render into. @default WaveProvider `portalContainer`, else `document.body` */\n container?: HTMLElement | null;\n /**\n * Stacking layer: `--wave-z-overlay` (dialogs, popovers, menus, listboxes), `--wave-z-toast`\n * (above overlays) or `--wave-z-tooltip`. The portal's nesting depth is added.\n * @default 'overlay'\n */\n layer?: 'overlay' | 'toast' | 'tooltip';\n /**\n * The dismiss layer of the content (from `useDismiss`). Provided to the children through\n * `DismissLayerProvider`, so layers opened inside become its descendants.\n */\n layerId?: string;\n /** Render the children inline instead (no portal, no wrapper). @default false */\n disabled?: boolean;\n /** Extra classes for the portal wrapper. */\n className?: string;\n /** Ref to the portal wrapper `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Nesting depth of portals: `0` outside any portal; each `Portal` provides `depth + 1` to its\n * children. Portals add it to their z-index so a surface opened from inside another overlay always\n * stacks above it — also when both mount in the same commit.\n */\nexport const PortalDepthContext = React.createContext(0);\nPortalDepthContext.displayName = 'PortalDepthContext';\n\n/**\n * Renders its children into `document.body` (or the WaveProvider `portalContainer`), wrapped in a\n * `<div class=\"wave-portal …\">` that carries the provider's theme classes, direction and font, so\n * portaled overlays look like the rest of the themed tree. The wrapper paints no background.\n *\n * - **Client only, first commit**: nothing is rendered on the server or during hydration (no\n * mismatch); in every other client render the portal exists in the first commit, so layout\n * effects and ref callbacks of the content see connected elements.\n * - **Stacking**: `z-index: calc(<layer variable> + depth)`, where depth is the number of portals\n * it is rendered in, so a surface opened from inside another overlay always stacks above it,\n * also when both mount in the same commit.\n * - **Layers**: the wrapper is registered with the enclosing dismiss layer (the parent), so presses\n * and focus inside the portal count as inside that layer; `layerId` is provided only to the\n * children.\n *\n * Content that must exist on the server or before an overlay opens (tooltip descriptions, closed\n * listboxes) is rendered inline instead.\n */\nexport const Portal = ({\n children,\n container,\n layer = 'overlay',\n layerId,\n disabled = false,\n className,\n ref,\n}: PortalProps) => {\n const isClient = useIsClient();\n const { themeClassName, portalContainer } = useWaveTheme();\n const dir = useDirection();\n const depth = React.useContext(PortalDepthContext);\n const parentLayerId = React.useContext(DismissLayerContext);\n\n const registerWithParent = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el || parentLayerId === null) return;\n return registerLayerElement(parentLayerId, el);\n },\n [parentLayerId],\n );\n const wrapperRef = useMergedRefs<HTMLDivElement>(ref, registerWithParent);\n\n const content =\n layerId === undefined ? (\n children\n ) : (\n <DismissLayerProvider layerId={layerId}>{children}</DismissLayerProvider>\n );\n\n if (disabled) return <>{content}</>;\n if (!isClient) return null;\n\n const target = container ?? portalContainer ?? document.body;\n\n return createPortal(\n <div\n ref={wrapperRef}\n className={cn(portalClassNames.root, themeClassName, className)}\n dir={dir}\n data-wave-portal=\"\"\n data-layer={layer}\n style={{ position: 'relative'
|
|
1
|
+
{"version":3,"file":"Portal.cjs","names":["React","useIsClient","useWaveTheme","useDirection","DismissLayerContext","registerLayerElement","useMergedRefs","DismissLayerProvider","_Fragment","createPortal","cn","portalClassNames","Z_INDEX","require_runtime","require_cn","require_classNames","require_useMergedRefs","require_useIsClient","require_WaveProvider","require_useDirection","require_layers","require_useDismiss","react","react_jsx_runtime","react_dom","PortalDepthContext","Portal","isClient","dir","depth","parentLayerId","registerWithParent","el","wrapperRef","ref","content","layerId","children","disabled","target","container","portalContainer","themeClassName","className","layer","applyStylesToPortals","tokenStyle"],"sources":["../../../src/components/portal/Portal.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '../../lib/cn';\nimport { portalClassNames } from '../../lib/classNames';\nimport { Z_INDEX, registerLayerElement } from '../../lib/layers';\nimport { useIsClient } from '../../hooks/useIsClient';\nimport { useDirection } from '../../hooks/useDirection';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { DismissLayerContext, DismissLayerProvider } from '../../hooks/useDismiss';\nimport { useWaveTheme } from '../provider/WaveProvider';\n\n/** Props accepted by {@link Portal}. */\nexport interface PortalProps {\n /** Content rendered into the portal. */\n children: React.ReactNode;\n /** Element to render into. @default WaveProvider `portalContainer`, else `document.body` */\n container?: HTMLElement | null;\n /**\n * Stacking layer: `--wave-z-overlay` (dialogs, popovers, menus, listboxes), `--wave-z-toast`\n * (above overlays) or `--wave-z-tooltip`. The portal's nesting depth is added.\n * @default 'overlay'\n */\n layer?: 'overlay' | 'toast' | 'tooltip';\n /**\n * The dismiss layer of the content (from `useDismiss`). Provided to the children through\n * `DismissLayerProvider`, so layers opened inside become its descendants.\n */\n layerId?: string;\n /** Render the children inline instead (no portal, no wrapper). @default false */\n disabled?: boolean;\n /** Extra classes for the portal wrapper. */\n className?: string;\n /** Ref to the portal wrapper `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Nesting depth of portals: `0` outside any portal; each `Portal` provides `depth + 1` to its\n * children. Portals add it to their z-index so a surface opened from inside another overlay always\n * stacks above it — also when both mount in the same commit.\n */\nexport const PortalDepthContext = React.createContext(0);\nPortalDepthContext.displayName = 'PortalDepthContext';\n\n/**\n * Renders its children into `document.body` (or the WaveProvider `portalContainer`), wrapped in a\n * `<div class=\"wave-portal …\">` that carries the provider's theme classes, direction and font, and\n * its token overrides (WaveProvider `tokens`, unless its `applyStylesToPortals` is `false`), so\n * portaled overlays look like the rest of the themed tree. The wrapper paints no background.\n *\n * - **Client only, first commit**: nothing is rendered on the server or during hydration (no\n * mismatch); in every other client render the portal exists in the first commit, so layout\n * effects and ref callbacks of the content see connected elements.\n * - **Stacking**: `z-index: calc(<layer variable> + depth)`, where depth is the number of portals\n * it is rendered in, so a surface opened from inside another overlay always stacks above it,\n * also when both mount in the same commit.\n * - **Layers**: the wrapper is registered with the enclosing dismiss layer (the parent), so presses\n * and focus inside the portal count as inside that layer; `layerId` is provided only to the\n * children.\n *\n * Content that must exist on the server or before an overlay opens (tooltip descriptions, closed\n * listboxes) is rendered inline instead.\n */\nexport const Portal = ({\n children,\n container,\n layer = 'overlay',\n layerId,\n disabled = false,\n className,\n ref,\n}: PortalProps) => {\n const isClient = useIsClient();\n const { themeClassName, portalContainer, tokenStyle, applyStylesToPortals } = useWaveTheme();\n const dir = useDirection();\n const depth = React.useContext(PortalDepthContext);\n const parentLayerId = React.useContext(DismissLayerContext);\n\n const registerWithParent = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el || parentLayerId === null) return;\n return registerLayerElement(parentLayerId, el);\n },\n [parentLayerId],\n );\n const wrapperRef = useMergedRefs<HTMLDivElement>(ref, registerWithParent);\n\n const content =\n layerId === undefined ? (\n children\n ) : (\n <DismissLayerProvider layerId={layerId}>{children}</DismissLayerProvider>\n );\n\n if (disabled) return <>{content}</>;\n if (!isClient) return null;\n\n const target = container ?? portalContainer ?? document.body;\n\n return createPortal(\n <div\n ref={wrapperRef}\n className={cn(portalClassNames.root, themeClassName, className)}\n dir={dir}\n data-wave-portal=\"\"\n data-layer={layer}\n // The provider's token overrides first, so the wrapper's own two properties win.\n style={{\n ...(applyStylesToPortals ? tokenStyle : undefined),\n position: 'relative',\n zIndex: `calc(${Z_INDEX[layer]} + ${depth})`,\n }}\n >\n <PortalDepthContext.Provider value={depth + 1}>{content}</PortalDepthContext.Provider>\n </div>,\n target,\n );\n};\n\nPortal.displayName = 'Portal';\n"],"mappings":"6dAyCA,IAAayB,EAAqBzB,EAAM,cAAc,CAAC,EACvD,EAAmB,YAAc,qBAqBjC,IAAa0B,GAAU,CACrB,WACA,YACA,QAAQ,UACR,UACA,WAAW,GACX,YACA,SACiB,CACjB,IAAMC,EAAW1B,EAAAA,YAAY,EACvB,CAAE,iBAAgB,kBAAiB,aAAY,wBAAyBC,EAAAA,aAAa,EACrF0B,EAAMzB,EAAAA,aAAa,EACnB0B,EAAQ7B,EAAM,WAAWyB,CAAkB,EAC3CK,EAAgB9B,EAAM,WAAWI,EAAAA,mBAAmB,EAEpD2B,EAAqB/B,EAAM,YAC9B,GAA8B,CAC7B,GAAKgC,GAAMF,IAAkB,KAC7B,OAAOzB,EAAAA,qBAAqByB,EAAeE,CAAE,CAC/C,EACA,CAACF,CAAa,CAChB,EACMG,EAAa3B,EAAAA,cAA8B4B,EAAKH,CAAkB,EAElEI,EACJC,IAAY,IAAA,GACVC,GAEA,EAAAd,EAAA,IAAA,CAAChB,EAAAA,qBAAD,CAA+B,UAAU,UAA+B,CAAA,EAG5E,GAAI+B,EAAU,OAAO,EAAAf,EAAA,IAAA,CAAAf,EAAAA,SAAA,CAAA,SAAG2B,CAAU,CAAA,EAClC,GAAI,CAACR,EAAU,OAAO,KAEtB,IAAMY,EAASC,GAAaC,GAAmB,SAAS,KAExD,OAAA,EAAOhC,EAAAA,aAAAA,EACL,EAAAc,EAAA,IAAA,CAAC,MAAD,CACE,IAAKU,EACL,UAAWvB,EAAAA,GAAGC,EAAAA,iBAAiB,KAAM+B,EAAgBC,CAAS,EACzD,MACL,mBAAiB,GACjB,aAAYC,EAEZ,MAAO,CACL,GAAIC,EAAuBC,EAAa,IAAA,GACxC,SAAU,WACV,OAAQ,QAAQlC,EAAAA,QAAQgC,GAAO,KAAKf,EAAM,EAC5C,EAEA,UAAA,EAAAN,EAAA,IAAA,CAACE,EAAmB,SAApB,CAA6B,MAAOI,EAAQ,EAAI,SAAAM,CAAqC,CAAA,CAClF,CAAA,EACLI,CACF,CACF,EAEA,EAAO,YAAc"}
|
|
@@ -14,30 +14,31 @@ import { createPortal as p } from "react-dom";
|
|
|
14
14
|
var m = u.createContext(0);
|
|
15
15
|
m.displayName = "PortalDepthContext";
|
|
16
16
|
var h = ({ children: h, container: g, layer: _ = "overlay", layerId: v, disabled: y = !1, className: b, ref: x }) => {
|
|
17
|
-
let S = r(), { themeClassName: C, portalContainer: w } = i(),
|
|
18
|
-
if (e &&
|
|
19
|
-
}, [
|
|
17
|
+
let S = r(), { themeClassName: C, portalContainer: w, tokenStyle: T, applyStylesToPortals: E } = i(), D = a(), O = u.useContext(m), k = u.useContext(c), A = u.useCallback((e) => {
|
|
18
|
+
if (e && k !== null) return s(k, e);
|
|
19
|
+
}, [k]), j = n(x, A), M = v === void 0 ? h : /* @__PURE__ */ f(l, {
|
|
20
20
|
layerId: v,
|
|
21
21
|
children: h
|
|
22
22
|
});
|
|
23
|
-
if (y) return /* @__PURE__ */ f(d, { children:
|
|
23
|
+
if (y) return /* @__PURE__ */ f(d, { children: M });
|
|
24
24
|
if (!S) return null;
|
|
25
|
-
let
|
|
25
|
+
let N = g ?? w ?? document.body;
|
|
26
26
|
return p(/* @__PURE__ */ f("div", {
|
|
27
|
-
ref:
|
|
27
|
+
ref: j,
|
|
28
28
|
className: e(t.root, C, b),
|
|
29
|
-
dir:
|
|
29
|
+
dir: D,
|
|
30
30
|
"data-wave-portal": "",
|
|
31
31
|
"data-layer": _,
|
|
32
32
|
style: {
|
|
33
|
+
...E ? T : void 0,
|
|
33
34
|
position: "relative",
|
|
34
|
-
zIndex: `calc(${o[_]} + ${
|
|
35
|
+
zIndex: `calc(${o[_]} + ${O})`
|
|
35
36
|
},
|
|
36
37
|
children: /* @__PURE__ */ f(m.Provider, {
|
|
37
|
-
value:
|
|
38
|
-
children:
|
|
38
|
+
value: O + 1,
|
|
39
|
+
children: M
|
|
39
40
|
})
|
|
40
|
-
}),
|
|
41
|
+
}), N);
|
|
41
42
|
};
|
|
42
43
|
h.displayName = "Portal";
|
|
43
44
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Portal.mjs","names":["_Fragment","React","PortalDepthContext","Portal","isClient","useIsClient","useWaveTheme","dir","useDirection","depth","parentLayerId","DismissLayerContext","registerWithParent","el","registerLayerElement","wrapperRef","useMergedRefs","ref","content","layerId","children","DismissLayerProvider","disabled","Fragment","target","container","portalContainer","createPortal","cn","portalClassNames","themeClassName","className","layer","Z_INDEX"],"sources":["../../../src/components/portal/Portal.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '../../lib/cn';\nimport { portalClassNames } from '../../lib/classNames';\nimport { Z_INDEX, registerLayerElement } from '../../lib/layers';\nimport { useIsClient } from '../../hooks/useIsClient';\nimport { useDirection } from '../../hooks/useDirection';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { DismissLayerContext, DismissLayerProvider } from '../../hooks/useDismiss';\nimport { useWaveTheme } from '../provider/WaveProvider';\n\n/** Props accepted by {@link Portal}. */\nexport interface PortalProps {\n /** Content rendered into the portal. */\n children: React.ReactNode;\n /** Element to render into. @default WaveProvider `portalContainer`, else `document.body` */\n container?: HTMLElement | null;\n /**\n * Stacking layer: `--wave-z-overlay` (dialogs, popovers, menus, listboxes), `--wave-z-toast`\n * (above overlays) or `--wave-z-tooltip`. The portal's nesting depth is added.\n * @default 'overlay'\n */\n layer?: 'overlay' | 'toast' | 'tooltip';\n /**\n * The dismiss layer of the content (from `useDismiss`). Provided to the children through\n * `DismissLayerProvider`, so layers opened inside become its descendants.\n */\n layerId?: string;\n /** Render the children inline instead (no portal, no wrapper). @default false */\n disabled?: boolean;\n /** Extra classes for the portal wrapper. */\n className?: string;\n /** Ref to the portal wrapper `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Nesting depth of portals: `0` outside any portal; each `Portal` provides `depth + 1` to its\n * children. Portals add it to their z-index so a surface opened from inside another overlay always\n * stacks above it — also when both mount in the same commit.\n */\nexport const PortalDepthContext = React.createContext(0);\nPortalDepthContext.displayName = 'PortalDepthContext';\n\n/**\n * Renders its children into `document.body` (or the WaveProvider `portalContainer`), wrapped in a\n * `<div class=\"wave-portal …\">` that carries the provider's theme classes, direction and font, so\n * portaled overlays look like the rest of the themed tree. The wrapper paints no background.\n *\n * - **Client only, first commit**: nothing is rendered on the server or during hydration (no\n * mismatch); in every other client render the portal exists in the first commit, so layout\n * effects and ref callbacks of the content see connected elements.\n * - **Stacking**: `z-index: calc(<layer variable> + depth)`, where depth is the number of portals\n * it is rendered in, so a surface opened from inside another overlay always stacks above it,\n * also when both mount in the same commit.\n * - **Layers**: the wrapper is registered with the enclosing dismiss layer (the parent), so presses\n * and focus inside the portal count as inside that layer; `layerId` is provided only to the\n * children.\n *\n * Content that must exist on the server or before an overlay opens (tooltip descriptions, closed\n * listboxes) is rendered inline instead.\n */\nexport const Portal = ({\n children,\n container,\n layer = 'overlay',\n layerId,\n disabled = false,\n className,\n ref,\n}: PortalProps) => {\n const isClient = useIsClient();\n const { themeClassName, portalContainer } = useWaveTheme();\n const dir = useDirection();\n const depth = React.useContext(PortalDepthContext);\n const parentLayerId = React.useContext(DismissLayerContext);\n\n const registerWithParent = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el || parentLayerId === null) return;\n return registerLayerElement(parentLayerId, el);\n },\n [parentLayerId],\n );\n const wrapperRef = useMergedRefs<HTMLDivElement>(ref, registerWithParent);\n\n const content =\n layerId === undefined ? (\n children\n ) : (\n <DismissLayerProvider layerId={layerId}>{children}</DismissLayerProvider>\n );\n\n if (disabled) return <>{content}</>;\n if (!isClient) return null;\n\n const target = container ?? portalContainer ?? document.body;\n\n return createPortal(\n <div\n ref={wrapperRef}\n className={cn(portalClassNames.root, themeClassName, className)}\n dir={dir}\n data-wave-portal=\"\"\n data-layer={layer}\n style={{ position: 'relative'
|
|
1
|
+
{"version":3,"file":"Portal.mjs","names":["_Fragment","React","PortalDepthContext","Portal","isClient","useIsClient","useWaveTheme","dir","useDirection","depth","parentLayerId","DismissLayerContext","registerWithParent","el","registerLayerElement","wrapperRef","useMergedRefs","ref","content","layerId","children","DismissLayerProvider","disabled","Fragment","target","container","portalContainer","createPortal","cn","portalClassNames","themeClassName","className","layer","applyStylesToPortals","tokenStyle","Z_INDEX"],"sources":["../../../src/components/portal/Portal.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '../../lib/cn';\nimport { portalClassNames } from '../../lib/classNames';\nimport { Z_INDEX, registerLayerElement } from '../../lib/layers';\nimport { useIsClient } from '../../hooks/useIsClient';\nimport { useDirection } from '../../hooks/useDirection';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { DismissLayerContext, DismissLayerProvider } from '../../hooks/useDismiss';\nimport { useWaveTheme } from '../provider/WaveProvider';\n\n/** Props accepted by {@link Portal}. */\nexport interface PortalProps {\n /** Content rendered into the portal. */\n children: React.ReactNode;\n /** Element to render into. @default WaveProvider `portalContainer`, else `document.body` */\n container?: HTMLElement | null;\n /**\n * Stacking layer: `--wave-z-overlay` (dialogs, popovers, menus, listboxes), `--wave-z-toast`\n * (above overlays) or `--wave-z-tooltip`. The portal's nesting depth is added.\n * @default 'overlay'\n */\n layer?: 'overlay' | 'toast' | 'tooltip';\n /**\n * The dismiss layer of the content (from `useDismiss`). Provided to the children through\n * `DismissLayerProvider`, so layers opened inside become its descendants.\n */\n layerId?: string;\n /** Render the children inline instead (no portal, no wrapper). @default false */\n disabled?: boolean;\n /** Extra classes for the portal wrapper. */\n className?: string;\n /** Ref to the portal wrapper `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Nesting depth of portals: `0` outside any portal; each `Portal` provides `depth + 1` to its\n * children. Portals add it to their z-index so a surface opened from inside another overlay always\n * stacks above it — also when both mount in the same commit.\n */\nexport const PortalDepthContext = React.createContext(0);\nPortalDepthContext.displayName = 'PortalDepthContext';\n\n/**\n * Renders its children into `document.body` (or the WaveProvider `portalContainer`), wrapped in a\n * `<div class=\"wave-portal …\">` that carries the provider's theme classes, direction and font, and\n * its token overrides (WaveProvider `tokens`, unless its `applyStylesToPortals` is `false`), so\n * portaled overlays look like the rest of the themed tree. The wrapper paints no background.\n *\n * - **Client only, first commit**: nothing is rendered on the server or during hydration (no\n * mismatch); in every other client render the portal exists in the first commit, so layout\n * effects and ref callbacks of the content see connected elements.\n * - **Stacking**: `z-index: calc(<layer variable> + depth)`, where depth is the number of portals\n * it is rendered in, so a surface opened from inside another overlay always stacks above it,\n * also when both mount in the same commit.\n * - **Layers**: the wrapper is registered with the enclosing dismiss layer (the parent), so presses\n * and focus inside the portal count as inside that layer; `layerId` is provided only to the\n * children.\n *\n * Content that must exist on the server or before an overlay opens (tooltip descriptions, closed\n * listboxes) is rendered inline instead.\n */\nexport const Portal = ({\n children,\n container,\n layer = 'overlay',\n layerId,\n disabled = false,\n className,\n ref,\n}: PortalProps) => {\n const isClient = useIsClient();\n const { themeClassName, portalContainer, tokenStyle, applyStylesToPortals } = useWaveTheme();\n const dir = useDirection();\n const depth = React.useContext(PortalDepthContext);\n const parentLayerId = React.useContext(DismissLayerContext);\n\n const registerWithParent = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el || parentLayerId === null) return;\n return registerLayerElement(parentLayerId, el);\n },\n [parentLayerId],\n );\n const wrapperRef = useMergedRefs<HTMLDivElement>(ref, registerWithParent);\n\n const content =\n layerId === undefined ? (\n children\n ) : (\n <DismissLayerProvider layerId={layerId}>{children}</DismissLayerProvider>\n );\n\n if (disabled) return <>{content}</>;\n if (!isClient) return null;\n\n const target = container ?? portalContainer ?? document.body;\n\n return createPortal(\n <div\n ref={wrapperRef}\n className={cn(portalClassNames.root, themeClassName, className)}\n dir={dir}\n data-wave-portal=\"\"\n data-layer={layer}\n // The provider's token overrides first, so the wrapper's own two properties win.\n style={{\n ...(applyStylesToPortals ? tokenStyle : undefined),\n position: 'relative',\n zIndex: `calc(${Z_INDEX[layer]} + ${depth})`,\n }}\n >\n <PortalDepthContext.Provider value={depth + 1}>{content}</PortalDepthContext.Provider>\n </div>,\n target,\n );\n};\n\nPortal.displayName = 'Portal';\n"],"mappings":";;;;;;;;;;;;;AAyCA,IAAaE,IAAqBD,EAAM,cAAc,CAAC;AACvD,EAAmB,cAAc;AAqBjC,IAAaE,KAAU,EACrB,aACA,cACA,WAAQ,WACR,YACA,cAAW,IACX,cACA,aACiB;CACjB,IAAMC,IAAWC,EAAY,GACvB,EAAE,mBAAgB,oBAAiB,eAAY,4BAAyBC,EAAa,GACrFC,IAAMC,EAAa,GACnBC,IAAQR,EAAM,WAAWC,CAAkB,GAC3CQ,IAAgBT,EAAM,WAAWU,CAAmB,GAEpDC,IAAqBX,EAAM,aAC9B,MAA8B;EAC7B,IAAKY,KAAMH,MAAkB,MAC7B,OAAOI,EAAqBJ,GAAeG,CAAE;CAC/C,GACA,CAACH,CAAa,CAChB,GACMK,IAAaC,EAA8BC,GAAKL,CAAkB,GAElEM,IACJC,MAAY,KAAA,IACVC,IAEA,kBAACC,GAAD;EAA+B;EAAU;CAA+B,CAAA;CAG5E,IAAIC,GAAU,OAAO,kBAAAtB,GAAA,EAAA,UAAGkB,EAAU,CAAA;CAClC,IAAI,CAACd,GAAU,OAAO;CAEtB,IAAMoB,IAASC,KAAaC,KAAmB,SAAS;CAExD,OAAOC,EACL,kBAAC,OAAD;EACE,KAAKZ;EACL,WAAWa,EAAGC,EAAiB,MAAMC,GAAgBC,CAAS;EACzD;EACL,oBAAiB;EACjB,cAAYC;EAEZ,OAAO;GACL,GAAIC,IAAuBC,IAAa,KAAA;GACxC,UAAU;GACV,QAAQ,QAAQC,EAAQH,GAAO,KAAKvB,EAAM;EAC5C;EAEA,UAAA,kBAACP,EAAmB,UAApB;GAA6B,OAAOO,IAAQ;GAAI,UAAAS;EAAqC,CAAA;CAClF,CAAA,GACLM,CACF;AACF;AAEA,EAAO,cAAc"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../lib/theme.cjs");let
|
|
1
|
+
"use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../lib/tokens.cjs"),a=require("../../lib/theme.cjs");let o=require("react");o=e.__toESM(o,1);let s=require("react/jsx-runtime");var c=o.createContext({theme:`light`,dir:`ltr`,themeClassName:``,portalContainer:null,inputDefaults:{},motion:`default`,tokens:{},tokenStyle:void 0,applyStylesToPortals:!0,hasProvider:!1});function l(e,t){let n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every((n,i)=>n===r[i]&&Object.is(e[n],t[n]))}function u(){return o.useContext(c)}var d=({theme:e,dir:u,portalContainer:d,inputDefaults:f,motion:p,tokens:m,applyStylesToPortals:h,children:g,className:_,style:v,ref:y,...b})=>{let x=o.useContext(c),S=e??x.theme,C=u??x.dir,w=p??x.motion,T=h??x.applyStylesToPortals,E=d===void 0?x.portalContainer:d,D=i.mergeTokenOverrides(x.tokens,m),[O,k]=o.useState(D);l(O,D)||k(D);let{style:A,invalid:j}=o.useMemo(()=>i.collectTokenStyle(O),[O]);o.useEffect(()=>{for(let{key:e,reason:t}of j)n.warnOnce(`WaveProvider:tokens:${t}:${e}`,`WaveProvider: \`tokens\` skips \`${e}\`: `+(t===`name`?"not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).":"its value must be a finite number or a string without `;`, `{` or `}`."))},[j]);let M=f?.size??x.inputDefaults.size,N=f?.appearance??x.inputDefaults.appearance,P=o.useMemo(()=>{let e={};return M!==void 0&&(e.size=M),N!==void 0&&(e.appearance=N),e},[M,N]),F=a.isWaveTheme(S)?S:`light`,I=a.getThemeClassName(F);o.useEffect(()=>{a.isWaveTheme(S)||n.warnOnce(`WaveProvider:theme:${String(S)}`,`WaveProvider: unknown theme "${String(S)}". Valid themes: ${a.WAVE_THEMES.join(`, `)}. Using "light".`)},[S]);let L=o.useMemo(()=>({theme:F,dir:C,themeClassName:I,portalContainer:E,inputDefaults:P,motion:w,tokens:O,tokenStyle:A,applyStylesToPortals:T,hasProvider:!0}),[F,C,I,E,P,w,O,A,T]);return(0,s.jsx)(c.Provider,{value:L,children:(0,s.jsx)(`div`,{...b,style:A?{...A,...v}:v,ref:y,dir:C,"data-wave-theme":F,className:t.cn(r.waveProviderClassNames.root,I,`bg-background text-foreground font-wave text-body-1`,_),children:g})})};d.displayName=`WaveProvider`,exports.WaveProvider=d,exports.getThemeClassName=a.getThemeClassName,exports.useWaveTheme=u;
|
|
2
2
|
//# sourceMappingURL=WaveProvider.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaveProvider.cjs","names":["React","isWaveTheme","getThemeClassName","WAVE_THEMES","cn","waveProviderClassNames","require_runtime","require_cn","require_dev","require_classNames","require_theme","react","react_jsx_runtime","WaveContext","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","rest","parent","theme","dir","motion","portalContainer","size","appearance","inputDefaults","merged","resolvedTheme","themeClassName","value","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/**\n * Reads the theme, direction, theme classes and portal container of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n children,\n className,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n hasProvider: true,\n }),\n [resolvedTheme, dir, themeClassName, portalContainer, inputDefaults, motion],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":"oRAmGA,IAAMa,EAAcb,EAAM,cAAgC,CATxD,MAAO,QACP,IAAK,MACL,eAAgB,GAChB,gBAAiB,KACjB,cAAe,CAAC,EAChB,OAAQ,UACR,YAAa,EAG2C,CAAe,EAQzE,SAAgBc,GAAiC,CAC/C,OAAOd,EAAM,WAAWa,CAAW,CACrC,CA0BA,IAAaE,GAAgB,CAC3B,MAAOC,EACP,IAAKC,EACL,gBAAiBC,EACjB,cAAeC,EACf,OAAQC,EACR,WACA,YACA,MACA,GAAGC,KACoB,CAIvB,IAAMC,EAAStB,EAAM,WAAWa,CAAW,EACrCU,EAAQP,GAAaM,EAAO,MAC5BE,EAAMP,GAAWK,EAAO,IACxBG,EAASL,GAAcE,EAAO,OAC9BI,EACJR,IAAwB,IAAA,GAAYI,EAAO,gBAAkBJ,EAIzDS,EAAOR,GAAmB,MAAQG,EAAO,cAAc,KACvDM,EAAaT,GAAmB,YAAcG,EAAO,cAAc,WACnEO,EAAgB7B,EAAM,YAA6B,CACvD,IAAM8B,EAAwB,CAAC,EAG/B,OAFIH,IAAS,IAAA,KAAW,EAAO,KAAOA,GAClCC,IAAe,IAAA,KAAW,EAAO,WAAaA,GAC3CE,CACT,EAAG,CAACH,EAAMC,CAAU,CAAC,EAGfG,EAA2B9B,EAAAA,YAAYsB,CAAK,EAAIA,EAAQ,QACxDS,EAAiB9B,EAAAA,kBAAkB6B,CAAa,EAEtDpB,EAAM,cAAgB,CACfV,EAAAA,YAAYsB,CAAK,GACpBf,EAAA,SACE,sBAAsB,OAAOe,CAAK,IAClC,gCAAgC,OAAOA,CAAK,EAAE,mBAAmBpB,EAAAA,YAAY,KAAK,IAAI,EAAE,iBAC1F,CAEJ,EAAG,CAACoB,CAAK,CAAC,EAEV,IAAMU,EAAQjC,EAAM,aACX,CACL,MAAO+B,EACP,MACA,iBACA,kBACA,gBACA,SACA,YAAa,EACf,GACA,CAACA,EAAeP,EAAKQ,EAAgBN,EAAiBG,EAAeJ,CAAM,CAC7E,EAEA,OACE,EAAAb,EAAA,IAAA,CAACC,EAAY,SAAb,CAA6B,QAC3B,UAAA,EAAAD,EAAA,IAAA,CAAC,MAAD,CACE,GAAIS,EACC,MACA,MACL,kBAAiBU,EACjB,UAAW3B,EAAAA,GACTC,EAAAA,uBAAuB,KACvB2B,EACA,sDACAE,CACF,EAEC,UACE,CAAA,CACe,CAAA,CAE1B,EAEA,EAAa,YAAc"}
|
|
1
|
+
{"version":3,"file":"WaveProvider.cjs","names":["React","mergeTokenOverrides","collectTokenStyle","isWaveTheme","getThemeClassName","WAVE_THEMES","cn","waveProviderClassNames","require_runtime","require_cn","require_dev","require_classNames","require_tokens","require_theme","react","react_jsx_runtime","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","tokens","setTokens","tokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","themeClassName","value","style","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier, or a value that is neither a finite number nor a\n * string without `;`, `{` or `}` (such a value could add a declaration of its own to the\n * server-rendered `style` attribute), is skipped with a development warning; a well-formed name\n * is not checked against the list of tokens (TypeScript refuses a misspelled one).\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier, or whose value is neither a finite number nor a string without\n * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no\n * `style` attribute unless you pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number or a string without `;`, `{` or `}`.'),\n );\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":"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"}
|
|
@@ -2,60 +2,84 @@
|
|
|
2
2
|
import { cn as e } from "../../lib/cn.mjs";
|
|
3
3
|
import { warnOnce as t } from "../../lib/dev.mjs";
|
|
4
4
|
import { waveProviderClassNames as n } from "../../lib/classNames.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import { collectTokenStyle as r, mergeTokenOverrides as i } from "../../lib/tokens.mjs";
|
|
6
|
+
import { WAVE_THEMES as a, getThemeClassName as o, isWaveTheme as s } from "../../lib/theme.mjs";
|
|
7
|
+
import * as c from "react";
|
|
8
|
+
import { jsx as l } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/provider/WaveProvider.tsx
|
|
9
|
-
var
|
|
10
|
+
var u = c.createContext({
|
|
10
11
|
theme: "light",
|
|
11
12
|
dir: "ltr",
|
|
12
13
|
themeClassName: "",
|
|
13
14
|
portalContainer: null,
|
|
14
15
|
inputDefaults: {},
|
|
15
16
|
motion: "default",
|
|
17
|
+
tokens: {},
|
|
18
|
+
tokenStyle: void 0,
|
|
19
|
+
applyStylesToPortals: !0,
|
|
16
20
|
hasProvider: !1
|
|
17
21
|
});
|
|
18
|
-
function
|
|
19
|
-
|
|
22
|
+
function d(e, t) {
|
|
23
|
+
let n = Object.keys(e), r = Object.keys(t);
|
|
24
|
+
return n.length === r.length && n.every((n, i) => n === r[i] && Object.is(e[n], t[n]));
|
|
20
25
|
}
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
function f() {
|
|
27
|
+
return c.useContext(u);
|
|
28
|
+
}
|
|
29
|
+
var p = ({ theme: f, dir: p, portalContainer: m, inputDefaults: h, motion: g, tokens: _, applyStylesToPortals: v, children: y, className: b, style: x, ref: S, ...C }) => {
|
|
30
|
+
let w = c.useContext(u), T = f ?? w.theme, E = p ?? w.dir, D = g ?? w.motion, O = v ?? w.applyStylesToPortals, k = m === void 0 ? w.portalContainer : m, A = i(w.tokens, _), [j, M] = c.useState(A);
|
|
31
|
+
d(j, A) || M(A);
|
|
32
|
+
let { style: N, invalid: P } = c.useMemo(() => r(j), [j]);
|
|
33
|
+
c.useEffect(() => {
|
|
34
|
+
for (let { key: e, reason: n } of P) t(`WaveProvider:tokens:${n}:${e}`, `WaveProvider: \`tokens\` skips \`${e}\`: ` + (n === "name" ? "not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`)." : "its value must be a finite number or a string without `;`, `{` or `}`."));
|
|
35
|
+
}, [P]);
|
|
36
|
+
let F = h?.size ?? w.inputDefaults.size, I = h?.appearance ?? w.inputDefaults.appearance, L = c.useMemo(() => {
|
|
23
37
|
let e = {};
|
|
24
|
-
return
|
|
25
|
-
}, [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}, [
|
|
29
|
-
let
|
|
30
|
-
theme:
|
|
31
|
-
dir:
|
|
32
|
-
themeClassName:
|
|
33
|
-
portalContainer:
|
|
34
|
-
inputDefaults:
|
|
35
|
-
motion:
|
|
38
|
+
return F !== void 0 && (e.size = F), I !== void 0 && (e.appearance = I), e;
|
|
39
|
+
}, [F, I]), R = s(T) ? T : "light", z = o(R);
|
|
40
|
+
c.useEffect(() => {
|
|
41
|
+
s(T) || t(`WaveProvider:theme:${String(T)}`, `WaveProvider: unknown theme "${String(T)}". Valid themes: ${a.join(", ")}. Using "light".`);
|
|
42
|
+
}, [T]);
|
|
43
|
+
let B = c.useMemo(() => ({
|
|
44
|
+
theme: R,
|
|
45
|
+
dir: E,
|
|
46
|
+
themeClassName: z,
|
|
47
|
+
portalContainer: k,
|
|
48
|
+
inputDefaults: L,
|
|
49
|
+
motion: D,
|
|
50
|
+
tokens: j,
|
|
51
|
+
tokenStyle: N,
|
|
52
|
+
applyStylesToPortals: O,
|
|
36
53
|
hasProvider: !0
|
|
37
54
|
}), [
|
|
55
|
+
R,
|
|
38
56
|
E,
|
|
39
|
-
|
|
57
|
+
z,
|
|
58
|
+
k,
|
|
59
|
+
L,
|
|
40
60
|
D,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
j,
|
|
62
|
+
N,
|
|
63
|
+
O
|
|
44
64
|
]);
|
|
45
|
-
return /* @__PURE__ */
|
|
46
|
-
value:
|
|
47
|
-
children: /* @__PURE__ */
|
|
48
|
-
...
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
return /* @__PURE__ */ l(u.Provider, {
|
|
66
|
+
value: B,
|
|
67
|
+
children: /* @__PURE__ */ l("div", {
|
|
68
|
+
...C,
|
|
69
|
+
style: N ? {
|
|
70
|
+
...N,
|
|
71
|
+
...x
|
|
72
|
+
} : x,
|
|
73
|
+
ref: S,
|
|
74
|
+
dir: E,
|
|
75
|
+
"data-wave-theme": R,
|
|
76
|
+
className: e(n.root, z, "bg-background text-foreground font-wave text-body-1", b),
|
|
77
|
+
children: y
|
|
54
78
|
})
|
|
55
79
|
});
|
|
56
80
|
};
|
|
57
|
-
|
|
81
|
+
p.displayName = "WaveProvider";
|
|
58
82
|
//#endregion
|
|
59
|
-
export {
|
|
83
|
+
export { p as WaveProvider, o as getThemeClassName, f as useWaveTheme };
|
|
60
84
|
|
|
61
85
|
//# sourceMappingURL=WaveProvider.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaveProvider.mjs","names":["React","WaveContext","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","rest","parent","theme","dir","motion","portalContainer","size","appearance","inputDefaults","merged","resolvedTheme","isWaveTheme","themeClassName","getThemeClassName","warnOnce","WAVE_THEMES","value","cn","waveProviderClassNames","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/**\n * Reads the theme, direction, theme classes and portal container of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n children,\n className,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n hasProvider: true,\n }),\n [resolvedTheme, dir, themeClassName, portalContainer, inputDefaults, motion],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":";;;;;;;;AAmGA,IAAMC,IAAcD,EAAM,cAAgC;CATxD,OAAO;CACP,KAAK;CACL,gBAAgB;CAChB,iBAAiB;CACjB,eAAe,CAAC;CAChB,QAAQ;CACR,aAAa;AAG2C,CAAe;AAQzE,SAAgBE,IAAiC;CAC/C,OAAOF,EAAM,WAAWC,CAAW;AACrC;AA0BA,IAAaE,KAAgB,EAC3B,OAAOC,GACP,KAAKC,GACL,iBAAiBC,GACjB,eAAeC,GACf,QAAQC,GACR,aACA,cACA,QACA,GAAGC,QACoB;CAIvB,IAAMC,IAASV,EAAM,WAAWC,CAAW,GACrCU,IAAQP,KAAaM,EAAO,OAC5BE,IAAMP,KAAWK,EAAO,KACxBG,IAASL,KAAcE,EAAO,QAC9BI,IACJR,MAAwB,KAAA,IAAYI,EAAO,kBAAkBJ,GAIzDS,IAAOR,GAAmB,QAAQG,EAAO,cAAc,MACvDM,IAAaT,GAAmB,cAAcG,EAAO,cAAc,YACnEO,IAAgBjB,EAAM,cAA6B;EACvD,IAAMkB,IAAwB,CAAC;EAG/B,OAFIH,MAAS,KAAA,MAAW,EAAO,OAAOA,IAClCC,MAAe,KAAA,MAAW,EAAO,aAAaA,IAC3CE;CACT,GAAG,CAACH,GAAMC,CAAU,CAAC,GAGfG,IAA2BC,EAAYT,CAAK,IAAIA,IAAQ,SACxDU,IAAiBC,EAAkBH,CAAa;CAEtD,EAAM,gBAAgB;EACpB,AAAKC,EAAYT,CAAK,KACpBY,EACE,sBAAsB,OAAOZ,CAAK,KAClC,gCAAgC,OAAOA,CAAK,EAAE,mBAAmBa,EAAY,KAAK,IAAI,EAAE,iBAC1F;CAEJ,GAAG,CAACb,CAAK,CAAC;CAEV,IAAMc,IAAQzB,EAAM,eACX;EACL,OAAOmB;EACP;EACA;EACA;EACA;EACA;EACA,aAAa;CACf,IACA;EAACA;EAAeP;EAAKS;EAAgBP;EAAiBG;EAAeJ;CAAM,CAC7E;CAEA,OACE,kBAACZ,EAAY,UAAb;EAA6B;EAC3B,UAAA,kBAAC,OAAD;GACE,GAAIQ;GACC;GACA;GACL,mBAAiBU;GACjB,WAAWO,EACTC,EAAuB,MACvBN,GACA,uDACAO,CACF;GAEC;EACE,CAAA;CACe,CAAA;AAE1B;AAEA,EAAa,cAAc"}
|
|
1
|
+
{"version":3,"file":"WaveProvider.mjs","names":["React","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","mergeTokenOverrides","tokens","setTokens","tokenStyle","collectTokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","isWaveTheme","themeClassName","getThemeClassName","warnOnce","WAVE_THEMES","value","style","cn","waveProviderClassNames","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier, or a value that is neither a finite number nor a\n * string without `;`, `{` or `}` (such a value could add a declaration of its own to the\n * server-rendered `style` attribute), is skipped with a development warning; a well-formed name\n * is not checked against the list of tokens (TypeScript refuses a misspelled one).\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier, or whose value is neither a finite number nor a string without\n * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no\n * `style` attribute unless you pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number or a string without `;`, `{` or `}`.'),\n );\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":";;;;;;;;;AAuKA,IAAMC,IAAcD,EAAM,cAAgC;CAZxD,OAAO;CACP,KAAK;CACL,gBAAgB;CAChB,iBAAiB;CACjB,eAAe,CAAC;CAChB,QAAQ;CACR,QAAQ,CAAC;CACT,YAAY,KAAA;CACZ,sBAAsB;CACtB,aAAa;AAG2C,CAAe;AAGzE,SAASE,EAAW,GAAuB,GAAgC;CACzE,IAAMC,IAAO,OAAO,KAAKC,CAAC,GACpBC,IAAS,OAAO,KAAKC,CAAC;CAC5B,OACEH,EAAK,WAAWE,EAAO,UACvBF,EAAK,OAAO,GAAK,MAAUI,MAAQF,EAAOG,MAAU,OAAO,GAAGJ,EAAEG,IAAMD,EAAEC,EAAI,CAAC;AAEjF;AAQA,SAAgBE,IAAiC;CAC/C,OAAOT,EAAM,WAAWC,CAAW;AACrC;AAmCA,IAAaS,KAAgB,EAC3B,OAAOC,GACP,KAAKC,GACL,iBAAiBC,GACjB,eAAeC,GACf,QAAQC,GACR,QAAQC,GACR,sBAAsBC,GACtB,aACA,cACA,UACA,QACA,GAAGC,QACoB;CAIvB,IAAMC,IAASnB,EAAM,WAAWC,CAAW,GACrCmB,IAAQT,KAAaQ,EAAO,OAC5BE,IAAMT,KAAWO,EAAO,KACxBG,IAASP,KAAcI,EAAO,QAC9BI,IAAuBN,KAA4BE,EAAO,sBAC1DK,IACJX,MAAwB,KAAA,IAAYM,EAAO,kBAAkBN,GAKzDY,IAAeC,EAAoBP,EAAO,QAAQH,CAAU,GAC5D,CAACW,GAAQC,KAAa5B,EAAM,SAASyB,CAAY;CACvD,AAAKvB,EAAWyB,GAAQF,CAAY,KAAGG,EAAUH,CAAY;CAE7D,IAAM,EAAE,OAAOI,GAAY,eAAY7B,EAAM,cAAc8B,EAAkBH,CAAM,GAAG,CAACA,CAAM,CAAC;CAE9F,EAAM,gBAAgB;EACpB,KAAK,IAAM,EAAE,QAAK,eAAYI,GAC5B,EACE,uBAAuBC,EAAO,GAAGzB,KACjC,oCAAoCA,EAAI,SACrCyB,MAAW,SACR,qGACA,yEACR;CAEJ,GAAG,CAACD,CAAO,CAAC;CAIZ,IAAME,IAAOnB,GAAmB,QAAQK,EAAO,cAAc,MACvDe,IAAapB,GAAmB,cAAcK,EAAO,cAAc,YACnEgB,IAAgBnC,EAAM,cAA6B;EACvD,IAAMoC,IAAwB,CAAC;EAG/B,OAFIH,MAAS,KAAA,MAAW,EAAO,OAAOA,IAClCC,MAAe,KAAA,MAAW,EAAO,aAAaA,IAC3CE;CACT,GAAG,CAACH,GAAMC,CAAU,CAAC,GAGfG,IAA2BC,EAAYlB,CAAK,IAAIA,IAAQ,SACxDmB,IAAiBC,EAAkBH,CAAa;CAEtD,EAAM,gBAAgB;EACpB,AAAKC,EAAYlB,CAAK,KACpBqB,EACE,sBAAsB,OAAOrB,CAAK,KAClC,gCAAgC,OAAOA,CAAK,EAAE,mBAAmBsB,EAAY,KAAK,IAAI,EAAE,iBAC1F;CAEJ,GAAG,CAACtB,CAAK,CAAC;CAEV,IAAMuB,IAAQ3C,EAAM,eACX;EACL,OAAOqC;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;CACf,IACA;EACEA;EACAhB;EACAkB;EACAf;EACAW;EACAb;EACAK;EACAE;EACAN;CACF,CACF;CAEA,OACE,kBAACtB,EAAY,UAAb;EAA6B;EAC3B,UAAA,kBAAC,OAAD;GACE,GAAIiB;GAIJ,OAAOW,IAAa;IAAE,GAAGA;IAAY,GAAGe;GAAM,IAAIA;GAC7C;GACA;GACL,mBAAiBP;GACjB,WAAWQ,EACTC,EAAuB,MACvBP,GACA,uDACAQ,CACF;GAEC;EACE,CAAA;CACe,CAAA;AAE1B;AAEA,EAAa,cAAc"}
|