@cjaye/react-meta-state 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/README.md ADDED
File without changes
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const csf = require('storybook/internal/csf');
4
+ const addon_preview = require('./preview.cjs');
5
+ require('react/jsx-runtime');
6
+ require('react');
7
+ require('../shared/react-meta-state.C5ASg9b-.cjs');
8
+ require('../shared/react-meta-state.BXhLgGzA.cjs');
9
+
10
+ const index = () => csf.definePreviewAddon(addon_preview);
11
+
12
+ module.exports = index;
@@ -0,0 +1,5 @@
1
+ import * as storybook_internal_csf from 'storybook/internal/csf';
2
+
3
+ declare const _default: () => storybook_internal_csf.PreviewAddon<storybook_internal_csf.AddonTypes>;
4
+
5
+ export { _default as default };
@@ -0,0 +1,10 @@
1
+ import { definePreviewAddon } from 'storybook/internal/csf';
2
+ import preview from './preview.mjs';
3
+ import 'react/jsx-runtime';
4
+ import 'react';
5
+ import '../shared/react-meta-state.C3o6Eh_L.mjs';
6
+ import '../shared/react-meta-state.BjE3OmFW.mjs';
7
+
8
+ const index = () => definePreviewAddon(preview);
9
+
10
+ export { index as default };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const state = require('../shared/react-meta-state.C5ASg9b-.cjs');
5
+ const managerApi = require('storybook/manager-api');
6
+ const helpers = require('../shared/react-meta-state.C6XVhRdK.cjs');
7
+ const react = require('react');
8
+ const icons = require('@storybook/icons');
9
+ const components = require('storybook/internal/components');
10
+
11
+ const MetaStateTool = react.memo(({ api }) => {
12
+ const [globals, updateGlobals] = managerApi.useGlobals();
13
+ const data = react.useMemo(() => globals[state.TOOL_ID] ?? {}, [globals]);
14
+ const options = react.useMemo(() => helpers.keysOf(state.DEFAULT_COMPONENT_STATE).map((k) => ({ title: k, value: k })), []);
15
+ const updateState = react.useCallback((state$1 = {}) => {
16
+ updateGlobals({ [state.TOOL_ID]: state$1 });
17
+ }, [updateGlobals]);
18
+ react.useEffect(() => {
19
+ void api.setAddonShortcut(state.ADDON_ID, {
20
+ label: "Meta States",
21
+ defaultShortcut: ["alt", "M"],
22
+ actionName: "outline",
23
+ showInMenu: true,
24
+ action: () => updateState({})
25
+ });
26
+ }, [api, updateState]);
27
+ return /* @__PURE__ */ jsxRuntime.jsx(
28
+ components.Select,
29
+ {
30
+ resetLabel: "Reset meta states",
31
+ onReset: updateState,
32
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.ButtonIcon, {}),
33
+ ariaLabel: "Meta States",
34
+ tooltip: "Apply meta states",
35
+ options,
36
+ value: helpers.keysOf(data).filter((x) => !!data[x]),
37
+ multiSelect: true,
38
+ onChange: (selected) => {
39
+ updateState(helpers.entriesOf(state.DEFAULT_COMPONENT_STATE).reduce((v, [k]) => ({ ...v, [k]: selected.includes(k) }), {}));
40
+ }
41
+ },
42
+ state.TOOL_ID
43
+ );
44
+ });
45
+
46
+ function renderLabel(item) {
47
+ if (item.type !== "story" && item.type !== "docs") {
48
+ return;
49
+ }
50
+ if (item.title.startsWith(state.ADDON_ID)) {
51
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
52
+ "\u{1F31F}",
53
+ item.name
54
+ ] });
55
+ }
56
+ }
57
+
58
+ managerApi.addons.register(state.ADDON_ID, (api) => {
59
+ managerApi.addons.add(state.TOOL_ID, {
60
+ type: managerApi.types.TOOL,
61
+ title: "React Meta State",
62
+ match: ({ viewMode }) => viewMode === "story",
63
+ render: () => /* @__PURE__ */ jsxRuntime.jsx(MetaStateTool, { api })
64
+ });
65
+ managerApi.addons.setConfig({
66
+ sidebar: { renderLabel }
67
+ });
68
+ });
@@ -0,0 +1,2 @@
1
+
2
+ export { };
@@ -0,0 +1,66 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { T as TOOL_ID, A as ADDON_ID, D as DEFAULT_COMPONENT_STATE } from '../shared/react-meta-state.C3o6Eh_L.mjs';
3
+ import { useGlobals, addons, types } from 'storybook/manager-api';
4
+ import { k as keysOf, e as entriesOf } from '../shared/react-meta-state.CzzhN3TK.mjs';
5
+ import { memo, useMemo, useCallback, useEffect } from 'react';
6
+ import { ButtonIcon } from '@storybook/icons';
7
+ import { Select } from 'storybook/internal/components';
8
+
9
+ const MetaStateTool = memo(({ api }) => {
10
+ const [globals, updateGlobals] = useGlobals();
11
+ const data = useMemo(() => globals[TOOL_ID] ?? {}, [globals]);
12
+ const options = useMemo(() => keysOf(DEFAULT_COMPONENT_STATE).map((k) => ({ title: k, value: k })), []);
13
+ const updateState = useCallback((state = {}) => {
14
+ updateGlobals({ [TOOL_ID]: state });
15
+ }, [updateGlobals]);
16
+ useEffect(() => {
17
+ void api.setAddonShortcut(ADDON_ID, {
18
+ label: "Meta States",
19
+ defaultShortcut: ["alt", "M"],
20
+ actionName: "outline",
21
+ showInMenu: true,
22
+ action: () => updateState({})
23
+ });
24
+ }, [api, updateState]);
25
+ return /* @__PURE__ */ jsx(
26
+ Select,
27
+ {
28
+ resetLabel: "Reset meta states",
29
+ onReset: updateState,
30
+ icon: /* @__PURE__ */ jsx(ButtonIcon, {}),
31
+ ariaLabel: "Meta States",
32
+ tooltip: "Apply meta states",
33
+ options,
34
+ value: keysOf(data).filter((x) => !!data[x]),
35
+ multiSelect: true,
36
+ onChange: (selected) => {
37
+ updateState(entriesOf(DEFAULT_COMPONENT_STATE).reduce((v, [k]) => ({ ...v, [k]: selected.includes(k) }), {}));
38
+ }
39
+ },
40
+ TOOL_ID
41
+ );
42
+ });
43
+
44
+ function renderLabel(item) {
45
+ if (item.type !== "story" && item.type !== "docs") {
46
+ return;
47
+ }
48
+ if (item.title.startsWith(ADDON_ID)) {
49
+ return /* @__PURE__ */ jsxs("span", { children: [
50
+ "\u{1F31F}",
51
+ item.name
52
+ ] });
53
+ }
54
+ }
55
+
56
+ addons.register(ADDON_ID, (api) => {
57
+ addons.add(TOOL_ID, {
58
+ type: types.TOOL,
59
+ title: "React Meta State",
60
+ match: ({ viewMode }) => viewMode === "story",
61
+ render: () => /* @__PURE__ */ jsx(MetaStateTool, { api })
62
+ });
63
+ addons.setConfig({
64
+ sidebar: { renderLabel }
65
+ });
66
+ });
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const react = require('react');
5
+ const state = require('../shared/react-meta-state.C5ASg9b-.cjs');
6
+ const parse = require('../shared/react-meta-state.BXhLgGzA.cjs');
7
+
8
+ const preview = {
9
+ decorators: [
10
+ (Story, { globals, canvasElement }) => {
11
+ const canvas = canvasElement;
12
+ const data = react.useMemo(() => globals[state.TOOL_ID] ?? {}, [globals]);
13
+ react.useEffect(() => {
14
+ canvas.querySelectorAll("[data-rms]").forEach((el) => {
15
+ el.setAttribute("data-rms", parse.toJson(data));
16
+ });
17
+ }, [data, canvas]);
18
+ return /* @__PURE__ */ jsxRuntime.jsx(Story, {});
19
+ }
20
+ ],
21
+ initialGlobals: {
22
+ [state.TOOL_ID]: {}
23
+ }
24
+ };
25
+
26
+ module.exports = preview;
@@ -0,0 +1,5 @@
1
+ import { ProjectAnnotations, Renderer } from 'storybook/internal/types';
2
+
3
+ declare const preview: ProjectAnnotations<Renderer>;
4
+
5
+ export { preview as default };
@@ -0,0 +1,24 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useMemo, useEffect } from 'react';
3
+ import { T as TOOL_ID } from '../shared/react-meta-state.C3o6Eh_L.mjs';
4
+ import { t as toJson } from '../shared/react-meta-state.BjE3OmFW.mjs';
5
+
6
+ const preview = {
7
+ decorators: [
8
+ (Story, { globals, canvasElement }) => {
9
+ const canvas = canvasElement;
10
+ const data = useMemo(() => globals[TOOL_ID] ?? {}, [globals]);
11
+ useEffect(() => {
12
+ canvas.querySelectorAll("[data-rms]").forEach((el) => {
13
+ el.setAttribute("data-rms", toJson(data));
14
+ });
15
+ }, [data, canvas]);
16
+ return /* @__PURE__ */ jsx(Story, {});
17
+ }
18
+ ],
19
+ initialGlobals: {
20
+ [TOOL_ID]: {}
21
+ }
22
+ };
23
+
24
+ export { preview as default };
package/dist/index.cjs ADDED
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ const helpers = require('./shared/react-meta-state.C6XVhRdK.cjs');
4
+ const react = require('react');
5
+ const state = require('./shared/react-meta-state.C5ASg9b-.cjs');
6
+ const parse = require('./shared/react-meta-state.BXhLgGzA.cjs');
7
+
8
+ function mergeArr(target, source) {
9
+ const merged = [...target];
10
+ for (const item of source) {
11
+ if (!merged.includes(item)) {
12
+ merged.push(item);
13
+ }
14
+ }
15
+ return merged;
16
+ }
17
+ function deepMerge(target, source) {
18
+ if (!helpers.isObj(target)) {
19
+ return source;
20
+ }
21
+ if (!helpers.isObj(source)) {
22
+ return target;
23
+ }
24
+ const obj = { ...target };
25
+ for (const key in source) {
26
+ const t = target[key];
27
+ const s = source[key];
28
+ if (!(key in target) || target[key] === void 0) {
29
+ obj[key] = s;
30
+ continue;
31
+ }
32
+ if (helpers.isArr(s)) {
33
+ obj[key] = helpers.isArr(t) ? mergeArr(t, s) : s;
34
+ continue;
35
+ }
36
+ if (helpers.isObj(s)) {
37
+ obj[key] = helpers.isObj(t) ? deepMerge(t, s) : s;
38
+ continue;
39
+ }
40
+ obj[key] = s ?? t;
41
+ }
42
+ return obj;
43
+ }
44
+ function deepMergeAll(...objects) {
45
+ return objects.reduce((v, x) => deepMerge(v, x), {});
46
+ }
47
+
48
+ async function wait(ms) {
49
+ return new Promise((r) => setTimeout(r, ms));
50
+ }
51
+
52
+ function useComponentState(options) {
53
+ const {
54
+ ref: parentRef,
55
+ stateDefinition = state.DEFAULT_COMPONENT_STATE,
56
+ onStateChange,
57
+ stateOverride
58
+ } = options ?? {};
59
+ const refs = react.useRef({});
60
+ const states = react.useRef({});
61
+ const listeners = react.useRef({});
62
+ const observers = react.useRef({});
63
+ const refreshTimes = react.useRef({});
64
+ const queuedState = react.useRef({});
65
+ const overrides = react.useRef({});
66
+ const [finalState, setFinalState] = react.useState({});
67
+ const getAttributeState = react.useCallback((key) => {
68
+ const attributeState = refs.current[key]?.getAttribute("data-rms");
69
+ return attributeState === "" ? {} : parse.fromJson(attributeState ?? "{}");
70
+ }, []);
71
+ const normaliseState = react.useCallback((state) => {
72
+ if (state.focus) {
73
+ state.focusWithin = true;
74
+ }
75
+ if (state.disabled) {
76
+ state.focus = state.focusWithin = state.active = state.selected = false;
77
+ }
78
+ return state;
79
+ }, []);
80
+ const refresh = react.useCallback(async (key = "default") => {
81
+ normaliseState(queuedState.current[key]);
82
+ const attributeState = getAttributeState(key);
83
+ const allStates = normaliseState(deepMergeAll(
84
+ states.current[key],
85
+ queuedState.current[key] ?? {},
86
+ overrides.current?.[key] ?? {},
87
+ attributeState
88
+ ));
89
+ if (helpers.keysOf(allStates).every((k) => allStates[k] === states.current[key]?.[k])) {
90
+ queuedState.current[key] = {};
91
+ return;
92
+ }
93
+ const rt = refreshTimes.current[key] ?? 0;
94
+ if (!!rt) return;
95
+ const rt2 = refreshTimes.current[key] = Date.now();
96
+ const delay = Math.max(rt2 + 8 - Date.now(), 0);
97
+ if (!!delay) {
98
+ void await wait(delay);
99
+ }
100
+ refreshTimes.current[key] = 0;
101
+ const el = refs.current[key];
102
+ const innerState = deepMerge(
103
+ states.current[key],
104
+ queuedState.current[key] ?? {}
105
+ );
106
+ const newState = normaliseState(deepMergeAll(
107
+ innerState,
108
+ overrides.current?.[key] ?? {},
109
+ attributeState
110
+ ));
111
+ const prevState = states.current[key];
112
+ queuedState.current[key] = {};
113
+ if (helpers.keysOf(newState).every((k) => newState[k] === states.current[key]?.[k])) return;
114
+ states.current[key] = innerState;
115
+ setFinalState((s) => ({ ...s, [key]: newState }));
116
+ helpers.keysOf(stateDefinition).forEach((k) => el.classList[newState[k] ? "add" : "remove"](k));
117
+ onStateChange?.(newState, key);
118
+ if (helpers.isIn(el, "disabled") && el.disabled !== newState.disabled) {
119
+ el.disabled = !!newState.disabled;
120
+ }
121
+ if (newState.focus && el !== document.activeElement && !prevState.focus) {
122
+ el.focus();
123
+ }
124
+ if (!newState.focus && el === document.activeElement && prevState.focus) {
125
+ el.blur();
126
+ }
127
+ }, [getAttributeState, normaliseState, onStateChange, stateDefinition]);
128
+ const updateState = react.useCallback((patch, key = "default") => {
129
+ queuedState.current[key] = deepMerge(queuedState.current[key] ?? {}, patch);
130
+ void refresh(key);
131
+ }, [refresh]);
132
+ const getRef = react.useCallback((node, key = "default") => {
133
+ const ref = refs.current[key];
134
+ const observer = observers.current[key];
135
+ if (helpers.isFunc(parentRef)) {
136
+ parentRef?.(node);
137
+ } else if (helpers.isIn(parentRef, "current")) {
138
+ parentRef.current = node;
139
+ }
140
+ if (ref && node && ref !== node) {
141
+ const l2 = listeners.current[key];
142
+ observer.disconnect();
143
+ document.removeEventListener("pointerup", l2.pointerup);
144
+ node.removeEventListener("pointerdown", l2.pointerdown);
145
+ node.removeEventListener("pointerleave", l2.pointerleave);
146
+ node.removeEventListener("pointerenter", l2.pointerenter);
147
+ node.removeEventListener("focus", l2.focus);
148
+ node.removeEventListener("focusin", l2.focusin);
149
+ node.removeEventListener("focusout", l2.focusout);
150
+ delete listeners.current[key];
151
+ delete refs.current[key];
152
+ delete states.current[key];
153
+ delete observers.current[key];
154
+ }
155
+ if (!node || ref === node) return;
156
+ refs.current[key] = node;
157
+ listeners.current[key] = {
158
+ pointerup: () => {
159
+ if (!helpers.isIn(stateDefinition, "pressed")) return;
160
+ updateState({ pressed: false }, key);
161
+ },
162
+ pointerdown: () => {
163
+ if (!helpers.isIn(stateDefinition, "pressed")) return;
164
+ updateState({ pressed: true }, key);
165
+ },
166
+ pointerleave: () => {
167
+ if (!helpers.isIn(stateDefinition, "hover")) return;
168
+ updateState({ hover: false }, key);
169
+ },
170
+ pointerenter: () => {
171
+ if (!helpers.isIn(stateDefinition, "hover")) return;
172
+ updateState({ hover: true }, key);
173
+ },
174
+ focus: () => {
175
+ if (!helpers.isIn(stateDefinition, "focus")) return;
176
+ updateState({ focus: true }, key);
177
+ },
178
+ focusin: () => {
179
+ if (!helpers.isIn(stateDefinition, "focusWithin")) return;
180
+ updateState({ focusWithin: true }, key);
181
+ },
182
+ focusout: () => {
183
+ const active = document.activeElement;
184
+ let shouldFocusWithinOut = false;
185
+ if (helpers.isIn(stateDefinition, "focusWithin")) {
186
+ if (!refs.current[key]?.contains(active) && refs.current[key] !== active && refs.current[key] !== active) {
187
+ shouldFocusWithinOut = true;
188
+ }
189
+ }
190
+ if (helpers.isIn(stateDefinition, "focus")) {
191
+ updateState({ focus: false, focusWithin: shouldFocusWithinOut ? false : void 0 }, key);
192
+ }
193
+ }
194
+ };
195
+ const l = listeners.current[key];
196
+ const obs = new MutationObserver((mutations) => {
197
+ for (const mutation of mutations) {
198
+ if (mutation.type === "attributes" && mutation.attributeName === "data-rms") {
199
+ return updateState({}, key);
200
+ }
201
+ if (mutation.type === "attributes" && mutation.attributeName === "class") {
202
+ const target = mutation.target;
203
+ const oldClasses = mutation.oldValue?.split(" ") ?? [];
204
+ const newClasses = target.classList.value.split(" ");
205
+ const added = newClasses.filter((x) => !oldClasses.includes(x) && helpers.isIn(stateDefinition, x) && !states.current[key]?.[x]);
206
+ const removed = oldClasses.filter((x) => !newClasses.includes(x) && helpers.isIn(stateDefinition, x) && !!states.current[key]?.[x]);
207
+ updateState({
208
+ ...added.reduce((a, v) => ({ ...a, [v]: true }), {}),
209
+ ...removed.reduce((a, v) => ({ ...a, [v]: false }), {})
210
+ }, key);
211
+ return;
212
+ }
213
+ const el = mutation.target;
214
+ if (!helpers.isIn(el, "disabled") || mutation.type !== "attributes" || mutation.attributeName !== "disabled" || el.disabled === states.current[key]?.disabled || !helpers.isIn(stateDefinition, "disabled")) {
215
+ return;
216
+ }
217
+ const attributeState = getAttributeState(key);
218
+ if (el.disabled) {
219
+ if (attributeState.disabled === false || overrides.current?.[key]?.disabled === false) {
220
+ el.disabled = false;
221
+ return;
222
+ }
223
+ updateState({ disabled: true }, key);
224
+ } else {
225
+ if (attributeState.disabled === true || overrides.current?.[key]?.disabled === true) {
226
+ el.disabled = true;
227
+ return;
228
+ }
229
+ updateState({ disabled: false }, key);
230
+ }
231
+ }
232
+ });
233
+ document.addEventListener("pointerup", l.pointerup);
234
+ node.addEventListener("pointerdown", l.pointerdown);
235
+ node.addEventListener("pointerleave", l.pointerleave);
236
+ node.addEventListener("pointerenter", l.pointerenter);
237
+ node.addEventListener("focus", l.focus);
238
+ node.addEventListener("focusin", l.focusin);
239
+ node.addEventListener("focusout", l.focusout);
240
+ node.setAttribute("data-rms", "");
241
+ obs.observe(node, {
242
+ attributes: true,
243
+ attributeOldValue: true,
244
+ attributeFilter: ["disabled", "class", "data-rms"]
245
+ });
246
+ const s = {};
247
+ if (helpers.isIn(stateDefinition, "focus")) {
248
+ s.focus = node === document.activeElement;
249
+ }
250
+ if (helpers.isIn(stateDefinition, "focusWithin")) {
251
+ s.focusWithin = node === document.activeElement || node.contains(document.activeElement);
252
+ }
253
+ if (helpers.isIn(stateDefinition, "disabled")) {
254
+ s.disabled = helpers.isIn(node, "disabled") && !!node.disabled;
255
+ }
256
+ observers.current[key] = obs;
257
+ states.current[key] ??= {};
258
+ queuedState.current[key] = deepMergeAll(
259
+ stateDefinition ?? {},
260
+ queuedState.current[key] ?? {},
261
+ s ?? {}
262
+ );
263
+ void refresh(key);
264
+ }, [parentRef, stateDefinition, refresh, updateState, getAttributeState]);
265
+ react.useEffect(() => {
266
+ const o = overrides.current;
267
+ const changedKeys = [];
268
+ helpers.entriesOf(stateOverride ?? {}).forEach(([k, v]) => {
269
+ helpers.entriesOf(v).forEach(([kk, vv]) => {
270
+ if (o?.[kk]?.[k] !== vv && !changedKeys.includes(kk)) {
271
+ changedKeys.push(kk);
272
+ }
273
+ o[kk] = o[kk] ?? {};
274
+ o[kk][k] = vv;
275
+ });
276
+ });
277
+ if (!changedKeys.length) return;
278
+ changedKeys.forEach((key) => {
279
+ const newState = deepMerge(
280
+ queuedState.current[key],
281
+ o?.[key] ?? {}
282
+ );
283
+ if (helpers.keysOf(newState).every((k) => newState[k] === states.current[key]?.[k])) {
284
+ return;
285
+ }
286
+ queuedState.current[key] = newState;
287
+ void refresh(key);
288
+ });
289
+ }, [stateOverride]);
290
+ return {
291
+ ref: getRef,
292
+ refs,
293
+ state: finalState,
294
+ updateState
295
+ };
296
+ }
297
+
298
+ exports.useComponentState = useComponentState;
@@ -0,0 +1,39 @@
1
+ import * as react from 'react';
2
+ import { ElementType, ComponentPropsWithRef, PropsWithChildren, Ref } from 'react';
3
+
4
+ type Key = string | number | symbol;
5
+ type Obj<T = unknown, K extends Key = string> = Record<K, T>;
6
+
7
+ type ComponentState<T extends Obj<boolean | undefined> = Obj<boolean | undefined>> = Obj<boolean | undefined> & {
8
+ selected?: boolean | undefined;
9
+ hover?: boolean | undefined;
10
+ focusWithin?: boolean | undefined;
11
+ focus?: boolean | undefined;
12
+ pressed?: boolean | undefined;
13
+ active?: boolean | undefined;
14
+ highlighted?: boolean | undefined;
15
+ disabled?: boolean | undefined;
16
+ } & {
17
+ [K in keyof T]?: boolean | undefined;
18
+ };
19
+ interface ComponentStateProps<S extends ComponentState = ComponentState, E extends Ref<HTMLElement | SVGElement> = Ref<HTMLElement | SVGElement>> {
20
+ stateDefinition?: ComponentState<S>;
21
+ stateOverride?: {
22
+ [K in keyof ComponentState<S>]: Obj<boolean | undefined>;
23
+ };
24
+ onStateChange?: (state: ComponentState<S>, key: string) => void;
25
+ ref?: E;
26
+ }
27
+ type BaseProps<T = {}, O extends keyof (T extends ElementType ? ComponentPropsWithRef<T> : PropsWithChildren<T>) = never, S extends ComponentState = never> = Omit<(T extends ElementType ? ComponentPropsWithRef<T> : PropsWithChildren<T>), O> & (S extends never ? {} : {
28
+ stateProps?: ComponentStateProps<S, T extends ElementType ? ComponentPropsWithRef<T>["ref"] : Ref<HTMLElement | SVGElement>>;
29
+ });
30
+
31
+ declare function useComponentState<S extends Obj<boolean | undefined> = ComponentState>(options?: ComponentStateProps<S>): {
32
+ ref: (node: HTMLElement | SVGElement | null, key?: string) => void;
33
+ refs: react.MutableRefObject<Obj<HTMLElement | SVGElement | null>>;
34
+ state: Obj<ComponentState<S>>;
35
+ updateState: <S_1 extends Obj<boolean | undefined> = Obj<boolean | undefined>>(patch: Partial<ComponentState<S_1>>, key?: string) => void;
36
+ };
37
+
38
+ export { useComponentState };
39
+ export type { BaseProps, ComponentState, ComponentStateProps };
package/dist/index.mjs ADDED
@@ -0,0 +1,296 @@
1
+ import { i as isObj, a as isArr, k as keysOf, b as isIn, c as isFunc, e as entriesOf } from './shared/react-meta-state.CzzhN3TK.mjs';
2
+ import { useRef, useState, useCallback, useEffect } from 'react';
3
+ import { D as DEFAULT_COMPONENT_STATE } from './shared/react-meta-state.C3o6Eh_L.mjs';
4
+ import { f as fromJson } from './shared/react-meta-state.BjE3OmFW.mjs';
5
+
6
+ function mergeArr(target, source) {
7
+ const merged = [...target];
8
+ for (const item of source) {
9
+ if (!merged.includes(item)) {
10
+ merged.push(item);
11
+ }
12
+ }
13
+ return merged;
14
+ }
15
+ function deepMerge(target, source) {
16
+ if (!isObj(target)) {
17
+ return source;
18
+ }
19
+ if (!isObj(source)) {
20
+ return target;
21
+ }
22
+ const obj = { ...target };
23
+ for (const key in source) {
24
+ const t = target[key];
25
+ const s = source[key];
26
+ if (!(key in target) || target[key] === void 0) {
27
+ obj[key] = s;
28
+ continue;
29
+ }
30
+ if (isArr(s)) {
31
+ obj[key] = isArr(t) ? mergeArr(t, s) : s;
32
+ continue;
33
+ }
34
+ if (isObj(s)) {
35
+ obj[key] = isObj(t) ? deepMerge(t, s) : s;
36
+ continue;
37
+ }
38
+ obj[key] = s ?? t;
39
+ }
40
+ return obj;
41
+ }
42
+ function deepMergeAll(...objects) {
43
+ return objects.reduce((v, x) => deepMerge(v, x), {});
44
+ }
45
+
46
+ async function wait(ms) {
47
+ return new Promise((r) => setTimeout(r, ms));
48
+ }
49
+
50
+ function useComponentState(options) {
51
+ const {
52
+ ref: parentRef,
53
+ stateDefinition = DEFAULT_COMPONENT_STATE,
54
+ onStateChange,
55
+ stateOverride
56
+ } = options ?? {};
57
+ const refs = useRef({});
58
+ const states = useRef({});
59
+ const listeners = useRef({});
60
+ const observers = useRef({});
61
+ const refreshTimes = useRef({});
62
+ const queuedState = useRef({});
63
+ const overrides = useRef({});
64
+ const [finalState, setFinalState] = useState({});
65
+ const getAttributeState = useCallback((key) => {
66
+ const attributeState = refs.current[key]?.getAttribute("data-rms");
67
+ return attributeState === "" ? {} : fromJson(attributeState ?? "{}");
68
+ }, []);
69
+ const normaliseState = useCallback((state) => {
70
+ if (state.focus) {
71
+ state.focusWithin = true;
72
+ }
73
+ if (state.disabled) {
74
+ state.focus = state.focusWithin = state.active = state.selected = false;
75
+ }
76
+ return state;
77
+ }, []);
78
+ const refresh = useCallback(async (key = "default") => {
79
+ normaliseState(queuedState.current[key]);
80
+ const attributeState = getAttributeState(key);
81
+ const allStates = normaliseState(deepMergeAll(
82
+ states.current[key],
83
+ queuedState.current[key] ?? {},
84
+ overrides.current?.[key] ?? {},
85
+ attributeState
86
+ ));
87
+ if (keysOf(allStates).every((k) => allStates[k] === states.current[key]?.[k])) {
88
+ queuedState.current[key] = {};
89
+ return;
90
+ }
91
+ const rt = refreshTimes.current[key] ?? 0;
92
+ if (!!rt) return;
93
+ const rt2 = refreshTimes.current[key] = Date.now();
94
+ const delay = Math.max(rt2 + 8 - Date.now(), 0);
95
+ if (!!delay) {
96
+ void await wait(delay);
97
+ }
98
+ refreshTimes.current[key] = 0;
99
+ const el = refs.current[key];
100
+ const innerState = deepMerge(
101
+ states.current[key],
102
+ queuedState.current[key] ?? {}
103
+ );
104
+ const newState = normaliseState(deepMergeAll(
105
+ innerState,
106
+ overrides.current?.[key] ?? {},
107
+ attributeState
108
+ ));
109
+ const prevState = states.current[key];
110
+ queuedState.current[key] = {};
111
+ if (keysOf(newState).every((k) => newState[k] === states.current[key]?.[k])) return;
112
+ states.current[key] = innerState;
113
+ setFinalState((s) => ({ ...s, [key]: newState }));
114
+ keysOf(stateDefinition).forEach((k) => el.classList[newState[k] ? "add" : "remove"](k));
115
+ onStateChange?.(newState, key);
116
+ if (isIn(el, "disabled") && el.disabled !== newState.disabled) {
117
+ el.disabled = !!newState.disabled;
118
+ }
119
+ if (newState.focus && el !== document.activeElement && !prevState.focus) {
120
+ el.focus();
121
+ }
122
+ if (!newState.focus && el === document.activeElement && prevState.focus) {
123
+ el.blur();
124
+ }
125
+ }, [getAttributeState, normaliseState, onStateChange, stateDefinition]);
126
+ const updateState = useCallback((patch, key = "default") => {
127
+ queuedState.current[key] = deepMerge(queuedState.current[key] ?? {}, patch);
128
+ void refresh(key);
129
+ }, [refresh]);
130
+ const getRef = useCallback((node, key = "default") => {
131
+ const ref = refs.current[key];
132
+ const observer = observers.current[key];
133
+ if (isFunc(parentRef)) {
134
+ parentRef?.(node);
135
+ } else if (isIn(parentRef, "current")) {
136
+ parentRef.current = node;
137
+ }
138
+ if (ref && node && ref !== node) {
139
+ const l2 = listeners.current[key];
140
+ observer.disconnect();
141
+ document.removeEventListener("pointerup", l2.pointerup);
142
+ node.removeEventListener("pointerdown", l2.pointerdown);
143
+ node.removeEventListener("pointerleave", l2.pointerleave);
144
+ node.removeEventListener("pointerenter", l2.pointerenter);
145
+ node.removeEventListener("focus", l2.focus);
146
+ node.removeEventListener("focusin", l2.focusin);
147
+ node.removeEventListener("focusout", l2.focusout);
148
+ delete listeners.current[key];
149
+ delete refs.current[key];
150
+ delete states.current[key];
151
+ delete observers.current[key];
152
+ }
153
+ if (!node || ref === node) return;
154
+ refs.current[key] = node;
155
+ listeners.current[key] = {
156
+ pointerup: () => {
157
+ if (!isIn(stateDefinition, "pressed")) return;
158
+ updateState({ pressed: false }, key);
159
+ },
160
+ pointerdown: () => {
161
+ if (!isIn(stateDefinition, "pressed")) return;
162
+ updateState({ pressed: true }, key);
163
+ },
164
+ pointerleave: () => {
165
+ if (!isIn(stateDefinition, "hover")) return;
166
+ updateState({ hover: false }, key);
167
+ },
168
+ pointerenter: () => {
169
+ if (!isIn(stateDefinition, "hover")) return;
170
+ updateState({ hover: true }, key);
171
+ },
172
+ focus: () => {
173
+ if (!isIn(stateDefinition, "focus")) return;
174
+ updateState({ focus: true }, key);
175
+ },
176
+ focusin: () => {
177
+ if (!isIn(stateDefinition, "focusWithin")) return;
178
+ updateState({ focusWithin: true }, key);
179
+ },
180
+ focusout: () => {
181
+ const active = document.activeElement;
182
+ let shouldFocusWithinOut = false;
183
+ if (isIn(stateDefinition, "focusWithin")) {
184
+ if (!refs.current[key]?.contains(active) && refs.current[key] !== active && refs.current[key] !== active) {
185
+ shouldFocusWithinOut = true;
186
+ }
187
+ }
188
+ if (isIn(stateDefinition, "focus")) {
189
+ updateState({ focus: false, focusWithin: shouldFocusWithinOut ? false : void 0 }, key);
190
+ }
191
+ }
192
+ };
193
+ const l = listeners.current[key];
194
+ const obs = new MutationObserver((mutations) => {
195
+ for (const mutation of mutations) {
196
+ if (mutation.type === "attributes" && mutation.attributeName === "data-rms") {
197
+ return updateState({}, key);
198
+ }
199
+ if (mutation.type === "attributes" && mutation.attributeName === "class") {
200
+ const target = mutation.target;
201
+ const oldClasses = mutation.oldValue?.split(" ") ?? [];
202
+ const newClasses = target.classList.value.split(" ");
203
+ const added = newClasses.filter((x) => !oldClasses.includes(x) && isIn(stateDefinition, x) && !states.current[key]?.[x]);
204
+ const removed = oldClasses.filter((x) => !newClasses.includes(x) && isIn(stateDefinition, x) && !!states.current[key]?.[x]);
205
+ updateState({
206
+ ...added.reduce((a, v) => ({ ...a, [v]: true }), {}),
207
+ ...removed.reduce((a, v) => ({ ...a, [v]: false }), {})
208
+ }, key);
209
+ return;
210
+ }
211
+ const el = mutation.target;
212
+ if (!isIn(el, "disabled") || mutation.type !== "attributes" || mutation.attributeName !== "disabled" || el.disabled === states.current[key]?.disabled || !isIn(stateDefinition, "disabled")) {
213
+ return;
214
+ }
215
+ const attributeState = getAttributeState(key);
216
+ if (el.disabled) {
217
+ if (attributeState.disabled === false || overrides.current?.[key]?.disabled === false) {
218
+ el.disabled = false;
219
+ return;
220
+ }
221
+ updateState({ disabled: true }, key);
222
+ } else {
223
+ if (attributeState.disabled === true || overrides.current?.[key]?.disabled === true) {
224
+ el.disabled = true;
225
+ return;
226
+ }
227
+ updateState({ disabled: false }, key);
228
+ }
229
+ }
230
+ });
231
+ document.addEventListener("pointerup", l.pointerup);
232
+ node.addEventListener("pointerdown", l.pointerdown);
233
+ node.addEventListener("pointerleave", l.pointerleave);
234
+ node.addEventListener("pointerenter", l.pointerenter);
235
+ node.addEventListener("focus", l.focus);
236
+ node.addEventListener("focusin", l.focusin);
237
+ node.addEventListener("focusout", l.focusout);
238
+ node.setAttribute("data-rms", "");
239
+ obs.observe(node, {
240
+ attributes: true,
241
+ attributeOldValue: true,
242
+ attributeFilter: ["disabled", "class", "data-rms"]
243
+ });
244
+ const s = {};
245
+ if (isIn(stateDefinition, "focus")) {
246
+ s.focus = node === document.activeElement;
247
+ }
248
+ if (isIn(stateDefinition, "focusWithin")) {
249
+ s.focusWithin = node === document.activeElement || node.contains(document.activeElement);
250
+ }
251
+ if (isIn(stateDefinition, "disabled")) {
252
+ s.disabled = isIn(node, "disabled") && !!node.disabled;
253
+ }
254
+ observers.current[key] = obs;
255
+ states.current[key] ??= {};
256
+ queuedState.current[key] = deepMergeAll(
257
+ stateDefinition ?? {},
258
+ queuedState.current[key] ?? {},
259
+ s ?? {}
260
+ );
261
+ void refresh(key);
262
+ }, [parentRef, stateDefinition, refresh, updateState, getAttributeState]);
263
+ useEffect(() => {
264
+ const o = overrides.current;
265
+ const changedKeys = [];
266
+ entriesOf(stateOverride ?? {}).forEach(([k, v]) => {
267
+ entriesOf(v).forEach(([kk, vv]) => {
268
+ if (o?.[kk]?.[k] !== vv && !changedKeys.includes(kk)) {
269
+ changedKeys.push(kk);
270
+ }
271
+ o[kk] = o[kk] ?? {};
272
+ o[kk][k] = vv;
273
+ });
274
+ });
275
+ if (!changedKeys.length) return;
276
+ changedKeys.forEach((key) => {
277
+ const newState = deepMerge(
278
+ queuedState.current[key],
279
+ o?.[key] ?? {}
280
+ );
281
+ if (keysOf(newState).every((k) => newState[k] === states.current[key]?.[k])) {
282
+ return;
283
+ }
284
+ queuedState.current[key] = newState;
285
+ void refresh(key);
286
+ });
287
+ }, [stateOverride]);
288
+ return {
289
+ ref: getRef,
290
+ refs,
291
+ state: finalState,
292
+ updateState
293
+ };
294
+ }
295
+
296
+ export { useComponentState };
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@cjaye/react-meta-state",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "author": "Charles Jaye",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/c-jaye/react-meta-state.git"
11
+ },
12
+ "private": false,
13
+ "type": "module",
14
+ "scripts": {
15
+ "clean": "del-cli --force \"**/package-lock.json\" \"**/storybook-static\" \"**/test-results.xml\" \"**/debug-storybook.log\" \"**/node_modules\" \"**/dist\" \"**/verdaccio/storage\" \"**/verdaccio/htpasswd\" \"**/verdaccio/config.yaml\"",
16
+ "postinstall": "pnpm run postinstall:playwright && pnpm run postinstall:verdaccio",
17
+ "postinstall:playwright": "pnpx playwright install --with-deps",
18
+ "postinstall:verdaccio": "chmod +x verdaccio/init verdaccio/login",
19
+ "build": "pnpm run test && pnpm run build:bundle",
20
+ "build:storybook": "storybook build",
21
+ "build:bundle": "unbuild",
22
+ "dev": "npm run build:bundle && concurrently \"pnpm run dev:storybook\" \"pnpm run dev:verdaccio\"",
23
+ "dev:storybook": "storybook dev -p 6006 -c ./playground/.storybook",
24
+ "dev:verdaccio": "verdaccio --listen 30333",
25
+ "lint": "tsc -b && pnpm run lint:check",
26
+ "lint:check": "pnpm eslint .",
27
+ "lint:fix": "pnpm eslint . --fix",
28
+ "test": "pnpm run lint && pnpm run test:unit",
29
+ "test:unit": "vitest --no-watch",
30
+ "prepublishOnly": "del-cli --force \"**/dist\" && pnpm run build",
31
+ "publish:verdaccio": "verdaccio/init || true && verdaccio/login && pnpm publish --access public --tag latest --no-git-checks --registry http://localhost:30333",
32
+ "publish:chromatic": "pnpx chromatic@latest --project-token",
33
+ "publish:npm": "pnpm publish --access public --tag latest --no-git-checks --registry registry.npmjs.org",
34
+ "publish:npm:login": "pnpm adduser --registry registry.npmjs.org --userconfig .npmrc"
35
+ },
36
+ "packageManager": "pnpm@10.31.0",
37
+ "engines": {
38
+ "node": ">=22.17.1"
39
+ },
40
+ "main": "./dist/index.mjs",
41
+ "types": "./dist/index.d.mts",
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.mts",
48
+ "import": "./dist/index.mjs",
49
+ "require": "./dist/index.cjs",
50
+ "default": "./dist/index.mjs"
51
+ },
52
+ "./addon": {
53
+ "types": "./dist/addon/index.d.mts",
54
+ "import": "./dist/addon/index.mjs",
55
+ "require": "./dist/addon/index.cjs",
56
+ "default": "./dist/addon/index.mjs"
57
+ },
58
+ "./addon/preview": {
59
+ "types": "./dist/addon/preview.d.mts",
60
+ "import": "./dist/addon/preview.mjs",
61
+ "require": "./dist/addon/preview.cjs",
62
+ "default": "./dist/addon/preview.mjs"
63
+ },
64
+ "./addon/manager": {
65
+ "types": "./dist/addon/manager.d.mts",
66
+ "import": "./dist/addon/manager.mjs",
67
+ "require": "./dist/addon/manager.cjs",
68
+ "default": "./dist/addon/manager.mjs"
69
+ },
70
+ "./package.json": "./package.json"
71
+ },
72
+ "os": [
73
+ "darwin",
74
+ "linux",
75
+ "win32"
76
+ ],
77
+ "dependencies": {
78
+ "react": "^18.3.1",
79
+ "react-dom": "^18.3.1"
80
+ },
81
+ "peerDependencies": {
82
+ "react": "^18.3.1",
83
+ "react-dom": "^18.3.1",
84
+ "storybook": "^10.3.3"
85
+ },
86
+ "devDependencies": {
87
+ "@chromatic-com/storybook": "^5.0.1",
88
+ "@storybook/addon-a11y": "^10.2.16",
89
+ "@storybook/addon-designs": "^11.1.2",
90
+ "@storybook/addon-vitest": "^10.2.16",
91
+ "@storybook/react-vite": "^10.2.16",
92
+ "@stylistic/eslint-plugin": "^5.10.0",
93
+ "@types/react": "^18.3.28",
94
+ "@types/react-dom": "^18.3.7",
95
+ "@vitejs/plugin-react-swc": "^4.3.0",
96
+ "@vitest/browser": "^4.0.18",
97
+ "@vitest/browser-playwright": "^4.0.18",
98
+ "@vitest/coverage-v8": "^4.0.18",
99
+ "classnames": "^2.5.1",
100
+ "concurrently": "^9.2.1",
101
+ "del-cli": "^7.0.0",
102
+ "eslint": "^9.39.4",
103
+ "eslint-import-resolver-typescript": "^4.4.4",
104
+ "eslint-plugin-import-x": "^4.16.1",
105
+ "eslint-plugin-react-hooks": "^7.0.1",
106
+ "eslint-plugin-storybook": "10.3.3",
107
+ "globals": "^17.4.0",
108
+ "jiti": "^2.6.1",
109
+ "playwright": "^1.58.2",
110
+ "sass-embedded": "^1.97.3",
111
+ "storybook": "^10.2.16",
112
+ "storybook-addon-pseudo-states": "^10.2.16",
113
+ "tinyglobby": "^0.2.15",
114
+ "typescript": "~5.9.3",
115
+ "typescript-eslint": "^8.56.1",
116
+ "unbuild": "^3.6.1",
117
+ "verdaccio": "^6.3.1",
118
+ "vite-tsconfig-paths": "^6.1.1",
119
+ "vitest": "^4.0.18"
120
+ }
121
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ function fromJson(json) {
4
+ return JSON.parse(json);
5
+ }
6
+ function toJson(json) {
7
+ return JSON.stringify(json);
8
+ }
9
+
10
+ exports.fromJson = fromJson;
11
+ exports.toJson = toJson;
@@ -0,0 +1,8 @@
1
+ function fromJson(json) {
2
+ return JSON.parse(json);
3
+ }
4
+ function toJson(json) {
5
+ return JSON.stringify(json);
6
+ }
7
+
8
+ export { fromJson as f, toJson as t };
@@ -0,0 +1,14 @@
1
+ const ADDON_ID = "react-meta-state";
2
+ const TOOL_ID = `${ADDON_ID}/tool`;
3
+ const DEFAULT_COMPONENT_STATE = {
4
+ selected: false,
5
+ hover: false,
6
+ focusWithin: false,
7
+ focus: false,
8
+ pressed: false,
9
+ active: false,
10
+ highlighted: false,
11
+ disabled: false
12
+ };
13
+
14
+ export { ADDON_ID as A, DEFAULT_COMPONENT_STATE as D, TOOL_ID as T };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const ADDON_ID = "react-meta-state";
4
+ const TOOL_ID = `${ADDON_ID}/tool`;
5
+ const DEFAULT_COMPONENT_STATE = {
6
+ selected: false,
7
+ hover: false,
8
+ focusWithin: false,
9
+ focus: false,
10
+ pressed: false,
11
+ active: false,
12
+ highlighted: false,
13
+ disabled: false
14
+ };
15
+
16
+ exports.ADDON_ID = ADDON_ID;
17
+ exports.DEFAULT_COMPONENT_STATE = DEFAULT_COMPONENT_STATE;
18
+ exports.TOOL_ID = TOOL_ID;
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ function keysOf(obj) {
4
+ return Object.keys(obj);
5
+ }
6
+ function entriesOf(obj) {
7
+ return Object.entries(obj);
8
+ }
9
+ function isIn(obj, key) {
10
+ return !!obj && key in obj && obj[key] !== void 0;
11
+ }
12
+ function isArr(value) {
13
+ return Array.isArray(value);
14
+ }
15
+ function isObj(value) {
16
+ return !!value && !isArr(value) && typeof value === "object";
17
+ }
18
+ function isFunc(value) {
19
+ return typeof value === "function";
20
+ }
21
+
22
+ exports.entriesOf = entriesOf;
23
+ exports.isArr = isArr;
24
+ exports.isFunc = isFunc;
25
+ exports.isIn = isIn;
26
+ exports.isObj = isObj;
27
+ exports.keysOf = keysOf;
@@ -0,0 +1,20 @@
1
+ function keysOf(obj) {
2
+ return Object.keys(obj);
3
+ }
4
+ function entriesOf(obj) {
5
+ return Object.entries(obj);
6
+ }
7
+ function isIn(obj, key) {
8
+ return !!obj && key in obj && obj[key] !== void 0;
9
+ }
10
+ function isArr(value) {
11
+ return Array.isArray(value);
12
+ }
13
+ function isObj(value) {
14
+ return !!value && !isArr(value) && typeof value === "object";
15
+ }
16
+ function isFunc(value) {
17
+ return typeof value === "function";
18
+ }
19
+
20
+ export { isArr as a, isIn as b, isFunc as c, entriesOf as e, isObj as i, keysOf as k };
package/package.json ADDED
@@ -0,0 +1,119 @@
1
+ {
2
+ "name": "@cjaye/react-meta-state",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "author": "Charles Jaye",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/c-jaye/react-meta-state.git"
11
+ },
12
+ "private": false,
13
+ "type": "module",
14
+ "engines": {
15
+ "node": ">=22.17.1"
16
+ },
17
+ "main": "./dist/index.mjs",
18
+ "types": "./dist/index.d.mts",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.mts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.cjs",
27
+ "default": "./dist/index.mjs"
28
+ },
29
+ "./addon": {
30
+ "types": "./dist/addon/index.d.mts",
31
+ "import": "./dist/addon/index.mjs",
32
+ "require": "./dist/addon/index.cjs",
33
+ "default": "./dist/addon/index.mjs"
34
+ },
35
+ "./addon/preview": {
36
+ "types": "./dist/addon/preview.d.mts",
37
+ "import": "./dist/addon/preview.mjs",
38
+ "require": "./dist/addon/preview.cjs",
39
+ "default": "./dist/addon/preview.mjs"
40
+ },
41
+ "./addon/manager": {
42
+ "types": "./dist/addon/manager.d.mts",
43
+ "import": "./dist/addon/manager.mjs",
44
+ "require": "./dist/addon/manager.cjs",
45
+ "default": "./dist/addon/manager.mjs"
46
+ },
47
+ "./package.json": "./package.json"
48
+ },
49
+ "os": [
50
+ "darwin",
51
+ "linux",
52
+ "win32"
53
+ ],
54
+ "dependencies": {
55
+ "react": "^18.3.1",
56
+ "react-dom": "^18.3.1"
57
+ },
58
+ "peerDependencies": {
59
+ "react": "^18.3.1",
60
+ "react-dom": "^18.3.1",
61
+ "storybook": "^10.3.3"
62
+ },
63
+ "devDependencies": {
64
+ "@chromatic-com/storybook": "^5.0.1",
65
+ "@storybook/addon-a11y": "^10.2.16",
66
+ "@storybook/addon-designs": "^11.1.2",
67
+ "@storybook/addon-vitest": "^10.2.16",
68
+ "@storybook/react-vite": "^10.2.16",
69
+ "@stylistic/eslint-plugin": "^5.10.0",
70
+ "@types/react": "^18.3.28",
71
+ "@types/react-dom": "^18.3.7",
72
+ "@vitejs/plugin-react-swc": "^4.3.0",
73
+ "@vitest/browser": "^4.0.18",
74
+ "@vitest/browser-playwright": "^4.0.18",
75
+ "@vitest/coverage-v8": "^4.0.18",
76
+ "classnames": "^2.5.1",
77
+ "concurrently": "^9.2.1",
78
+ "del-cli": "^7.0.0",
79
+ "eslint": "^9.39.4",
80
+ "eslint-import-resolver-typescript": "^4.4.4",
81
+ "eslint-plugin-import-x": "^4.16.1",
82
+ "eslint-plugin-react-hooks": "^7.0.1",
83
+ "eslint-plugin-storybook": "10.3.3",
84
+ "globals": "^17.4.0",
85
+ "jiti": "^2.6.1",
86
+ "playwright": "^1.58.2",
87
+ "sass-embedded": "^1.97.3",
88
+ "storybook": "^10.2.16",
89
+ "storybook-addon-pseudo-states": "^10.2.16",
90
+ "tinyglobby": "^0.2.15",
91
+ "typescript": "~5.9.3",
92
+ "typescript-eslint": "^8.56.1",
93
+ "unbuild": "^3.6.1",
94
+ "verdaccio": "^6.3.1",
95
+ "vite-tsconfig-paths": "^6.1.1",
96
+ "vitest": "^4.0.18"
97
+ },
98
+ "scripts": {
99
+ "clean": "del-cli --force \"**/package-lock.json\" \"**/storybook-static\" \"**/test-results.xml\" \"**/debug-storybook.log\" \"**/node_modules\" \"**/dist\" \"**/verdaccio/storage\" \"**/verdaccio/htpasswd\" \"**/verdaccio/config.yaml\"",
100
+ "postinstall": "pnpm run postinstall:playwright && pnpm run postinstall:verdaccio",
101
+ "postinstall:playwright": "pnpx playwright install --with-deps",
102
+ "postinstall:verdaccio": "chmod +x verdaccio/init verdaccio/login",
103
+ "build": "pnpm run test && pnpm run build:bundle",
104
+ "build:storybook": "storybook build",
105
+ "build:bundle": "unbuild",
106
+ "dev": "npm run build:bundle && concurrently \"pnpm run dev:storybook\" \"pnpm run dev:verdaccio\"",
107
+ "dev:storybook": "storybook dev -p 6006 -c ./playground/.storybook",
108
+ "dev:verdaccio": "verdaccio --listen 30333",
109
+ "lint": "tsc -b && pnpm run lint:check",
110
+ "lint:check": "pnpm eslint .",
111
+ "lint:fix": "pnpm eslint . --fix",
112
+ "test": "pnpm run lint && pnpm run test:unit",
113
+ "test:unit": "vitest --no-watch",
114
+ "publish:verdaccio": "verdaccio/init || true && verdaccio/login && pnpm publish --access public --tag latest --no-git-checks --registry http://localhost:30333",
115
+ "publish:chromatic": "pnpx chromatic@latest --project-token",
116
+ "publish:npm": "pnpm publish --access public --tag latest --no-git-checks --registry registry.npmjs.org",
117
+ "publish:npm:login": "pnpm adduser --registry registry.npmjs.org --userconfig .npmrc"
118
+ }
119
+ }