@iris-ui-kit/react 0.1.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/LICENSE +21 -0
- package/dist/SidebarLayout-fWQRvK0a.d.cts +26 -0
- package/dist/SidebarLayout-fWQRvK0a.d.ts +26 -0
- package/dist/admin.cjs +1583 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +183 -0
- package/dist/admin.d.ts +183 -0
- package/dist/admin.js +9 -0
- package/dist/admin.js.map +1 -0
- package/dist/async.cjs +97 -0
- package/dist/async.cjs.map +1 -0
- package/dist/async.d.cts +57 -0
- package/dist/async.d.ts +57 -0
- package/dist/async.js +5 -0
- package/dist/async.js.map +1 -0
- package/dist/behaviors.cjs +569 -0
- package/dist/behaviors.cjs.map +1 -0
- package/dist/behaviors.d.cts +188 -0
- package/dist/behaviors.d.ts +188 -0
- package/dist/behaviors.js +4 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/chunk-2TQSTINK.js +541 -0
- package/dist/chunk-2TQSTINK.js.map +1 -0
- package/dist/chunk-53KFLVIZ.js +68 -0
- package/dist/chunk-53KFLVIZ.js.map +1 -0
- package/dist/chunk-6BMFMCMQ.js +140 -0
- package/dist/chunk-6BMFMCMQ.js.map +1 -0
- package/dist/chunk-AUA5GBT6.js +143 -0
- package/dist/chunk-AUA5GBT6.js.map +1 -0
- package/dist/chunk-DE6V6NOI.js +122 -0
- package/dist/chunk-DE6V6NOI.js.map +1 -0
- package/dist/chunk-HEJGN6UI.js +78 -0
- package/dist/chunk-HEJGN6UI.js.map +1 -0
- package/dist/chunk-IDD35UJ6.js +83 -0
- package/dist/chunk-IDD35UJ6.js.map +1 -0
- package/dist/chunk-J5FB2UB2.js +166 -0
- package/dist/chunk-J5FB2UB2.js.map +1 -0
- package/dist/chunk-OMOTUHYI.js +66 -0
- package/dist/chunk-OMOTUHYI.js.map +1 -0
- package/dist/chunk-RZNYIFV2.js +34 -0
- package/dist/chunk-RZNYIFV2.js.map +1 -0
- package/dist/chunk-SJO74IB3.js +54 -0
- package/dist/chunk-SJO74IB3.js.map +1 -0
- package/dist/chunk-SORLRLS4.js +53 -0
- package/dist/chunk-SORLRLS4.js.map +1 -0
- package/dist/chunk-TO7YVSAI.js +64 -0
- package/dist/chunk-TO7YVSAI.js.map +1 -0
- package/dist/chunk-TQ33XJIK.js +22 -0
- package/dist/chunk-TQ33XJIK.js.map +1 -0
- package/dist/chunk-UWUUEZCD.js +950 -0
- package/dist/chunk-UWUUEZCD.js.map +1 -0
- package/dist/chunk-VWCJJRSC.js +1200 -0
- package/dist/chunk-VWCJJRSC.js.map +1 -0
- package/dist/chunk-WIHBIDVJ.js +54 -0
- package/dist/chunk-WIHBIDVJ.js.map +1 -0
- package/dist/chunk-XCK4RIXB.js +66 -0
- package/dist/chunk-XCK4RIXB.js.map +1 -0
- package/dist/chunk-YAHXR2CJ.js +48 -0
- package/dist/chunk-YAHXR2CJ.js.map +1 -0
- package/dist/chunk-YXIO5THG.js +4 -0
- package/dist/chunk-YXIO5THG.js.map +1 -0
- package/dist/chunk-ZKBDUWUD.js +156 -0
- package/dist/chunk-ZKBDUWUD.js.map +1 -0
- package/dist/data.cjs +112 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +53 -0
- package/dist/data.d.ts +53 -0
- package/dist/data.js +5 -0
- package/dist/data.js.map +1 -0
- package/dist/error-boundary.cjs +106 -0
- package/dist/error-boundary.cjs.map +1 -0
- package/dist/error-boundary.d.cts +41 -0
- package/dist/error-boundary.d.ts +41 -0
- package/dist/error-boundary.js +6 -0
- package/dist/error-boundary.js.map +1 -0
- package/dist/floating.cjs +163 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +92 -0
- package/dist/floating.d.ts +92 -0
- package/dist/floating.js +5 -0
- package/dist/floating.js.map +1 -0
- package/dist/form.cjs +159 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +120 -0
- package/dist/form.d.ts +120 -0
- package/dist/form.js +5 -0
- package/dist/form.js.map +1 -0
- package/dist/i18n.cjs +100 -0
- package/dist/i18n.cjs.map +1 -0
- package/dist/i18n.d.cts +50 -0
- package/dist/i18n.d.ts +50 -0
- package/dist/i18n.js +5 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.cjs +16635 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2964 -0
- package/dist/index.d.ts +2964 -0
- package/dist/index.js +12217 -0
- package/dist/index.js.map +1 -0
- package/dist/layouts.cjs +386 -0
- package/dist/layouts.cjs.map +1 -0
- package/dist/layouts.d.cts +92 -0
- package/dist/layouts.d.ts +92 -0
- package/dist/layouts.js +6 -0
- package/dist/layouts.js.map +1 -0
- package/dist/modal-utils.cjs +168 -0
- package/dist/modal-utils.cjs.map +1 -0
- package/dist/modal-utils.d.cts +43 -0
- package/dist/modal-utils.d.ts +43 -0
- package/dist/modal-utils.js +4 -0
- package/dist/modal-utils.js.map +1 -0
- package/dist/motion.cjs +104 -0
- package/dist/motion.cjs.map +1 -0
- package/dist/motion.d.cts +41 -0
- package/dist/motion.d.ts +41 -0
- package/dist/motion.js +4 -0
- package/dist/motion.js.map +1 -0
- package/dist/provider.cjs +191 -0
- package/dist/provider.cjs.map +1 -0
- package/dist/provider.d.cts +62 -0
- package/dist/provider.d.ts +62 -0
- package/dist/provider.js +8 -0
- package/dist/provider.js.map +1 -0
- package/dist/resource.cjs +61 -0
- package/dist/resource.cjs.map +1 -0
- package/dist/resource.d.cts +21 -0
- package/dist/resource.d.ts +21 -0
- package/dist/resource.js +5 -0
- package/dist/resource.js.map +1 -0
- package/dist/skeletons.cjs +1304 -0
- package/dist/skeletons.cjs.map +1 -0
- package/dist/skeletons.d.cts +70 -0
- package/dist/skeletons.d.ts +70 -0
- package/dist/skeletons.js +8 -0
- package/dist/skeletons.js.map +1 -0
- package/dist/skins.cjs +134 -0
- package/dist/skins.cjs.map +1 -0
- package/dist/skins.d.cts +45 -0
- package/dist/skins.d.ts +45 -0
- package/dist/skins.js +5 -0
- package/dist/skins.js.map +1 -0
- package/dist/theme.cjs +100 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +71 -0
- package/dist/theme.d.ts +71 -0
- package/dist/theme.js +5 -0
- package/dist/theme.js.map +1 -0
- package/dist/undo.cjs +73 -0
- package/dist/undo.cjs.map +1 -0
- package/dist/undo.d.cts +47 -0
- package/dist/undo.d.ts +47 -0
- package/dist/undo.js +4 -0
- package/dist/undo.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useStore } from './chunk-RZNYIFV2.js';
|
|
3
|
+
import * as React2 from 'react';
|
|
4
|
+
import { createDataSource, createGroupedView } from '@iris-ui-kit/core';
|
|
5
|
+
export { createClientDataSource, createDataSource, createGroupedView } from '@iris-ui-kit/core';
|
|
6
|
+
|
|
7
|
+
function useDataSource(config) {
|
|
8
|
+
const ref = React2.useRef(null);
|
|
9
|
+
if (ref.current === null) ref.current = createDataSource({ ...config, immediate: false });
|
|
10
|
+
const controller = ref.current;
|
|
11
|
+
const immediate = config.immediate !== false;
|
|
12
|
+
React2.useEffect(() => {
|
|
13
|
+
if (immediate) void controller.load();
|
|
14
|
+
return () => controller.destroy();
|
|
15
|
+
}, [controller, immediate]);
|
|
16
|
+
const state = useStore(controller.store);
|
|
17
|
+
return React2.useMemo(() => ({ ...controller, state }), [controller, state]);
|
|
18
|
+
}
|
|
19
|
+
function useGroupedView(config) {
|
|
20
|
+
const ref = React2.useRef(null);
|
|
21
|
+
if (ref.current === null) ref.current = createGroupedView(config);
|
|
22
|
+
const controller = ref.current;
|
|
23
|
+
const storeState = useStore(controller.store);
|
|
24
|
+
const state = storeState.state;
|
|
25
|
+
const setRows = React2.useCallback(
|
|
26
|
+
(rows, columns) => {
|
|
27
|
+
controller.setRows(rows, columns);
|
|
28
|
+
},
|
|
29
|
+
[controller]
|
|
30
|
+
);
|
|
31
|
+
const toggleGroup = React2.useCallback((key) => controller.toggleGroup(key), [controller]);
|
|
32
|
+
const expandGroup = React2.useCallback((key) => controller.expandGroup(key), [controller]);
|
|
33
|
+
const collapseGroup = React2.useCallback((key) => controller.collapseGroup(key), [controller]);
|
|
34
|
+
const expandAll = React2.useCallback(() => controller.expandAll(), [controller]);
|
|
35
|
+
const collapseAll = React2.useCallback(() => controller.collapseAll(), [controller]);
|
|
36
|
+
const setConfig = React2.useCallback(
|
|
37
|
+
(partial) => controller.setConfig(partial),
|
|
38
|
+
[controller]
|
|
39
|
+
);
|
|
40
|
+
return React2.useMemo(
|
|
41
|
+
() => ({
|
|
42
|
+
store: controller.store,
|
|
43
|
+
setRows,
|
|
44
|
+
toggleGroup,
|
|
45
|
+
expandGroup,
|
|
46
|
+
collapseGroup,
|
|
47
|
+
expandAll,
|
|
48
|
+
collapseAll,
|
|
49
|
+
state,
|
|
50
|
+
setConfig
|
|
51
|
+
}),
|
|
52
|
+
[
|
|
53
|
+
controller,
|
|
54
|
+
setRows,
|
|
55
|
+
toggleGroup,
|
|
56
|
+
expandGroup,
|
|
57
|
+
collapseGroup,
|
|
58
|
+
expandAll,
|
|
59
|
+
collapseAll,
|
|
60
|
+
state,
|
|
61
|
+
setConfig
|
|
62
|
+
]
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { useDataSource, useGroupedView };
|
|
67
|
+
//# sourceMappingURL=chunk-53KFLVIZ.js.map
|
|
68
|
+
//# sourceMappingURL=chunk-53KFLVIZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/useDataSource.ts","../src/data/useGroupedView.ts"],"names":["React"],"mappings":"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","file":"chunk-53KFLVIZ.js","sourcesContent":["import * as React from 'react'\nimport {\n createDataSource,\n type DataSourceController,\n type DataSourceConfig,\n type DataSourceState,\n} from '@iris-ui-kit/core'\nimport { useStore } from '../useStore'\n\nexport interface UseDataSource<T> extends DataSourceController<T> {\n /**\n * The live data-source state: rows, total, page/pageSize, sort/multiSort,\n * filters/filterRules, loading/loadingMore, hasMore, selectedKeys, and the\n * per-row pendingRows/rowErrors.\n */\n state: DataSourceState<T>\n}\n\n/**\n * React bridge over the framework-agnostic {@link createDataSource} — the unified\n * data engine (paged/infinite + multi-sort + typed filters + selection +\n * per-row + optimistic mutate). Creates the controller once, subscribes to its\n * store, and returns the controller plus its live `state`. A thin bridge — all\n * logic lives in `@iris-ui-kit/core`.\n *\n * Constructed with `immediate: false` so no fetch fires during render; the\n * initial load is kicked from an effect and the controller is torn down on\n * unmount (aborting any in-flight request) so a late response never writes back.\n */\nexport function useDataSource<T>(config: DataSourceConfig<T>): UseDataSource<T> {\n const ref = React.useRef<DataSourceController<T> | null>(null)\n if (ref.current === null) ref.current = createDataSource({ ...config, immediate: false })\n const controller = ref.current\n const immediate = config.immediate !== false\n\n React.useEffect(() => {\n if (immediate) void controller.load()\n return () => controller.destroy()\n }, [controller, immediate])\n\n const state = useStore(controller.store)\n return React.useMemo(() => ({ ...controller, state }), [controller, state])\n}\n","import * as React from 'react'\nimport { createGroupedView, type GroupedViewConfig, type GroupedViewState } from '@iris-ui-kit/core'\nimport { useStore } from '../useStore'\n\nexport interface UseGroupedView<Row, K = string> {\n /** Subscribable store with the full grouped state. */\n store: ReturnType<typeof createGroupedView<Row, K>>['store']\n /** Set the source rows (re-computes groups and aggregates). */\n setRows: (\n rows: readonly Row[],\n columns?: readonly import('@iris-ui-kit/core').DataViewColumn<Row>[],\n ) => void\n /** Toggle a group's expanded state. */\n toggleGroup: (key: K) => void\n /** Expand a specific group. */\n expandGroup: (key: K) => void\n /** Collapse a specific group. */\n collapseGroup: (key: K) => void\n /** Expand all groups. */\n expandAll: () => void\n /** Collapse all groups. */\n collapseAll: () => void\n /** The live grouped-view state snapshot. */\n state: GroupedViewState<Row, K>\n /** Update config. */\n setConfig: (partial: Partial<GroupedViewConfig<Row, K>>) => void\n}\n\n/**\n * React bridge over the framework-agnostic {@link createGroupedView}.\n * Creates the controller once (ref), subscribes to its store, and returns\n * the controller plus its live `state`.\n */\nexport function useGroupedView<Row, K = string>(\n config: GroupedViewConfig<Row, K>,\n): UseGroupedView<Row, K> {\n const ref = React.useRef<ReturnType<typeof createGroupedView<Row, K>> | null>(null)\n if (ref.current === null) ref.current = createGroupedView<Row, K>(config)\n const controller = ref.current\n\n const storeState = useStore(controller.store)\n const state: GroupedViewState<Row, K> = storeState.state\n\n const setRows = React.useCallback(\n (\n rows: readonly Row[],\n columns?: readonly import('@iris-ui-kit/core').DataViewColumn<Row>[],\n ) => {\n controller.setRows(rows, columns)\n },\n [controller],\n )\n\n const toggleGroup = React.useCallback((key: K) => controller.toggleGroup(key), [controller])\n const expandGroup = React.useCallback((key: K) => controller.expandGroup(key), [controller])\n const collapseGroup = React.useCallback((key: K) => controller.collapseGroup(key), [controller])\n const expandAll = React.useCallback(() => controller.expandAll(), [controller])\n const collapseAll = React.useCallback(() => controller.collapseAll(), [controller])\n const setConfig = React.useCallback(\n (partial: Partial<GroupedViewConfig<Row, K>>) => controller.setConfig(partial),\n [controller],\n )\n\n return React.useMemo(\n () => ({\n store: controller.store,\n setRows,\n toggleGroup,\n expandGroup,\n collapseGroup,\n expandAll,\n collapseAll,\n state,\n setConfig,\n }),\n [\n controller,\n setRows,\n toggleGroup,\n expandGroup,\n collapseGroup,\n expandAll,\n collapseAll,\n state,\n setConfig,\n ],\n )\n}\n"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { offset, flip, shift, size, arrow, computePosition, autoUpdate } from '@floating-ui/dom';
|
|
4
|
+
|
|
5
|
+
// src/floating/useFloating.ts
|
|
6
|
+
function useFloating(options) {
|
|
7
|
+
const {
|
|
8
|
+
anchor,
|
|
9
|
+
floating,
|
|
10
|
+
open,
|
|
11
|
+
placement = "bottom",
|
|
12
|
+
strategy = "absolute",
|
|
13
|
+
offset: offset$1,
|
|
14
|
+
flip: flip$1 = true,
|
|
15
|
+
shift: shift$1 = true,
|
|
16
|
+
size: size$1 = false,
|
|
17
|
+
middleware: extraMiddleware,
|
|
18
|
+
arrow: arrow$1
|
|
19
|
+
} = options;
|
|
20
|
+
const [x, setX] = React.useState(0);
|
|
21
|
+
const [y, setY] = React.useState(0);
|
|
22
|
+
const [finalPlacement, setFinalPlacement] = React.useState(placement);
|
|
23
|
+
const [arrowX, setArrowX] = React.useState();
|
|
24
|
+
const [arrowY, setArrowY] = React.useState();
|
|
25
|
+
const middleware = React.useMemo(() => {
|
|
26
|
+
const mw = [];
|
|
27
|
+
if (offset$1 !== void 0) mw.push(offset(offset$1));
|
|
28
|
+
if (flip$1) mw.push(flip());
|
|
29
|
+
if (shift$1) mw.push(shift({ padding: 8 }));
|
|
30
|
+
if (size$1) {
|
|
31
|
+
const padding = typeof size$1 === "number" ? size$1 : 8;
|
|
32
|
+
mw.push(
|
|
33
|
+
size({
|
|
34
|
+
padding,
|
|
35
|
+
apply({ availableWidth, availableHeight, elements }) {
|
|
36
|
+
Object.assign(elements.floating.style, {
|
|
37
|
+
maxWidth: `${Math.max(0, Math.floor(availableWidth))}px`,
|
|
38
|
+
maxHeight: `${Math.max(0, Math.floor(availableHeight))}px`
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (arrow$1?.current) mw.push(arrow({ element: arrow$1.current }));
|
|
45
|
+
if (extraMiddleware) mw.push(...extraMiddleware);
|
|
46
|
+
return mw;
|
|
47
|
+
}, [offset$1, flip$1, shift$1, size$1, extraMiddleware, arrow$1]);
|
|
48
|
+
const middlewareRef = React.useRef(middleware);
|
|
49
|
+
middlewareRef.current = middleware;
|
|
50
|
+
const epochRef = React.useRef(0);
|
|
51
|
+
const update = React.useCallback(async () => {
|
|
52
|
+
const a = anchor.current;
|
|
53
|
+
const f = floating.current;
|
|
54
|
+
if (!a || !f) return;
|
|
55
|
+
const token = ++epochRef.current;
|
|
56
|
+
const result = await computePosition(a, f, {
|
|
57
|
+
placement,
|
|
58
|
+
strategy,
|
|
59
|
+
middleware: middlewareRef.current
|
|
60
|
+
});
|
|
61
|
+
if (token !== epochRef.current) return;
|
|
62
|
+
setX(result.x);
|
|
63
|
+
setY(result.y);
|
|
64
|
+
setFinalPlacement(result.placement);
|
|
65
|
+
const arrowData = result.middlewareData.arrow;
|
|
66
|
+
setArrowX(arrowData?.x);
|
|
67
|
+
setArrowY(arrowData?.y);
|
|
68
|
+
}, [anchor, floating, placement, strategy]);
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
if (!open) return;
|
|
71
|
+
const a = anchor.current;
|
|
72
|
+
const f = floating.current;
|
|
73
|
+
if (!a || !f) return;
|
|
74
|
+
const cleanup = autoUpdate(a, f, () => {
|
|
75
|
+
void update();
|
|
76
|
+
});
|
|
77
|
+
return () => {
|
|
78
|
+
epochRef.current++;
|
|
79
|
+
cleanup();
|
|
80
|
+
};
|
|
81
|
+
}, [open, anchor, floating, update]);
|
|
82
|
+
const floatingStyles = React.useMemo(
|
|
83
|
+
() => ({
|
|
84
|
+
position: strategy,
|
|
85
|
+
top: 0,
|
|
86
|
+
left: 0,
|
|
87
|
+
transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
|
|
88
|
+
width: "max-content"
|
|
89
|
+
}),
|
|
90
|
+
[strategy, x, y]
|
|
91
|
+
);
|
|
92
|
+
const OPPOSITE = {
|
|
93
|
+
top: "bottom",
|
|
94
|
+
bottom: "top",
|
|
95
|
+
left: "right",
|
|
96
|
+
right: "left"
|
|
97
|
+
};
|
|
98
|
+
const arrowSide = OPPOSITE[finalPlacement.split("-")[0]];
|
|
99
|
+
return { x, y, strategy, finalPlacement, floatingStyles, update, arrowX, arrowY, arrowSide };
|
|
100
|
+
}
|
|
101
|
+
function useDismiss(options) {
|
|
102
|
+
const { enabled, escape = true, outsidePointerDown = true } = options;
|
|
103
|
+
const onDismissRef = React.useRef(options.onDismiss);
|
|
104
|
+
const excludeRef = React.useRef(options.exclude);
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
onDismissRef.current = options.onDismiss;
|
|
107
|
+
excludeRef.current = options.exclude;
|
|
108
|
+
});
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
if (!enabled || typeof document === "undefined") return;
|
|
111
|
+
const isExcluded = (target) => {
|
|
112
|
+
if (!target || !(target instanceof Node)) return false;
|
|
113
|
+
for (const r of excludeRef.current) {
|
|
114
|
+
const el = r.current;
|
|
115
|
+
if (el && (el === target || el.contains(target))) return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
};
|
|
119
|
+
const onPointerDown = (event) => {
|
|
120
|
+
if (isExcluded(event.target)) return;
|
|
121
|
+
onDismissRef.current();
|
|
122
|
+
};
|
|
123
|
+
const onKeyDown = (event) => {
|
|
124
|
+
if (event.key === "Escape") {
|
|
125
|
+
event.stopPropagation();
|
|
126
|
+
onDismissRef.current();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
if (outsidePointerDown) document.addEventListener("pointerdown", onPointerDown, true);
|
|
130
|
+
if (escape) document.addEventListener("keydown", onKeyDown);
|
|
131
|
+
return () => {
|
|
132
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
133
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
134
|
+
};
|
|
135
|
+
}, [enabled, escape, outsidePointerDown]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { useDismiss, useFloating };
|
|
139
|
+
//# sourceMappingURL=chunk-6BMFMCMQ.js.map
|
|
140
|
+
//# sourceMappingURL=chunk-6BMFMCMQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/floating/useFloating.ts","../src/floating/useDismiss.ts"],"names":["offset","flip","shift","size","arrow","offsetMiddleware","flipMiddleware","shiftMiddleware","sizeMiddleware","arrowMiddleware","React2"],"mappings":"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","file":"chunk-6BMFMCMQ.js","sourcesContent":["import * as React from 'react'\nimport {\n autoUpdate,\n computePosition,\n arrow as arrowMiddleware,\n flip as flipMiddleware,\n offset as offsetMiddleware,\n shift as shiftMiddleware,\n size as sizeMiddleware,\n type Middleware,\n type Placement,\n type Strategy,\n} from '@floating-ui/dom'\n\nexport interface UseFloatingOptions {\n /** Element the floating panel is positioned relative to. */\n anchor: React.RefObject<HTMLElement | null>\n /** The floating element itself. */\n floating: React.RefObject<HTMLElement | null>\n /** Whether the floating element is visible. Controls `autoUpdate` lifecycle. */\n open: boolean\n /** Default placement. Floating UI may flip it if `flip` is true. */\n placement?: Placement\n /** CSS position strategy. */\n strategy?: Strategy\n /** Pixel offset between anchor and floating element. */\n offset?: number\n /** Flip to the opposite side when the requested side overflows. Default `true`. */\n flip?: boolean\n /** Shift along the cross-axis to stay in view. Default `true`. */\n shift?: boolean\n /**\n * Constrain the floating element to the available viewport space — sets\n * `maxWidth`/`maxHeight` (minus 8px padding) on it so a long dropdown/popover\n * never overflows the screen (pair with `overflow:auto` for scroll). Off by\n * default. Pass a number to override the viewport padding.\n */\n size?: boolean | number\n /** Extra Floating UI middleware to append. */\n middleware?: Middleware[]\n /**\n * Optional ref to an arrow element inside the floating panel. When provided,\n * `arrowX`, `arrowY`, and `arrowSide` in the return value are populated by the\n * `@floating-ui/dom` arrow middleware so you can position the caret CSS.\n */\n arrow?: React.RefObject<HTMLElement | null>\n}\n\nexport interface UseFloatingReturn {\n x: number\n y: number\n strategy: Strategy\n finalPlacement: Placement\n /** Ready-to-bind inline style object for the floating element. */\n floatingStyles: React.CSSProperties\n /** Trigger a manual recompute. Usually not needed — `autoUpdate` handles scroll/resize. */\n update: () => Promise<void>\n /** Arrow x offset (px from floating start edge). Present when `arrow` option is set. */\n arrowX: number | undefined\n /** Arrow y offset (px from floating start edge). Present when `arrow` option is set. */\n arrowY: number | undefined\n /**\n * The side of the floating element the arrow is on — use as the CSS property\n * to apply `bottom: 0` / `top: 0` etc. to the arrow element.\n * E.g. `'bottom'` placement → arrow is on `'top'` of the floating element.\n */\n arrowSide: 'top' | 'right' | 'bottom' | 'left' | undefined\n}\n\n/**\n * Reactive Floating UI integration for React. Subscribes `autoUpdate`\n * whenever `open === true` and both refs are populated; tears it down on\n * close, unmount, or ref clearing.\n *\n * Pure positioning — does NOT render anything, does NOT manage focus,\n * does NOT handle dismiss behavior. Those concerns live in the consuming\n * primitive (Popover, Tooltip, Dialog, Menu, …).\n */\nexport function useFloating(options: UseFloatingOptions): UseFloatingReturn {\n const {\n anchor,\n floating,\n open,\n placement = 'bottom',\n strategy = 'absolute',\n offset,\n flip = true,\n shift = true,\n size = false,\n middleware: extraMiddleware,\n arrow,\n } = options\n\n const [x, setX] = React.useState(0)\n const [y, setY] = React.useState(0)\n const [finalPlacement, setFinalPlacement] = React.useState<Placement>(placement)\n const [arrowX, setArrowX] = React.useState<number | undefined>()\n const [arrowY, setArrowY] = React.useState<number | undefined>()\n\n const middleware = React.useMemo<Middleware[]>(() => {\n const mw: Middleware[] = []\n if (offset !== undefined) mw.push(offsetMiddleware(offset))\n if (flip) mw.push(flipMiddleware())\n if (shift) mw.push(shiftMiddleware({ padding: 8 }))\n if (size) {\n const padding = typeof size === 'number' ? size : 8\n mw.push(\n sizeMiddleware({\n padding,\n apply({ availableWidth, availableHeight, elements }) {\n Object.assign(elements.floating.style, {\n maxWidth: `${Math.max(0, Math.floor(availableWidth))}px`,\n maxHeight: `${Math.max(0, Math.floor(availableHeight))}px`,\n })\n },\n }),\n )\n }\n if (arrow?.current) mw.push(arrowMiddleware({ element: arrow.current }))\n if (extraMiddleware) mw.push(...extraMiddleware)\n return mw\n }, [offset, flip, shift, size, extraMiddleware, arrow])\n\n // Hold the latest middleware in a ref so `update` always sees current value\n // without being re-created (which would churn the autoUpdate subscription).\n const middlewareRef = React.useRef(middleware)\n middlewareRef.current = middleware\n\n // Monotonic token bumped on every new positioning cycle and on teardown.\n // `computePosition` is async; without this guard a result that resolves\n // after the panel closed/unmounted (or after a newer update started) would\n // apply stale coordinates and call setState on an unmounted component.\n const epochRef = React.useRef(0)\n\n const update = React.useCallback(async () => {\n const a = anchor.current\n const f = floating.current\n if (!a || !f) return\n const token = ++epochRef.current\n const result = await computePosition(a, f, {\n placement,\n strategy,\n middleware: middlewareRef.current,\n })\n // A newer update() started, or the effect tore down, while awaiting — drop.\n if (token !== epochRef.current) return\n setX(result.x)\n setY(result.y)\n setFinalPlacement(result.placement)\n const arrowData = result.middlewareData.arrow as { x?: number; y?: number } | undefined\n setArrowX(arrowData?.x)\n setArrowY(arrowData?.y)\n }, [anchor, floating, placement, strategy])\n\n React.useEffect(() => {\n if (!open) return\n const a = anchor.current\n const f = floating.current\n if (!a || !f) return\n const cleanup = autoUpdate(a, f, () => {\n void update()\n })\n return () => {\n // Invalidate any in-flight update() so its late result never lands.\n epochRef.current++\n cleanup()\n }\n }, [open, anchor, floating, update])\n\n const floatingStyles = React.useMemo<React.CSSProperties>(\n () => ({\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n width: 'max-content',\n }),\n [strategy, x, y],\n )\n\n const OPPOSITE: Record<string, 'top' | 'right' | 'bottom' | 'left'> = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n }\n const arrowSide = OPPOSITE[finalPlacement.split('-')[0]] as\n | 'top'\n | 'right'\n | 'bottom'\n | 'left'\n | undefined\n\n return { x, y, strategy, finalPlacement, floatingStyles, update, arrowX, arrowY, arrowSide }\n}\n","import * as React from 'react'\n\nexport interface UseDismissOptions {\n /** While true, the dismiss listeners are active. */\n enabled: boolean\n /** Elements that, if clicked, should NOT trigger dismiss (typically trigger + content). */\n exclude: Array<React.RefObject<HTMLElement | null>>\n /** Called when the user attempts to dismiss the surface. */\n onDismiss: () => void\n /** Close on Escape key. Default `true`. */\n escape?: boolean\n /** Close on pointerdown outside the excluded elements. Default `true`. */\n outsidePointerDown?: boolean\n}\n\n/**\n * Listen for the universal \"dismiss\" gestures: Escape key and outside\n * pointer-down. Used by Popover / Menu / Dropdown. Tooltip does NOT use this —\n * it dismisses on mouseleave/blur instead.\n *\n * Listeners attach to `document` (pointerdown in the capture phase, mirroring\n * the Vue adapter) only while `enabled` is true, and detach on disable /\n * unmount. SSR-safe (no-op without `document`). The latest `onDismiss` /\n * `exclude` are read through refs so toggling them never re-subscribes.\n */\nexport function useDismiss(options: UseDismissOptions): void {\n const { enabled, escape = true, outsidePointerDown = true } = options\n\n const onDismissRef = React.useRef(options.onDismiss)\n const excludeRef = React.useRef(options.exclude)\n React.useEffect(() => {\n onDismissRef.current = options.onDismiss\n excludeRef.current = options.exclude\n })\n\n React.useEffect(() => {\n if (!enabled || typeof document === 'undefined') return\n\n const isExcluded = (target: EventTarget | null): boolean => {\n if (!target || !(target instanceof Node)) return false\n for (const r of excludeRef.current) {\n const el = r.current\n if (el && (el === target || el.contains(target))) return true\n }\n return false\n }\n\n const onPointerDown = (event: PointerEvent) => {\n if (isExcluded(event.target)) return\n onDismissRef.current()\n }\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopPropagation()\n onDismissRef.current()\n }\n }\n\n if (outsidePointerDown) document.addEventListener('pointerdown', onPointerDown, true)\n if (escape) document.addEventListener('keydown', onKeyDown)\n return () => {\n document.removeEventListener('pointerdown', onPointerDown, true)\n document.removeEventListener('keydown', onKeyDown)\n }\n }, [enabled, escape, outsidePointerDown])\n}\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import * as React2 from 'react';
|
|
3
|
+
|
|
4
|
+
// src/modal-utils/useBodyScrollLock.ts
|
|
5
|
+
var lockCount = 0;
|
|
6
|
+
var savedOverflow = null;
|
|
7
|
+
var savedPaddingRight = null;
|
|
8
|
+
var appliedOverflow = null;
|
|
9
|
+
var appliedPaddingRight = null;
|
|
10
|
+
function lock() {
|
|
11
|
+
if (typeof document === "undefined") return;
|
|
12
|
+
lockCount += 1;
|
|
13
|
+
if (lockCount > 1) return;
|
|
14
|
+
const body = document.body;
|
|
15
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
16
|
+
savedOverflow = body.style.overflow;
|
|
17
|
+
savedPaddingRight = body.style.paddingRight;
|
|
18
|
+
body.style.overflow = "hidden";
|
|
19
|
+
appliedOverflow = "hidden";
|
|
20
|
+
if (scrollbarWidth > 0) {
|
|
21
|
+
const currentPadding = parseFloat(getComputedStyle(body).paddingRight || "0");
|
|
22
|
+
appliedPaddingRight = `${currentPadding + scrollbarWidth}px`;
|
|
23
|
+
body.style.paddingRight = appliedPaddingRight;
|
|
24
|
+
} else {
|
|
25
|
+
appliedPaddingRight = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function unlock() {
|
|
29
|
+
if (typeof document === "undefined") return;
|
|
30
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
31
|
+
if (lockCount > 0) return;
|
|
32
|
+
const body = document.body;
|
|
33
|
+
if (body.style.overflow === appliedOverflow) {
|
|
34
|
+
body.style.overflow = savedOverflow ?? "";
|
|
35
|
+
}
|
|
36
|
+
if (appliedPaddingRight !== null && body.style.paddingRight === appliedPaddingRight) {
|
|
37
|
+
body.style.paddingRight = savedPaddingRight ?? "";
|
|
38
|
+
}
|
|
39
|
+
savedOverflow = null;
|
|
40
|
+
savedPaddingRight = null;
|
|
41
|
+
appliedOverflow = null;
|
|
42
|
+
appliedPaddingRight = null;
|
|
43
|
+
}
|
|
44
|
+
function useBodyScrollLock(active) {
|
|
45
|
+
React2.useEffect(() => {
|
|
46
|
+
if (!active) return;
|
|
47
|
+
lock();
|
|
48
|
+
return () => {
|
|
49
|
+
unlock();
|
|
50
|
+
};
|
|
51
|
+
}, [active]);
|
|
52
|
+
}
|
|
53
|
+
function __getBodyScrollLockCount() {
|
|
54
|
+
return lockCount;
|
|
55
|
+
}
|
|
56
|
+
function __resetBodyScrollLock() {
|
|
57
|
+
lockCount = 0;
|
|
58
|
+
savedOverflow = null;
|
|
59
|
+
savedPaddingRight = null;
|
|
60
|
+
appliedOverflow = null;
|
|
61
|
+
appliedPaddingRight = null;
|
|
62
|
+
if (typeof document !== "undefined") {
|
|
63
|
+
document.body.style.overflow = "";
|
|
64
|
+
document.body.style.paddingRight = "";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var FOCUSABLE_SELECTOR = [
|
|
68
|
+
"a[href]",
|
|
69
|
+
"area[href]",
|
|
70
|
+
"button:not([disabled])",
|
|
71
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
72
|
+
"select:not([disabled])",
|
|
73
|
+
"textarea:not([disabled])",
|
|
74
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
75
|
+
'[contenteditable="true"]',
|
|
76
|
+
"audio[controls]",
|
|
77
|
+
"video[controls]",
|
|
78
|
+
"iframe",
|
|
79
|
+
"object",
|
|
80
|
+
"embed"
|
|
81
|
+
].join(",");
|
|
82
|
+
function getFocusable(root) {
|
|
83
|
+
return Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
84
|
+
(el) => !el.hasAttribute("disabled") && el.tabIndex !== -1
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function useFocusTrap(options) {
|
|
88
|
+
const { container, active, returnFocusTo, initialFocus = true } = options;
|
|
89
|
+
const previouslyFocusedRef = React2.useRef(null);
|
|
90
|
+
React2.useEffect(() => {
|
|
91
|
+
if (!active) return;
|
|
92
|
+
if (typeof document === "undefined") return;
|
|
93
|
+
previouslyFocusedRef.current = document.activeElement ?? null;
|
|
94
|
+
const onKeyDown = (event) => {
|
|
95
|
+
if (event.key !== "Tab") return;
|
|
96
|
+
const root = container.current;
|
|
97
|
+
if (!root) return;
|
|
98
|
+
const focusables = getFocusable(root);
|
|
99
|
+
if (focusables.length === 0) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
root.focus();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const first = focusables[0];
|
|
105
|
+
const last = focusables[focusables.length - 1];
|
|
106
|
+
const active2 = document.activeElement;
|
|
107
|
+
if (event.shiftKey) {
|
|
108
|
+
if (active2 === first || !root.contains(active2)) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
last.focus();
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
if (active2 === last || !root.contains(active2)) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
first.focus();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
document.addEventListener("keydown", onKeyDown);
|
|
120
|
+
let raf;
|
|
121
|
+
if (initialFocus) {
|
|
122
|
+
raf = requestAnimationFrame(() => {
|
|
123
|
+
const root = container.current;
|
|
124
|
+
if (!root) return;
|
|
125
|
+
const focusables = getFocusable(root);
|
|
126
|
+
(focusables[0] ?? root).focus();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return () => {
|
|
130
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
131
|
+
if (raf !== void 0) cancelAnimationFrame(raf);
|
|
132
|
+
const target = returnFocusTo?.current ?? previouslyFocusedRef.current;
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
if (target && target.isConnected) target.focus?.();
|
|
135
|
+
});
|
|
136
|
+
previouslyFocusedRef.current = null;
|
|
137
|
+
};
|
|
138
|
+
}, [active, container, returnFocusTo, initialFocus]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { __getBodyScrollLockCount, __resetBodyScrollLock, useBodyScrollLock, useFocusTrap };
|
|
142
|
+
//# sourceMappingURL=chunk-AUA5GBT6.js.map
|
|
143
|
+
//# sourceMappingURL=chunk-AUA5GBT6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/modal-utils/useBodyScrollLock.ts","../src/modal-utils/useFocusTrap.ts"],"names":["React","active"],"mappings":"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","file":"chunk-AUA5GBT6.js","sourcesContent":["import * as React from 'react'\n\nlet lockCount = 0\nlet savedOverflow: string | null = null\nlet savedPaddingRight: string | null = null\n// What we actually wrote, so unlock can detect a host mutation made while\n// locked and avoid clobbering it.\nlet appliedOverflow: string | null = null\nlet appliedPaddingRight: string | null = null\n\nfunction lock(): void {\n if (typeof document === 'undefined') return\n lockCount += 1\n if (lockCount > 1) return\n const body = document.body\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth\n savedOverflow = body.style.overflow\n savedPaddingRight = body.style.paddingRight\n body.style.overflow = 'hidden'\n appliedOverflow = 'hidden'\n if (scrollbarWidth > 0) {\n const currentPadding = parseFloat(getComputedStyle(body).paddingRight || '0')\n appliedPaddingRight = `${currentPadding + scrollbarWidth}px`\n body.style.paddingRight = appliedPaddingRight\n } else {\n appliedPaddingRight = null\n }\n}\n\nfunction unlock(): void {\n if (typeof document === 'undefined') return\n lockCount = Math.max(0, lockCount - 1)\n if (lockCount > 0) return\n const body = document.body\n // Only restore if our locked value is still in place; if a host changed body\n // overflow / padding-right while locked, respect that rather than clobber it.\n if (body.style.overflow === appliedOverflow) {\n body.style.overflow = savedOverflow ?? ''\n }\n if (appliedPaddingRight !== null && body.style.paddingRight === appliedPaddingRight) {\n body.style.paddingRight = savedPaddingRight ?? ''\n }\n savedOverflow = null\n savedPaddingRight = null\n appliedOverflow = null\n appliedPaddingRight = null\n}\n\n/**\n * Lock the document body's scroll while `active` is true. Reference-counted\n * so multiple stacked overlays cooperate. Restores prior `overflow` and\n * `padding-right` (to compensate for the scrollbar gap) when the last lock\n * releases. SSR-safe.\n */\nexport function useBodyScrollLock(active: boolean): void {\n React.useEffect(() => {\n if (!active) return\n lock()\n return () => {\n unlock()\n }\n }, [active])\n}\n\n/** Test-only: read the current lock count. */\nexport function __getBodyScrollLockCount(): number {\n return lockCount\n}\n\n/** Test-only: hard-reset the module-level lock state. */\nexport function __resetBodyScrollLock(): void {\n lockCount = 0\n savedOverflow = null\n savedPaddingRight = null\n appliedOverflow = null\n appliedPaddingRight = null\n if (typeof document !== 'undefined') {\n document.body.style.overflow = ''\n document.body.style.paddingRight = ''\n }\n}\n","import * as React from 'react'\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n 'audio[controls]',\n 'video[controls]',\n 'iframe',\n 'object',\n 'embed',\n].join(',')\n\nfunction getFocusable(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1,\n )\n}\n\nexport interface UseFocusTrapOptions {\n /** Element whose Tab key cycle is being trapped. */\n container: React.RefObject<HTMLElement | null>\n /** Whether the trap is currently active. */\n active: boolean\n /** Element to restore focus to when the trap deactivates. */\n returnFocusTo?: React.RefObject<HTMLElement | null | undefined>\n /** Whether to focus the first focusable element on activation. Default `true`. */\n initialFocus?: boolean\n}\n\n/**\n * Constrain Tab / Shift+Tab focus traversal to descendants of `container`.\n *\n * Behavior on activation:\n * - Records the currently focused element.\n * - If `initialFocus !== false`, focuses the first focusable descendant\n * (or the container itself if there are none).\n *\n * Behavior while active:\n * - Tab on the last focusable element wraps to the first; Shift+Tab on\n * the first wraps to the last.\n *\n * Behavior on deactivation:\n * - Restores focus to `returnFocusTo?.current` if provided, else to the\n * element that was focused when the trap activated.\n */\nexport function useFocusTrap(options: UseFocusTrapOptions): void {\n const { container, active, returnFocusTo, initialFocus = true } = options\n const previouslyFocusedRef = React.useRef<HTMLElement | null>(null)\n\n React.useEffect(() => {\n if (!active) return\n if (typeof document === 'undefined') return\n\n previouslyFocusedRef.current = (document.activeElement as HTMLElement | null) ?? null\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return\n const root = container.current\n if (!root) return\n const focusables = getFocusable(root)\n if (focusables.length === 0) {\n event.preventDefault()\n root.focus()\n return\n }\n const first = focusables[0]!\n const last = focusables[focusables.length - 1]!\n const active = document.activeElement as HTMLElement | null\n\n if (event.shiftKey) {\n if (active === first || !root.contains(active)) {\n event.preventDefault()\n last.focus()\n }\n } else {\n if (active === last || !root.contains(active)) {\n event.preventDefault()\n first.focus()\n }\n }\n }\n document.addEventListener('keydown', onKeyDown)\n\n let raf: number | undefined\n if (initialFocus) {\n raf = requestAnimationFrame(() => {\n const root = container.current\n if (!root) return\n const focusables = getFocusable(root)\n ;(focusables[0] ?? root).focus()\n })\n }\n\n return () => {\n document.removeEventListener('keydown', onKeyDown)\n if (raf !== undefined) cancelAnimationFrame(raf)\n const target = returnFocusTo?.current ?? previouslyFocusedRef.current\n requestAnimationFrame(() => {\n // The element that had focus when the trap opened may have been\n // unmounted while it was open; focusing a detached node silently drops\n // focus to <body>. Only restore if it is still in the document.\n if (target && target.isConnected) target.focus?.()\n })\n previouslyFocusedRef.current = null\n }\n }, [active, container, returnFocusTo, initialFocus])\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useStore } from './chunk-RZNYIFV2.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
import { createFormStore, formatPath, getByPath } from '@iris-ui-kit/core';
|
|
6
|
+
|
|
7
|
+
var FormContext = React.createContext(null);
|
|
8
|
+
function useFormContext() {
|
|
9
|
+
const ctx = React.useContext(FormContext);
|
|
10
|
+
if (!ctx) {
|
|
11
|
+
throw new Error("useField / useFormContext must be used within an <IrisForm>");
|
|
12
|
+
}
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
function focusFirstError(formEl, errors) {
|
|
16
|
+
if (!formEl) return;
|
|
17
|
+
const keys = Object.keys(errors);
|
|
18
|
+
if (keys.length === 0) return;
|
|
19
|
+
const controls = formEl.querySelectorAll(
|
|
20
|
+
"input[name], select[name], textarea[name], [data-iris-field]"
|
|
21
|
+
);
|
|
22
|
+
for (const el of Array.from(controls)) {
|
|
23
|
+
const name = el.getAttribute("name") ?? el.getAttribute("data-iris-field");
|
|
24
|
+
if (name && keys.includes(name)) {
|
|
25
|
+
el.focus();
|
|
26
|
+
try {
|
|
27
|
+
el.scrollIntoView({ block: "center" });
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function IrisForm({ form, children, ...rest }) {
|
|
35
|
+
const formRef = React.useRef(null);
|
|
36
|
+
return /* @__PURE__ */ jsx(FormContext.Provider, { value: form, children: /* @__PURE__ */ jsx(
|
|
37
|
+
"form",
|
|
38
|
+
{
|
|
39
|
+
...rest,
|
|
40
|
+
ref: formRef,
|
|
41
|
+
"data-iris-form": "",
|
|
42
|
+
onSubmit: (event) => {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
void form.handleSubmit().then(() => focusFirstError(formRef.current, form.getState().errors));
|
|
45
|
+
},
|
|
46
|
+
children
|
|
47
|
+
}
|
|
48
|
+
) });
|
|
49
|
+
}
|
|
50
|
+
function useForm(config) {
|
|
51
|
+
const latest = React.useRef(config);
|
|
52
|
+
latest.current = config;
|
|
53
|
+
const ref = React.useRef(null);
|
|
54
|
+
if (ref.current === null) {
|
|
55
|
+
ref.current = createFormStore({
|
|
56
|
+
initialValues: config.initialValues,
|
|
57
|
+
validators: config.validators,
|
|
58
|
+
validateOnChange: config.validateOnChange,
|
|
59
|
+
validateOnBlur: config.validateOnBlur,
|
|
60
|
+
validate: (values) => latest.current.validate?.(values) ?? {},
|
|
61
|
+
onSubmit: (values) => latest.current.onSubmit?.(values)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const form = ref.current;
|
|
65
|
+
const state = useStore(form.store);
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
store: form.store,
|
|
69
|
+
form,
|
|
70
|
+
isValid: Object.keys(state.errors).length === 0,
|
|
71
|
+
setFieldValue: form.setFieldValue,
|
|
72
|
+
setValues: form.setValues,
|
|
73
|
+
setFieldTouched: form.setFieldTouched,
|
|
74
|
+
setFieldError: form.setFieldError,
|
|
75
|
+
setErrors: form.setErrors,
|
|
76
|
+
validateField: form.validateField,
|
|
77
|
+
validateForm: form.validateForm,
|
|
78
|
+
handleSubmit: form.handleSubmit,
|
|
79
|
+
reset: form.reset
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function useField(name) {
|
|
83
|
+
const form = useFormContext();
|
|
84
|
+
const state = useStore(form.store);
|
|
85
|
+
const key = formatPath(name);
|
|
86
|
+
const value = getByPath(state.values, name);
|
|
87
|
+
return {
|
|
88
|
+
name,
|
|
89
|
+
value,
|
|
90
|
+
error: state.errors[key],
|
|
91
|
+
touched: Boolean(state.touched[key]),
|
|
92
|
+
dirty: Boolean(state.dirty[key]),
|
|
93
|
+
setValue: (next) => form.setFieldValue(name, next),
|
|
94
|
+
setTouched: (touched = true) => form.setFieldTouched(name, touched),
|
|
95
|
+
inputProps: {
|
|
96
|
+
name,
|
|
97
|
+
value: value ?? "",
|
|
98
|
+
onChange: (event) => form.setFieldValue(name, event.target.value),
|
|
99
|
+
onBlur: () => form.setFieldTouched(name, true)
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/form/useFieldArray.ts
|
|
105
|
+
function useFieldArray(name) {
|
|
106
|
+
const form = useFormContext();
|
|
107
|
+
const state = useStore(form.store);
|
|
108
|
+
const fields = Array.isArray(state.values[name]) ? state.values[name] : [];
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
fields,
|
|
112
|
+
push: (item) => form.arrayPush(name, item),
|
|
113
|
+
remove: (index) => form.arrayRemove(name, index),
|
|
114
|
+
insert: (index, item) => form.arrayInsert(name, index, item),
|
|
115
|
+
move: (from, to) => form.arrayMove(name, from, to),
|
|
116
|
+
replace: (items) => form.setFieldValue(name, items)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { FormContext, IrisForm, useField, useFieldArray, useForm, useFormContext };
|
|
121
|
+
//# sourceMappingURL=chunk-DE6V6NOI.js.map
|
|
122
|
+
//# sourceMappingURL=chunk-DE6V6NOI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/form/context.ts","../src/form/Form.tsx","../src/form/useForm.ts","../src/form/useField.ts","../src/form/useFieldArray.ts"],"names":["React2","React3"],"mappings":"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","file":"chunk-DE6V6NOI.js","sourcesContent":["import * as React from 'react'\nimport type { FormStore, FormValues } from '@iris-ui-kit/core'\n\n/**\n * Carries the active form's {@link FormStore} down to descendant fields so\n * `useField` can bind without prop drilling. The generic is erased at the\n * context boundary; `useField<T>` re-applies the value type at the call site.\n */\nexport const FormContext = React.createContext<FormStore<FormValues> | null>(null)\n\nexport function useFormContext(): FormStore<FormValues> {\n const ctx = React.useContext(FormContext)\n if (!ctx) {\n throw new Error('useField / useFormContext must be used within an <IrisForm>')\n }\n return ctx\n}\n","import * as React from 'react'\nimport type { FormStore, FormValues } from '@iris-ui-kit/core'\nimport { FormContext } from './context'\n\n/** Focus (and best-effort scroll to) the first errored named control in DOM order. */\nfunction focusFirstError(\n formEl: HTMLFormElement | null,\n errors: Record<string, string | undefined>,\n): void {\n if (!formEl) return\n const keys = Object.keys(errors)\n if (keys.length === 0) return\n const controls = formEl.querySelectorAll<HTMLElement>(\n 'input[name], select[name], textarea[name], [data-iris-field]',\n )\n for (const el of Array.from(controls)) {\n const name = el.getAttribute('name') ?? el.getAttribute('data-iris-field')\n if (name && keys.includes(name)) {\n el.focus()\n try {\n el.scrollIntoView({ block: 'center' })\n } catch {\n /* scrollIntoView is unavailable in jsdom — focus is the contract */\n }\n return\n }\n }\n}\n\nexport interface IrisFormProps<V extends FormValues> extends Omit<\n React.FormHTMLAttributes<HTMLFormElement>,\n 'onSubmit'\n> {\n /** The store from `useForm(...).form`. */\n form: FormStore<V>\n children?: React.ReactNode\n}\n\n/**\n * Provides the form store to descendant `useField` calls and wires the native\n * `<form>` submit to `handleSubmit` (with `preventDefault`).\n *\n * @example\n * const f = useForm({ initialValues: { email: '' }, onSubmit })\n * <IrisForm form={f.form}>\n * <IrisFormField label=\"Email\"><EmailField /></IrisFormField>\n * <IrisButton type=\"submit\" disabled={f.isSubmitting}>Save</IrisButton>\n * </IrisForm>\n */\nexport function IrisForm<V extends FormValues>({ form, children, ...rest }: IrisFormProps<V>) {\n const formRef = React.useRef<HTMLFormElement | null>(null)\n return (\n <FormContext.Provider value={form as unknown as FormStore<FormValues>}>\n <form\n {...rest}\n ref={formRef}\n data-iris-form=\"\"\n onSubmit={(event) => {\n event.preventDefault()\n // On a failed submit, move focus to the first errored field (a11y).\n void form\n .handleSubmit()\n .then(() => focusFirstError(formRef.current, form.getState().errors))\n }}\n >\n {children}\n </form>\n </FormContext.Provider>\n )\n}\n","import * as React from 'react'\nimport {\n createFormStore,\n type FormConfig,\n type FormState,\n type FormStore,\n type FormValues,\n} from '@iris-ui-kit/core'\nimport { useStore } from '../useStore'\n\nexport interface UseFormReturn<V extends FormValues> extends FormState<V> {\n /** Underlying subscribable store. */\n store: FormStore<V>['store']\n /** The form store itself — pass to `<IrisForm form={...}>`. */\n form: FormStore<V>\n isValid: boolean\n setFieldValue: FormStore<V>['setFieldValue']\n setValues: FormStore<V>['setValues']\n setFieldTouched: FormStore<V>['setFieldTouched']\n setFieldError: FormStore<V>['setFieldError']\n setErrors: FormStore<V>['setErrors']\n validateField: FormStore<V>['validateField']\n validateForm: FormStore<V>['validateForm']\n handleSubmit: FormStore<V>['handleSubmit']\n reset: FormStore<V>['reset']\n}\n\n/**\n * React binding for the framework-agnostic form engine. Instantiates the store\n * once (like `useState`'s lazy initializer) and bridges it into React via\n * `useSyncExternalStore`. `initialValues` / `validators` / timing are captured\n * on first render; `onSubmit` and `validate` are read through a ref each render\n * so they always see the latest closures (no stale-callback footgun).\n */\nexport function useForm<V extends FormValues>(config: FormConfig<V>): UseFormReturn<V> {\n const latest = React.useRef(config)\n latest.current = config\n\n const ref = React.useRef<FormStore<V> | null>(null)\n if (ref.current === null) {\n ref.current = createFormStore<V>({\n initialValues: config.initialValues,\n validators: config.validators,\n validateOnChange: config.validateOnChange,\n validateOnBlur: config.validateOnBlur,\n validate: (values) => latest.current.validate?.(values) ?? {},\n onSubmit: (values) => latest.current.onSubmit?.(values),\n })\n }\n const form = ref.current\n const state = useStore(form.store)\n\n return {\n ...state,\n store: form.store,\n form,\n isValid: Object.keys(state.errors).length === 0,\n setFieldValue: form.setFieldValue,\n setValues: form.setValues,\n setFieldTouched: form.setFieldTouched,\n setFieldError: form.setFieldError,\n setErrors: form.setErrors,\n validateField: form.validateField,\n validateForm: form.validateForm,\n handleSubmit: form.handleSubmit,\n reset: form.reset,\n }\n}\n","import * as React from 'react'\nimport { formatPath, getByPath } from '@iris-ui-kit/core'\nimport { useStore } from '../useStore'\nimport { useFormContext } from './context'\n\nexport interface FieldInputProps {\n name: string\n value: string\n onChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void\n onBlur: () => void\n}\n\nexport interface UseFieldReturn<T> {\n name: string\n value: T\n error: string | undefined\n touched: boolean\n dirty: boolean\n setValue: (value: T) => void\n setTouched: (touched?: boolean) => void\n /**\n * Convenience bundle for a native text input / textarea. Spread onto\n * `IrisInput` / `IrisTextarea`. Non-text controls (checkbox, select,\n * number, custom) should wire `value` + `setValue` directly.\n */\n inputProps: FieldInputProps\n}\n\n/**\n * Binds a single field to the surrounding `<IrisForm>`. Re-renders only its\n * host component when the form store changes. `T` is the value type at the\n * call site (the context erases the form's generic).\n *\n * `name` may be a flat top-level key (`'email'`) OR a nested PATH\n * (`'address.city'`, `'items[2].sku'`) — the form keys its per-field state by\n * the canonical path, so a flat key is just a 1-segment path and stays 100%\n * back-compatible (v3 R19).\n */\nexport function useField<T = unknown>(name: string): UseFieldReturn<T> {\n const form = useFormContext()\n const state = useStore(form.store)\n // Canonical key for per-field state lookups (a flat key maps to itself).\n const key = formatPath(name)\n const value = getByPath(state.values, name) as T\n\n return {\n name,\n value,\n error: state.errors[key],\n touched: Boolean(state.touched[key]),\n dirty: Boolean(state.dirty[key]),\n setValue: (next) => form.setFieldValue(name, next as never),\n setTouched: (touched = true) => form.setFieldTouched(name, touched),\n inputProps: {\n name,\n value: (value ?? '') as string,\n onChange: (event) => form.setFieldValue(name, event.target.value as never),\n onBlur: () => form.setFieldTouched(name, true),\n },\n }\n}\n","import { useStore } from '../useStore'\nimport { useFormContext } from './context'\n\nexport interface UseFieldArrayReturn<T> {\n name: string\n /** Current array value (reactive). */\n fields: T[]\n /** Append an item. */\n push: (item: T) => void\n /** Remove the item at `index`. */\n remove: (index: number) => void\n /** Insert `item` at `index` (shifting the rest right). */\n insert: (index: number, item: T) => void\n /** Move the item from `from` to `to`. */\n move: (from: number, to: number) => void\n /** Replace the whole array. */\n replace: (items: T[]) => void\n}\n\n/**\n * Manage a dynamic array field (repeatable rows) inside an `<IrisForm>`. Mutators\n * delegate to the core {@link FormStore} array helpers (`arrayPush`/`arrayInsert`/\n * `arrayRemove`/`arrayMove`), which RE-KEY each element's error/touched/dirty/\n * validating state across the mutation — so a row's per-element validation state\n * FOLLOWS the row when rows are removed or reordered (removing `items[0]` shifts\n * `items[2]`'s error down to `items[1]`). `replace` swaps the whole array.\n *\n * ```tsx\n * const items = useFieldArray<{ sku: string }>('items')\n * items.fields.map((_, i) => <SkuRow key={i} index={i} />)\n * <button onClick={() => items.push({ sku: '' })}>Add</button>\n * ```\n */\nexport function useFieldArray<T = unknown>(name: string): UseFieldArrayReturn<T> {\n const form = useFormContext()\n const state = useStore(form.store)\n const fields = (Array.isArray(state.values[name]) ? state.values[name] : []) as T[]\n\n return {\n name,\n fields,\n push: (item) => form.arrayPush(name as never, item as never),\n remove: (index) => form.arrayRemove(name as never, index),\n insert: (index, item) => form.arrayInsert(name as never, index, item as never),\n move: (from, to) => form.arrayMove(name as never, from, to),\n replace: (items) => form.setFieldValue(name, items as never),\n }\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { resolveDataState } from '@iris-ui-kit/core';
|
|
4
|
+
|
|
5
|
+
// src/motion/usePrefersReducedMotion.ts
|
|
6
|
+
var QUERY = "(prefers-reduced-motion: reduce)";
|
|
7
|
+
function read() {
|
|
8
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
9
|
+
return window.matchMedia(QUERY).matches;
|
|
10
|
+
}
|
|
11
|
+
function usePrefersReducedMotion() {
|
|
12
|
+
const [reduced, setReduced] = React.useState(read);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
15
|
+
const mq = window.matchMedia(QUERY);
|
|
16
|
+
const onChange = () => setReduced(mq.matches);
|
|
17
|
+
onChange();
|
|
18
|
+
mq.addEventListener?.("change", onChange);
|
|
19
|
+
return () => mq.removeEventListener?.("change", onChange);
|
|
20
|
+
}, []);
|
|
21
|
+
return reduced;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/motion/styles.ts
|
|
25
|
+
var __DATA_STATE_STYLE_ID = "iris-data-state-styles";
|
|
26
|
+
var DATA_STATE_CLASS = "iris-data-state-enter";
|
|
27
|
+
var CSS = `
|
|
28
|
+
@keyframes iris-data-state-enter {
|
|
29
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
30
|
+
to { opacity: 1; transform: none; }
|
|
31
|
+
}
|
|
32
|
+
.${DATA_STATE_CLASS} {
|
|
33
|
+
animation: iris-data-state-enter var(--iris-duration-md, 200ms) ease;
|
|
34
|
+
}
|
|
35
|
+
@media (prefers-reduced-motion: reduce) {
|
|
36
|
+
.${DATA_STATE_CLASS} { animation: none; }
|
|
37
|
+
}
|
|
38
|
+
`.trim();
|
|
39
|
+
var installed = false;
|
|
40
|
+
function installDataStateStyles() {
|
|
41
|
+
if (installed) return;
|
|
42
|
+
if (typeof document === "undefined") return;
|
|
43
|
+
if (document.getElementById(__DATA_STATE_STYLE_ID)) {
|
|
44
|
+
installed = true;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const el = document.createElement("style");
|
|
48
|
+
el.id = __DATA_STATE_STYLE_ID;
|
|
49
|
+
el.textContent = CSS;
|
|
50
|
+
document.head.appendChild(el);
|
|
51
|
+
installed = true;
|
|
52
|
+
}
|
|
53
|
+
function __resetDataStateStyles() {
|
|
54
|
+
installed = false;
|
|
55
|
+
if (typeof document !== "undefined") {
|
|
56
|
+
document.getElementById(__DATA_STATE_STYLE_ID)?.remove();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/motion/useDataState.ts
|
|
61
|
+
function useDataState(input) {
|
|
62
|
+
React.useEffect(installDataStateStyles, []);
|
|
63
|
+
const reduced = usePrefersReducedMotion();
|
|
64
|
+
const state = resolveDataState(input);
|
|
65
|
+
return {
|
|
66
|
+
state,
|
|
67
|
+
isContent: state === "content",
|
|
68
|
+
stateKey: state,
|
|
69
|
+
stateProps: {
|
|
70
|
+
className: reduced ? "" : DATA_STATE_CLASS,
|
|
71
|
+
"data-iris-state": state
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { DATA_STATE_CLASS, __resetDataStateStyles, installDataStateStyles, useDataState, usePrefersReducedMotion };
|
|
77
|
+
//# sourceMappingURL=chunk-HEJGN6UI.js.map
|
|
78
|
+
//# sourceMappingURL=chunk-HEJGN6UI.js.map
|