@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 +0 -0
- package/dist/addon/index.cjs +12 -0
- package/dist/addon/index.d.mts +5 -0
- package/dist/addon/index.mjs +10 -0
- package/dist/addon/manager.cjs +68 -0
- package/dist/addon/manager.d.mts +2 -0
- package/dist/addon/manager.mjs +66 -0
- package/dist/addon/preview.cjs +26 -0
- package/dist/addon/preview.d.mts +5 -0
- package/dist/addon/preview.mjs +24 -0
- package/dist/index.cjs +298 -0
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +296 -0
- package/dist/package.json +121 -0
- package/dist/shared/react-meta-state.BXhLgGzA.cjs +11 -0
- package/dist/shared/react-meta-state.BjE3OmFW.mjs +8 -0
- package/dist/shared/react-meta-state.C3o6Eh_L.mjs +14 -0
- package/dist/shared/react-meta-state.C5ASg9b-.cjs +18 -0
- package/dist/shared/react-meta-state.C6XVhRdK.cjs +27 -0
- package/dist/shared/react-meta-state.CzzhN3TK.mjs +20 -0
- package/package.json +119 -0
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,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,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,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;
|
package/dist/index.d.mts
ADDED
|
@@ -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,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
|
+
}
|