@ansh-ui/react 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-5CFQFMP5.js +178 -0
- package/dist/chunk-5CFQFMP5.js.map +1 -0
- package/dist/chunk-6VRF7X5C.cjs +164 -0
- package/dist/chunk-6VRF7X5C.cjs.map +1 -0
- package/dist/chunk-AVPEA5PB.cjs +718 -0
- package/dist/chunk-AVPEA5PB.cjs.map +1 -0
- package/dist/chunk-C32LKF23.cjs +198 -0
- package/dist/chunk-C32LKF23.cjs.map +1 -0
- package/dist/chunk-C5JG6TMM.cjs +1200 -0
- package/dist/chunk-C5JG6TMM.cjs.map +1 -0
- package/dist/chunk-DATBU46V.cjs +614 -0
- package/dist/chunk-DATBU46V.cjs.map +1 -0
- package/dist/chunk-EGG4CP7C.js +320 -0
- package/dist/chunk-EGG4CP7C.js.map +1 -0
- package/dist/chunk-H34OXXJU.js +55 -0
- package/dist/chunk-H34OXXJU.js.map +1 -0
- package/dist/chunk-HO2YSF5O.js +187 -0
- package/dist/chunk-HO2YSF5O.js.map +1 -0
- package/dist/chunk-HPGCILO4.js +760 -0
- package/dist/chunk-HPGCILO4.js.map +1 -0
- package/dist/chunk-IBXLC5IE.js +154 -0
- package/dist/chunk-IBXLC5IE.js.map +1 -0
- package/dist/chunk-LDCZQP3R.js +1185 -0
- package/dist/chunk-LDCZQP3R.js.map +1 -0
- package/dist/chunk-LXCIXR5T.js +27 -0
- package/dist/chunk-LXCIXR5T.js.map +1 -0
- package/dist/chunk-O2PX54KX.js +94 -0
- package/dist/chunk-O2PX54KX.js.map +1 -0
- package/dist/chunk-OHBWBXLO.cjs +101 -0
- package/dist/chunk-OHBWBXLO.cjs.map +1 -0
- package/dist/chunk-OJN5JBNP.cjs +776 -0
- package/dist/chunk-OJN5JBNP.cjs.map +1 -0
- package/dist/chunk-OLTS6G5M.js +606 -0
- package/dist/chunk-OLTS6G5M.js.map +1 -0
- package/dist/chunk-PO5DU3FU.cjs +29 -0
- package/dist/chunk-PO5DU3FU.cjs.map +1 -0
- package/dist/chunk-QO3E7TJS.js +2940 -0
- package/dist/chunk-QO3E7TJS.js.map +1 -0
- package/dist/chunk-RHTLNSVW.cjs +2976 -0
- package/dist/chunk-RHTLNSVW.cjs.map +1 -0
- package/dist/chunk-SFU5EPSN.cjs +61 -0
- package/dist/chunk-SFU5EPSN.cjs.map +1 -0
- package/dist/chunk-T4AK2KLM.js +699 -0
- package/dist/chunk-T4AK2KLM.js.map +1 -0
- package/dist/chunk-WGYNCSTP.cjs +333 -0
- package/dist/chunk-WGYNCSTP.cjs.map +1 -0
- package/dist/chunk-XEP6P7E6.js +279 -0
- package/dist/chunk-XEP6P7E6.js.map +1 -0
- package/dist/chunk-XMXECSQO.cjs +286 -0
- package/dist/chunk-XMXECSQO.cjs.map +1 -0
- package/dist/chunk-ZWAFLHC2.cjs +181 -0
- package/dist/chunk-ZWAFLHC2.cjs.map +1 -0
- package/dist/data.cjs +71 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +316 -0
- package/dist/data.d.ts +316 -0
- package/dist/data.js +6 -0
- package/dist/data.js.map +1 -0
- package/dist/form.cjs +153 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +660 -0
- package/dist/form.d.ts +660 -0
- package/dist/form.js +8 -0
- package/dist/form.js.map +1 -0
- package/dist/index.cjs +1641 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +552 -0
- package/dist/index.d.ts +552 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.cjs +68 -0
- package/dist/layout.cjs.map +1 -0
- package/dist/layout.d.cts +169 -0
- package/dist/layout.d.ts +169 -0
- package/dist/layout.js +7 -0
- package/dist/layout.js.map +1 -0
- package/dist/motion-CQmCq6_k.d.ts +395 -0
- package/dist/motion-CSX4g2N8.d.cts +395 -0
- package/dist/motion.cjs +79 -0
- package/dist/motion.cjs.map +1 -0
- package/dist/motion.d.cts +4 -0
- package/dist/motion.d.ts +4 -0
- package/dist/motion.js +6 -0
- package/dist/motion.js.map +1 -0
- package/dist/navigation.cjs +48 -0
- package/dist/navigation.cjs.map +1 -0
- package/dist/navigation.d.cts +210 -0
- package/dist/navigation.d.ts +210 -0
- package/dist/navigation.js +7 -0
- package/dist/navigation.js.map +1 -0
- package/dist/overlay.cjs +69 -0
- package/dist/overlay.cjs.map +1 -0
- package/dist/overlay.d.cts +330 -0
- package/dist/overlay.d.ts +330 -0
- package/dist/overlay.js +8 -0
- package/dist/overlay.js.map +1 -0
- package/dist/position-DVScKcaa.d.cts +66 -0
- package/dist/position-DVScKcaa.d.ts +66 -0
- package/dist/primitives.cjs +162 -0
- package/dist/primitives.cjs.map +1 -0
- package/dist/primitives.d.cts +294 -0
- package/dist/primitives.d.ts +294 -0
- package/dist/primitives.js +9 -0
- package/dist/primitives.js.map +1 -0
- package/dist/sidebar-D-PafAXw.d.cts +71 -0
- package/dist/sidebar-D-PafAXw.d.ts +71 -0
- package/dist/styles.css +5575 -0
- package/dist/use-observers-B9qtimrk.d.cts +99 -0
- package/dist/use-observers-B9qtimrk.d.ts +99 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ansh contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useControllableState } from './chunk-LXCIXR5T.js';
|
|
2
|
+
import { cx } from './chunk-H34OXXJU.js';
|
|
3
|
+
import { useState, useId, useEffect } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function Sidebar({
|
|
7
|
+
sections,
|
|
8
|
+
activeId,
|
|
9
|
+
onNavigate,
|
|
10
|
+
collapsed,
|
|
11
|
+
defaultCollapsed = false,
|
|
12
|
+
onCollapsedChange,
|
|
13
|
+
header,
|
|
14
|
+
footer,
|
|
15
|
+
collapsible = true,
|
|
16
|
+
label = "Main",
|
|
17
|
+
className
|
|
18
|
+
}) {
|
|
19
|
+
const [isCollapsed, setCollapsed] = useControllableState({
|
|
20
|
+
value: collapsed,
|
|
21
|
+
defaultValue: defaultCollapsed,
|
|
22
|
+
onChange: onCollapsedChange
|
|
23
|
+
});
|
|
24
|
+
return /* @__PURE__ */ jsxs(
|
|
25
|
+
"nav",
|
|
26
|
+
{
|
|
27
|
+
className: cx("ansh-sidebar", isCollapsed && "ansh-sidebar--collapsed", className),
|
|
28
|
+
"aria-label": label,
|
|
29
|
+
"data-collapsed": isCollapsed || void 0,
|
|
30
|
+
children: [
|
|
31
|
+
header ? /* @__PURE__ */ jsx("div", { className: "ansh-sidebar__header", children: header }) : null,
|
|
32
|
+
/* @__PURE__ */ jsx("div", { className: "ansh-sidebar__scroll", children: sections.map((section) => /* @__PURE__ */ jsxs("div", { className: "ansh-sidebar__section", children: [
|
|
33
|
+
section.label ? (
|
|
34
|
+
// Hidden visually when collapsed but kept for assistive technology, so the grouping
|
|
35
|
+
// survives the rail layout.
|
|
36
|
+
/* @__PURE__ */ jsx("h2", { className: cx("ansh-sidebar__heading", isCollapsed && "ansh-visually-hidden"), children: section.label })
|
|
37
|
+
) : null,
|
|
38
|
+
/* @__PURE__ */ jsx("ul", { className: "ansh-sidebar__list", children: section.items.map((item) => /* @__PURE__ */ jsx(
|
|
39
|
+
SidebarItem,
|
|
40
|
+
{
|
|
41
|
+
item,
|
|
42
|
+
activeId,
|
|
43
|
+
collapsed: isCollapsed,
|
|
44
|
+
onNavigate,
|
|
45
|
+
depth: 0
|
|
46
|
+
},
|
|
47
|
+
item.id
|
|
48
|
+
)) })
|
|
49
|
+
] }, section.id)) }),
|
|
50
|
+
footer ? /* @__PURE__ */ jsx("div", { className: "ansh-sidebar__footer", children: footer }) : null,
|
|
51
|
+
collapsible ? /* @__PURE__ */ jsx(
|
|
52
|
+
"button",
|
|
53
|
+
{
|
|
54
|
+
type: "button",
|
|
55
|
+
className: "ansh-sidebar__toggle ansh-focusable",
|
|
56
|
+
"aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
57
|
+
"aria-pressed": isCollapsed,
|
|
58
|
+
onClick: () => setCollapsed(!isCollapsed),
|
|
59
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
60
|
+
"path",
|
|
61
|
+
{
|
|
62
|
+
d: isCollapsed ? "M6 3.5L10.5 8L6 12.5" : "M10 3.5L5.5 8L10 12.5",
|
|
63
|
+
stroke: "currentColor",
|
|
64
|
+
strokeWidth: "1.6",
|
|
65
|
+
fill: "none",
|
|
66
|
+
strokeLinecap: "round",
|
|
67
|
+
strokeLinejoin: "round"
|
|
68
|
+
}
|
|
69
|
+
) })
|
|
70
|
+
}
|
|
71
|
+
) : null
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function SidebarItem({ item, activeId, collapsed, onNavigate, depth }) {
|
|
77
|
+
const hasChildren = Boolean(item.items && item.items.length > 0);
|
|
78
|
+
const containsActive = hasChildren && item.items.some((child) => child.id === activeId);
|
|
79
|
+
const [open, setOpen] = useState(containsActive);
|
|
80
|
+
const panelId = useId();
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (containsActive) setOpen(true);
|
|
83
|
+
}, [containsActive]);
|
|
84
|
+
const isActive = item.id === activeId;
|
|
85
|
+
if (hasChildren) {
|
|
86
|
+
return /* @__PURE__ */ jsxs("li", { className: "ansh-sidebar__item", children: [
|
|
87
|
+
/* @__PURE__ */ jsxs(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
className: cx(
|
|
92
|
+
"ansh-sidebar__link",
|
|
93
|
+
"ansh-sidebar__link--group",
|
|
94
|
+
containsActive && "ansh-sidebar__link--within",
|
|
95
|
+
"ansh-focusable"
|
|
96
|
+
),
|
|
97
|
+
"aria-expanded": open,
|
|
98
|
+
"aria-controls": panelId,
|
|
99
|
+
disabled: item.disabled,
|
|
100
|
+
onClick: () => setOpen((v) => !v),
|
|
101
|
+
children: [
|
|
102
|
+
item.icon ? /* @__PURE__ */ jsx("span", { className: "ansh-sidebar__icon", children: item.icon }) : null,
|
|
103
|
+
/* @__PURE__ */ jsx("span", { className: cx("ansh-sidebar__label", collapsed && "ansh-visually-hidden"), children: item.label }),
|
|
104
|
+
/* @__PURE__ */ jsx(
|
|
105
|
+
"span",
|
|
106
|
+
{
|
|
107
|
+
className: cx("ansh-sidebar__chevron", open && "ansh-sidebar__chevron--open"),
|
|
108
|
+
"aria-hidden": "true",
|
|
109
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "10", height: "10", children: /* @__PURE__ */ jsx(
|
|
110
|
+
"path",
|
|
111
|
+
{
|
|
112
|
+
d: "M3 4.5L6 7.5l3-3",
|
|
113
|
+
stroke: "currentColor",
|
|
114
|
+
strokeWidth: "1.5",
|
|
115
|
+
fill: "none",
|
|
116
|
+
strokeLinecap: "round",
|
|
117
|
+
strokeLinejoin: "round"
|
|
118
|
+
}
|
|
119
|
+
) })
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsx("ul", { className: "ansh-sidebar__list ansh-sidebar__list--nested", id: panelId, hidden: !open, children: item.items.map((child) => /* @__PURE__ */ jsx(
|
|
126
|
+
SidebarItem,
|
|
127
|
+
{
|
|
128
|
+
item: child,
|
|
129
|
+
activeId,
|
|
130
|
+
collapsed,
|
|
131
|
+
onNavigate,
|
|
132
|
+
depth: depth + 1
|
|
133
|
+
},
|
|
134
|
+
child.id
|
|
135
|
+
)) })
|
|
136
|
+
] });
|
|
137
|
+
}
|
|
138
|
+
const Component = item.href ? "a" : "button";
|
|
139
|
+
return /* @__PURE__ */ jsx("li", { className: "ansh-sidebar__item", children: /* @__PURE__ */ jsxs(
|
|
140
|
+
Component,
|
|
141
|
+
{
|
|
142
|
+
...item.href ? { href: item.href } : { type: "button" },
|
|
143
|
+
className: cx(
|
|
144
|
+
"ansh-sidebar__link",
|
|
145
|
+
isActive && "ansh-sidebar__link--active",
|
|
146
|
+
"ansh-focusable"
|
|
147
|
+
),
|
|
148
|
+
"aria-current": isActive ? "page" : void 0,
|
|
149
|
+
"aria-disabled": item.disabled || void 0,
|
|
150
|
+
title: collapsed ? item.label : void 0,
|
|
151
|
+
onClick: (event) => {
|
|
152
|
+
if (item.disabled) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
item.onClick?.();
|
|
157
|
+
onNavigate?.(item);
|
|
158
|
+
},
|
|
159
|
+
children: [
|
|
160
|
+
item.icon ? /* @__PURE__ */ jsx("span", { className: "ansh-sidebar__icon", children: item.icon }) : null,
|
|
161
|
+
/* @__PURE__ */ jsx("span", { className: cx("ansh-sidebar__label", collapsed && "ansh-visually-hidden"), children: item.label }),
|
|
162
|
+
item.badge !== void 0 && !collapsed ? /* @__PURE__ */ jsx("span", { className: "ansh-sidebar__badge", children: item.badge }) : null
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
) });
|
|
166
|
+
}
|
|
167
|
+
function AppShell({ sidebar, header, children, className }) {
|
|
168
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("ansh-shell", Boolean(sidebar) && "ansh-shell--with-sidebar", className), children: [
|
|
169
|
+
/* @__PURE__ */ jsx("a", { href: "#ansh-main", className: "ansh-skip-link", children: "Skip to main content" }),
|
|
170
|
+
header ? /* @__PURE__ */ jsx("header", { className: "ansh-shell__header", children: header }) : null,
|
|
171
|
+
sidebar ? /* @__PURE__ */ jsx("aside", { className: "ansh-shell__sidebar", children: sidebar }) : null,
|
|
172
|
+
/* @__PURE__ */ jsx("main", { className: "ansh-shell__main", id: "ansh-main", tabIndex: -1, children })
|
|
173
|
+
] });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { AppShell, Sidebar };
|
|
177
|
+
//# sourceMappingURL=chunk-5CFQFMP5.js.map
|
|
178
|
+
//# sourceMappingURL=chunk-5CFQFMP5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/navigation/sidebar.tsx"],"names":[],"mappings":";;;;;AAsDO,SAAS,OAAA,CAAQ;AAAA,EACtB,QAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA,GAAmB,KAAA;AAAA,EACnB,iBAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA,GAAc,IAAA;AAAA,EACd,KAAA,GAAQ,MAAA;AAAA,EACR;AACF,CAAA,EAAiB;AACf,EAAA,MAAM,CAAC,WAAA,EAAa,YAAY,CAAA,GAAI,oBAAA,CAA8B;AAAA,IAChE,KAAA,EAAO,SAAA;AAAA,IACP,YAAA,EAAc,gBAAA;AAAA,IACd,QAAA,EAAU;AAAA,GACX,CAAA;AAED,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA,CAAG,cAAA,EAAgB,WAAA,IAAe,2BAA2B,SAAS,CAAA;AAAA,MACjF,YAAA,EAAY,KAAA;AAAA,MACZ,kBAAgB,WAAA,IAAe,MAAA;AAAA,MAE9B,QAAA,EAAA;AAAA,QAAA,MAAA,mBAAS,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sBAAA,EAAwB,kBAAO,CAAA,GAAS,IAAA;AAAA,wBAEjE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sBAAA,EACZ,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,qBACb,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uBAAA,EACZ,QAAA,EAAA;AAAA,UAAA,OAAA,CAAQ,KAAA;AAAA;AAAA;AAAA,4BAGP,GAAA,CAAC,QAAG,SAAA,EAAW,EAAA,CAAG,yBAAyB,WAAA,IAAe,sBAAsB,CAAA,EAC7E,QAAA,EAAA,OAAA,CAAQ,KAAA,EACX;AAAA,cACE,IAAA;AAAA,0BAEJ,GAAA,CAAC,QAAG,SAAA,EAAU,oBAAA,EACX,kBAAQ,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBAClB,GAAA;AAAA,YAAC,WAAA;AAAA,YAAA;AAAA,cAEC,IAAA;AAAA,cACA,QAAA;AAAA,cACA,SAAA,EAAW,WAAA;AAAA,cACX,UAAA;AAAA,cACA,KAAA,EAAO;AAAA,aAAA;AAAA,YALF,IAAA,CAAK;AAAA,WAOb,CAAA,EACH;AAAA,SAAA,EAAA,EApB0C,OAAA,CAAQ,EAqBpD,CACD,CAAA,EACH,CAAA;AAAA,QAEC,yBAAS,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sBAAA,EAAwB,kBAAO,CAAA,GAAS,IAAA;AAAA,QAEhE,WAAA,mBACC,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,qCAAA;AAAA,YACV,YAAA,EAAY,cAAc,gBAAA,GAAmB,kBAAA;AAAA,YAC7C,cAAA,EAAc,WAAA;AAAA,YACd,OAAA,EAAS,MAAM,YAAA,CAAa,CAAC,WAAW,CAAA;AAAA,YAExC,QAAA,kBAAA,GAAA,CAAC,SAAI,OAAA,EAAQ,WAAA,EAAY,OAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,aAAA,EAAY,MAAA,EAC1D,QAAA,kBAAA,GAAA;AAAA,cAAC,MAAA;AAAA,cAAA;AAAA,gBACC,CAAA,EAAG,cAAc,sBAAA,GAAyB,uBAAA;AAAA,gBAC1C,MAAA,EAAO,cAAA;AAAA,gBACP,WAAA,EAAY,KAAA;AAAA,gBACZ,IAAA,EAAK,MAAA;AAAA,gBACL,aAAA,EAAc,OAAA;AAAA,gBACd,cAAA,EAAe;AAAA;AAAA,aACjB,EACF;AAAA;AAAA,SACF,GACE;AAAA;AAAA;AAAA,GACN;AAEJ;AAUA,SAAS,YAAY,EAAE,IAAA,EAAM,UAAU,SAAA,EAAW,UAAA,EAAY,OAAM,EAAqB;AACvF,EAAA,MAAM,cAAc,OAAA,CAAQ,IAAA,CAAK,SAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAC,CAAA;AAC/D,EAAA,MAAM,cAAA,GAAiB,eAAe,IAAA,CAAK,KAAA,CAAO,KAAK,CAAC,KAAA,KAAU,KAAA,CAAM,EAAA,KAAO,QAAQ,CAAA;AAIvF,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,cAAc,CAAA;AAC/C,EAAA,MAAM,UAAU,KAAA,EAAM;AAEtB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,cAAA,UAAwB,IAAI,CAAA;AAAA,EAClC,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,KAAK,EAAA,KAAO,QAAA;AAE7B,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,oBAAA,EACZ,QAAA,EAAA;AAAA,sBAAA,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,SAAA,EAAW,EAAA;AAAA,YACT,oBAAA;AAAA,YACA,2BAAA;AAAA,YACA,cAAA,IAAkB,4BAAA;AAAA,YAClB;AAAA,WACF;AAAA,UACA,eAAA,EAAe,IAAA;AAAA,UACf,eAAA,EAAe,OAAA;AAAA,UACf,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,SAAS,MAAM,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,UAE/B,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,oBAAA,EAAsB,QAAA,EAAA,IAAA,CAAK,MAAK,CAAA,GAAU,IAAA;AAAA,4BACvE,GAAA,CAAC,UAAK,SAAA,EAAW,EAAA,CAAG,uBAAuB,SAAA,IAAa,sBAAsB,CAAA,EAC3E,QAAA,EAAA,IAAA,CAAK,KAAA,EACR,CAAA;AAAA,4BACA,GAAA;AAAA,cAAC,MAAA;AAAA,cAAA;AAAA,gBACC,SAAA,EAAW,EAAA,CAAG,uBAAA,EAAyB,IAAA,IAAQ,6BAA6B,CAAA;AAAA,gBAC5E,aAAA,EAAY,MAAA;AAAA,gBAEZ,8BAAC,KAAA,EAAA,EAAI,OAAA,EAAQ,aAAY,KAAA,EAAM,IAAA,EAAK,QAAO,IAAA,EACzC,QAAA,kBAAA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,CAAA,EAAE,kBAAA;AAAA,oBACF,MAAA,EAAO,cAAA;AAAA,oBACP,WAAA,EAAY,KAAA;AAAA,oBACZ,IAAA,EAAK,MAAA;AAAA,oBACL,aAAA,EAAc,OAAA;AAAA,oBACd,cAAA,EAAe;AAAA;AAAA,iBACjB,EACF;AAAA;AAAA;AACF;AAAA;AAAA,OACF;AAAA,sBAIA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,+CAAA,EAAgD,EAAA,EAAI,OAAA,EAAS,MAAA,EAAQ,CAAC,IAAA,EACjF,QAAA,EAAA,IAAA,CAAK,KAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBAChB,GAAA;AAAA,QAAC,WAAA;AAAA,QAAA;AAAA,UAEC,IAAA,EAAM,KAAA;AAAA,UACN,QAAA;AAAA,UACA,SAAA;AAAA,UACA,UAAA;AAAA,UACA,OAAO,KAAA,GAAQ;AAAA,SAAA;AAAA,QALV,KAAA,CAAM;AAAA,OAOd,CAAA,EACH;AAAA,KAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,GAAO,GAAA,GAAM,QAAA;AAEpC,EAAA,uBACE,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,oBAAA,EACZ,QAAA,kBAAA,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACE,GAAI,IAAA,CAAK,IAAA,GAAO,EAAE,IAAA,EAAM,KAAK,IAAA,EAAK,GAAI,EAAE,IAAA,EAAM,QAAA,EAAkB;AAAA,MACjE,SAAA,EAAW,EAAA;AAAA,QACT,oBAAA;AAAA,QACA,QAAA,IAAY,4BAAA;AAAA,QACZ;AAAA,OACF;AAAA,MAEA,cAAA,EAAc,WAAW,MAAA,GAAS,MAAA;AAAA,MAClC,eAAA,EAAe,KAAK,QAAA,IAAY,MAAA;AAAA,MAGhC,KAAA,EAAO,SAAA,GAAY,IAAA,CAAK,KAAA,GAAQ,MAAA;AAAA,MAChC,OAAA,EAAS,CAAC,KAAA,KAA4B;AACpC,QAAA,IAAI,KAAK,QAAA,EAAU;AACjB,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,OAAA,IAAU;AACf,QAAA,UAAA,GAAa,IAAI,CAAA;AAAA,MACnB,CAAA;AAAA,MAEC,QAAA,EAAA;AAAA,QAAA,IAAA,CAAK,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,oBAAA,EAAsB,QAAA,EAAA,IAAA,CAAK,MAAK,CAAA,GAAU,IAAA;AAAA,wBACvE,GAAA,CAAC,UAAK,SAAA,EAAW,EAAA,CAAG,uBAAuB,SAAA,IAAa,sBAAsB,CAAA,EAC3E,QAAA,EAAA,IAAA,CAAK,KAAA,EACR,CAAA;AAAA,QACC,IAAA,CAAK,KAAA,KAAU,MAAA,IAAa,CAAC,SAAA,mBAC5B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM,CAAA,GAChD;AAAA;AAAA;AAAA,GACN,EACF,CAAA;AAEJ;AAoBO,SAAS,SAAS,EAAE,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAU,WAAU,EAAkB;AAChF,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,YAAA,EAAc,QAAQ,OAAO,CAAA,IAAK,0BAAA,EAA4B,SAAS,CAAA,EACxF,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,IAAA,EAAK,YAAA,EAAa,SAAA,EAAU,kBAAiB,QAAA,EAAA,sBAAA,EAEhD,CAAA;AAAA,IACC,yBAAS,GAAA,CAAC,QAAA,EAAA,EAAO,SAAA,EAAU,oBAAA,EAAsB,kBAAO,CAAA,GAAY,IAAA;AAAA,IACpE,0BAAU,GAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,mBAAQ,CAAA,GAAW,IAAA;AAAA,oBACtE,GAAA,CAAC,UAAK,SAAA,EAAU,kBAAA,EAAmB,IAAG,WAAA,EAAY,QAAA,EAAU,IACzD,QAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ","file":"chunk-5CFQFMP5.js","sourcesContent":["import { type ReactNode, useEffect, useId, useState } from 'react'\nimport { cx } from '../../primitives/compose'\nimport { useControllableState } from '../../primitives/use-controllable-state'\n\nexport interface NavItem {\n id: string\n label: string\n href?: string\n icon?: ReactNode\n /** Count or status shown on the right. */\n badge?: ReactNode\n disabled?: boolean\n onClick?: () => void\n /** Nested items, rendered in a collapsible group. */\n items?: readonly NavItem[]\n}\n\nexport interface NavSection {\n id: string\n /** Heading above the group. Omit for an unlabelled block. */\n label?: string\n items: readonly NavItem[]\n}\n\nexport interface SidebarProps {\n sections: readonly NavSection[]\n /** Id of the active item. */\n activeId?: string\n onNavigate?: (item: NavItem) => void\n /** Collapse to an icon rail. */\n collapsed?: boolean\n defaultCollapsed?: boolean\n onCollapsedChange?: (collapsed: boolean) => void\n /** Content above the nav — a logo, a workspace switcher. */\n header?: ReactNode\n /** Content pinned to the bottom — the user menu, usually. */\n footer?: ReactNode\n /** Show the collapse toggle. Default `true`. */\n collapsible?: boolean\n label?: string\n className?: string\n}\n\n/**\n * The application sidebar.\n *\n * A `<nav>` containing lists of links — the structure a screen reader user navigates by landmark,\n * and the reason this isn't a stack of divs. Nested groups are `<button aria-expanded>` controlling\n * a `<ul>`, so their state is announced.\n *\n * Collapsing to an icon rail keeps the labels in the DOM (visually hidden), rather than removing\n * them. Removing them would leave a rail of unlabelled icons for anyone using assistive technology,\n * which is exactly the population for whom the space saving is irrelevant.\n */\nexport function Sidebar({\n sections,\n activeId,\n onNavigate,\n collapsed,\n defaultCollapsed = false,\n onCollapsedChange,\n header,\n footer,\n collapsible = true,\n label = 'Main',\n className,\n}: SidebarProps) {\n const [isCollapsed, setCollapsed] = useControllableState<boolean>({\n value: collapsed,\n defaultValue: defaultCollapsed,\n onChange: onCollapsedChange,\n })\n\n return (\n <nav\n className={cx('ansh-sidebar', isCollapsed && 'ansh-sidebar--collapsed', className)}\n aria-label={label}\n data-collapsed={isCollapsed || undefined}\n >\n {header ? <div className=\"ansh-sidebar__header\">{header}</div> : null}\n\n <div className=\"ansh-sidebar__scroll\">\n {sections.map((section) => (\n <div className=\"ansh-sidebar__section\" key={section.id}>\n {section.label ? (\n // Hidden visually when collapsed but kept for assistive technology, so the grouping\n // survives the rail layout.\n <h2 className={cx('ansh-sidebar__heading', isCollapsed && 'ansh-visually-hidden')}>\n {section.label}\n </h2>\n ) : null}\n\n <ul className=\"ansh-sidebar__list\">\n {section.items.map((item) => (\n <SidebarItem\n key={item.id}\n item={item}\n activeId={activeId}\n collapsed={isCollapsed}\n onNavigate={onNavigate}\n depth={0}\n />\n ))}\n </ul>\n </div>\n ))}\n </div>\n\n {footer ? <div className=\"ansh-sidebar__footer\">{footer}</div> : null}\n\n {collapsible ? (\n <button\n type=\"button\"\n className=\"ansh-sidebar__toggle ansh-focusable\"\n aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n aria-pressed={isCollapsed}\n onClick={() => setCollapsed(!isCollapsed)}\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path\n d={isCollapsed ? 'M6 3.5L10.5 8L6 12.5' : 'M10 3.5L5.5 8L10 12.5'}\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n ) : null}\n </nav>\n )\n}\n\ninterface SidebarItemProps {\n item: NavItem\n activeId: string | undefined\n collapsed: boolean\n onNavigate: ((item: NavItem) => void) | undefined\n depth: number\n}\n\nfunction SidebarItem({ item, activeId, collapsed, onNavigate, depth }: SidebarItemProps) {\n const hasChildren = Boolean(item.items && item.items.length > 0)\n const containsActive = hasChildren && item.items!.some((child) => child.id === activeId)\n\n // Groups start open when they contain the active page — landing on a nested route with the group\n // collapsed leaves the user with no visible indication of where they are.\n const [open, setOpen] = useState(containsActive)\n const panelId = useId()\n\n useEffect(() => {\n if (containsActive) setOpen(true)\n }, [containsActive])\n\n const isActive = item.id === activeId\n\n if (hasChildren) {\n return (\n <li className=\"ansh-sidebar__item\">\n <button\n type=\"button\"\n className={cx(\n 'ansh-sidebar__link',\n 'ansh-sidebar__link--group',\n containsActive && 'ansh-sidebar__link--within',\n 'ansh-focusable',\n )}\n aria-expanded={open}\n aria-controls={panelId}\n disabled={item.disabled}\n onClick={() => setOpen((v) => !v)}\n >\n {item.icon ? <span className=\"ansh-sidebar__icon\">{item.icon}</span> : null}\n <span className={cx('ansh-sidebar__label', collapsed && 'ansh-visually-hidden')}>\n {item.label}\n </span>\n <span\n className={cx('ansh-sidebar__chevron', open && 'ansh-sidebar__chevron--open')}\n aria-hidden=\"true\"\n >\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path\n d=\"M3 4.5L6 7.5l3-3\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </span>\n </button>\n\n {/* `hidden` rather than unmounting: the group's open state is cheap to keep, and\n unmounting would lose scroll position within a long nested list. */}\n <ul className=\"ansh-sidebar__list ansh-sidebar__list--nested\" id={panelId} hidden={!open}>\n {item.items!.map((child) => (\n <SidebarItem\n key={child.id}\n item={child}\n activeId={activeId}\n collapsed={collapsed}\n onNavigate={onNavigate}\n depth={depth + 1}\n />\n ))}\n </ul>\n </li>\n )\n }\n\n const Component = item.href ? 'a' : 'button'\n\n return (\n <li className=\"ansh-sidebar__item\">\n <Component\n {...(item.href ? { href: item.href } : { type: 'button' as const })}\n className={cx(\n 'ansh-sidebar__link',\n isActive && 'ansh-sidebar__link--active',\n 'ansh-focusable',\n )}\n // The current page, announced. A colour change alone conveys nothing.\n aria-current={isActive ? 'page' : undefined}\n aria-disabled={item.disabled || undefined}\n // A collapsed rail shows only icons, so the label becomes the accessible name via `title`\n // for pointer users and stays in the DOM for everyone else.\n title={collapsed ? item.label : undefined}\n onClick={(event: React.MouseEvent) => {\n if (item.disabled) {\n event.preventDefault()\n return\n }\n item.onClick?.()\n onNavigate?.(item)\n }}\n >\n {item.icon ? <span className=\"ansh-sidebar__icon\">{item.icon}</span> : null}\n <span className={cx('ansh-sidebar__label', collapsed && 'ansh-visually-hidden')}>\n {item.label}\n </span>\n {item.badge !== undefined && !collapsed ? (\n <span className=\"ansh-sidebar__badge\">{item.badge}</span>\n ) : null}\n </Component>\n </li>\n )\n}\n\nexport interface AppShellProps {\n sidebar?: ReactNode\n header?: ReactNode\n children: ReactNode\n className?: string\n}\n\n/**\n * The header + sidebar + content frame.\n *\n * A grid rather than nested flex containers, so the sidebar and header can be `position: sticky`\n * without the content needing compensating margins — the arrangement that breaks the moment the\n * header's height changes.\n *\n * The skip link is the first focusable element on the page. It is the cheapest accessibility win\n * available and it is missing from almost every application shell: without it, a keyboard user\n * traverses the entire navigation on every single page load.\n */\nexport function AppShell({ sidebar, header, children, className }: AppShellProps) {\n return (\n <div className={cx('ansh-shell', Boolean(sidebar) && 'ansh-shell--with-sidebar', className)}>\n <a href=\"#ansh-main\" className=\"ansh-skip-link\">\n Skip to main content\n </a>\n {header ? <header className=\"ansh-shell__header\">{header}</header> : null}\n {sidebar ? <aside className=\"ansh-shell__sidebar\">{sidebar}</aside> : null}\n <main className=\"ansh-shell__main\" id=\"ansh-main\" tabIndex={-1}>\n {children}\n </main>\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var core = require('@ansh-ui/core');
|
|
5
|
+
|
|
6
|
+
// src/hooks/use-observers.ts
|
|
7
|
+
function useTicker(callback, options = {}) {
|
|
8
|
+
const callbackRef = react.useRef(callback);
|
|
9
|
+
callbackRef.current = callback;
|
|
10
|
+
const { phase = "write", priority = 0, enabled = true } = options;
|
|
11
|
+
react.useEffect(() => {
|
|
12
|
+
if (!enabled) return;
|
|
13
|
+
const ticker = core.getTicker();
|
|
14
|
+
return ticker.add((state) => callbackRef.current(state), { phase, priority, name: "useTicker" });
|
|
15
|
+
}, [phase, priority, enabled]);
|
|
16
|
+
}
|
|
17
|
+
function useInView(options = {}) {
|
|
18
|
+
const ref = react.useRef(null);
|
|
19
|
+
const [inView, setInView] = react.useState(false);
|
|
20
|
+
const { threshold = 0, rootMargin = "0px", once = false, root = null } = options;
|
|
21
|
+
react.useEffect(() => {
|
|
22
|
+
const element = ref.current;
|
|
23
|
+
if (!element) return;
|
|
24
|
+
let released = false;
|
|
25
|
+
const stop = core.observeIntersection(
|
|
26
|
+
element,
|
|
27
|
+
(entry) => {
|
|
28
|
+
setInView(entry.isIntersecting);
|
|
29
|
+
if (entry.isIntersecting && once && !released) {
|
|
30
|
+
released = true;
|
|
31
|
+
queueMicrotask(() => stop());
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{ threshold, rootMargin, root }
|
|
35
|
+
);
|
|
36
|
+
return () => {
|
|
37
|
+
released = true;
|
|
38
|
+
stop();
|
|
39
|
+
};
|
|
40
|
+
}, [threshold, rootMargin, once, root]);
|
|
41
|
+
return [ref, inView];
|
|
42
|
+
}
|
|
43
|
+
function useElementSize() {
|
|
44
|
+
const ref = react.useRef(null);
|
|
45
|
+
const [size, setSize] = react.useState({ width: 0, height: 0 });
|
|
46
|
+
react.useEffect(() => {
|
|
47
|
+
const element = ref.current;
|
|
48
|
+
if (!element) return;
|
|
49
|
+
return core.observeResize(element, ({ width, height }) => {
|
|
50
|
+
setSize(
|
|
51
|
+
(previous) => previous.width === width && previous.height === height ? previous : { width, height }
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}, []);
|
|
55
|
+
return [ref, size];
|
|
56
|
+
}
|
|
57
|
+
function useScrollProgress(options = {}) {
|
|
58
|
+
const ref = react.useRef(null);
|
|
59
|
+
const progressRef = react.useRef({
|
|
60
|
+
progress: 0,
|
|
61
|
+
velocity: 0,
|
|
62
|
+
scroll: 0,
|
|
63
|
+
direction: 0,
|
|
64
|
+
inView: false
|
|
65
|
+
});
|
|
66
|
+
const onChangeRef = react.useRef(options.onChange);
|
|
67
|
+
onChangeRef.current = options.onChange;
|
|
68
|
+
const { axis = "y", container = null } = options;
|
|
69
|
+
const offsetKey = JSON.stringify(options.offset ?? null);
|
|
70
|
+
react.useEffect(() => {
|
|
71
|
+
const element = ref.current;
|
|
72
|
+
if (!element) return;
|
|
73
|
+
return core.trackScroll(
|
|
74
|
+
element,
|
|
75
|
+
(progress) => {
|
|
76
|
+
progressRef.current = progress;
|
|
77
|
+
onChangeRef.current?.(progress);
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
axis,
|
|
81
|
+
container,
|
|
82
|
+
...options.offset ? { offset: options.offset } : {}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}, [axis, container, offsetKey]);
|
|
86
|
+
return [ref, progressRef];
|
|
87
|
+
}
|
|
88
|
+
function useWindowScroll(onChange, options = {}) {
|
|
89
|
+
const stateRef = react.useRef({ progress: 0, scroll: 0, velocity: 0, max: 1 });
|
|
90
|
+
const onChangeRef = react.useRef(onChange);
|
|
91
|
+
onChangeRef.current = onChange;
|
|
92
|
+
const { axis = "y", container = null } = options;
|
|
93
|
+
react.useEffect(
|
|
94
|
+
() => core.trackContainerScroll(
|
|
95
|
+
(info) => {
|
|
96
|
+
stateRef.current = info;
|
|
97
|
+
onChangeRef.current?.(info);
|
|
98
|
+
},
|
|
99
|
+
{ axis, container }
|
|
100
|
+
),
|
|
101
|
+
[axis, container]
|
|
102
|
+
);
|
|
103
|
+
return stateRef;
|
|
104
|
+
}
|
|
105
|
+
function usePointer(options = {}) {
|
|
106
|
+
const ref = react.useRef(null);
|
|
107
|
+
const stateRef = react.useRef(null);
|
|
108
|
+
const { smoothing, trackOutside = false, enabled = true } = options;
|
|
109
|
+
react.useEffect(() => {
|
|
110
|
+
const element = ref.current;
|
|
111
|
+
if (!element || !enabled) return;
|
|
112
|
+
const tracker = core.trackPointer(element, {
|
|
113
|
+
...smoothing !== void 0 ? { smoothing } : {},
|
|
114
|
+
trackOutside
|
|
115
|
+
});
|
|
116
|
+
stateRef.current = tracker.state;
|
|
117
|
+
return () => {
|
|
118
|
+
tracker.destroy();
|
|
119
|
+
stateRef.current = null;
|
|
120
|
+
};
|
|
121
|
+
}, [smoothing, trackOutside, enabled]);
|
|
122
|
+
return [ref, stateRef];
|
|
123
|
+
}
|
|
124
|
+
function useReducedMotion() {
|
|
125
|
+
const [reduced, setReduced] = react.useState(false);
|
|
126
|
+
react.useEffect(() => core.onReducedMotionChange(setReduced), []);
|
|
127
|
+
return reduced;
|
|
128
|
+
}
|
|
129
|
+
function useFrameBudget(enabled = true) {
|
|
130
|
+
const [report, setReport] = react.useState(null);
|
|
131
|
+
react.useEffect(() => {
|
|
132
|
+
if (!enabled) return;
|
|
133
|
+
const ticker = core.getTicker();
|
|
134
|
+
let last = 0;
|
|
135
|
+
return ticker.onBudget((next) => {
|
|
136
|
+
const now = performance.now();
|
|
137
|
+
if (now - last < 250) return;
|
|
138
|
+
last = now;
|
|
139
|
+
setReport(next);
|
|
140
|
+
});
|
|
141
|
+
}, [enabled]);
|
|
142
|
+
return report;
|
|
143
|
+
}
|
|
144
|
+
function useMergedRef(...refs) {
|
|
145
|
+
return react.useCallback((node) => {
|
|
146
|
+
for (const ref of refs) {
|
|
147
|
+
if (!ref) continue;
|
|
148
|
+
if (typeof ref === "function") ref(node);
|
|
149
|
+
else ref.current = node;
|
|
150
|
+
}
|
|
151
|
+
}, refs);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
exports.useElementSize = useElementSize;
|
|
155
|
+
exports.useFrameBudget = useFrameBudget;
|
|
156
|
+
exports.useInView = useInView;
|
|
157
|
+
exports.useMergedRef = useMergedRef;
|
|
158
|
+
exports.usePointer = usePointer;
|
|
159
|
+
exports.useReducedMotion = useReducedMotion;
|
|
160
|
+
exports.useScrollProgress = useScrollProgress;
|
|
161
|
+
exports.useTicker = useTicker;
|
|
162
|
+
exports.useWindowScroll = useWindowScroll;
|
|
163
|
+
//# sourceMappingURL=chunk-6VRF7X5C.cjs.map
|
|
164
|
+
//# sourceMappingURL=chunk-6VRF7X5C.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-observers.ts"],"names":["useRef","useEffect","getTicker","useState","observeIntersection","observeResize","trackScroll","trackContainerScroll","trackPointer","onReducedMotionChange","useCallback"],"mappings":"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","file":"chunk-6VRF7X5C.cjs","sourcesContent":["import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'\nimport {\n type BudgetReport,\n type FrameState,\n type PointerState,\n type ScrollProgress,\n type ScrollTrackOptions,\n getTicker,\n observeIntersection,\n observeResize,\n onReducedMotionChange,\n trackContainerScroll,\n trackPointer,\n trackScroll,\n} from '@ansh-ui/core'\n\n/**\n * Subscribe to the shared ticker.\n *\n * The callback is held in a ref and never re-subscribes, so a component that closes over changing\n * props doesn't tear down and rebuild its subscription on every render. That matters: resubscribing\n * changes execution order within the phase, which can visibly reorder dependent animations.\n */\nexport function useTicker(\n callback: (state: FrameState) => void,\n options: {\n phase?: 'read' | 'compute' | 'write' | 'post'\n priority?: number\n enabled?: boolean\n } = {},\n): void {\n const callbackRef = useRef(callback)\n callbackRef.current = callback\n\n const { phase = 'write', priority = 0, enabled = true } = options\n\n useEffect(() => {\n if (!enabled) return\n const ticker = getTicker()\n return ticker.add((state) => callbackRef.current(state), { phase, priority, name: 'useTicker' })\n }, [phase, priority, enabled])\n}\n\nexport interface InViewOptions {\n /** Fraction visible before triggering, in `[0, 1]`. Default `0`. */\n threshold?: number\n /** Extra margin around the root. Default `'0px'`. */\n rootMargin?: string\n /** Stop observing after the first intersection. Default `false`. */\n once?: boolean\n root?: Element | null\n}\n\n/**\n * Is the element in the viewport?\n *\n * Uses the pooled observer, so a page with 200 scroll-revealed elements creates one\n * `IntersectionObserver` rather than 200.\n */\nexport function useInView<El extends Element = HTMLDivElement>(\n options: InViewOptions = {},\n): [RefObject<El | null>, boolean] {\n const ref = useRef<El | null>(null)\n const [inView, setInView] = useState(false)\n\n const { threshold = 0, rootMargin = '0px', once = false, root = null } = options\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n\n let released = false\n const stop = observeIntersection(\n element,\n (entry) => {\n setInView(entry.isIntersecting)\n if (entry.isIntersecting && once && !released) {\n released = true\n // Release inside a microtask: unsubscribing from within the observer callback would\n // mutate the pool's callback set while it is being iterated.\n queueMicrotask(() => stop())\n }\n },\n { threshold, rootMargin, root },\n )\n\n return () => {\n released = true\n stop()\n }\n }, [threshold, rootMargin, once, root])\n\n return [ref, inView]\n}\n\n/**\n * Track an element's size.\n *\n * Returns state rather than a MotionValue because size changes at interaction rate, not frame\n * rate, so a re-render per change is appropriate and far more ergonomic.\n */\nexport function useElementSize<El extends Element = HTMLDivElement>(): [\n RefObject<El | null>,\n { width: number; height: number },\n] {\n const ref = useRef<El | null>(null)\n const [size, setSize] = useState({ width: 0, height: 0 })\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n return observeResize(element, ({ width, height }) => {\n // Guard against redundant renders: ResizeObserver fires on sub-pixel changes during\n // animation, and setting identical state still costs a render pass in React 18.\n setSize((previous) =>\n previous.width === width && previous.height === height ? previous : { width, height },\n )\n })\n }, [])\n\n return [ref, size]\n}\n\n/**\n * Track an element's scroll progress through the viewport.\n *\n * The returned progress is a **ref**, not state, deliberately — scroll progress changes every\n * frame, and rendering on each change would make scrolling janky. Read `progress.current` inside a\n * `useTicker` callback, or pass the `onChange` callback and write to the DOM directly.\n */\nexport function useScrollProgress<El extends HTMLElement = HTMLDivElement>(\n options: ScrollTrackOptions & { onChange?: (progress: ScrollProgress) => void } = {},\n): [RefObject<El | null>, RefObject<ScrollProgress>] {\n const ref = useRef<El | null>(null)\n const progressRef = useRef<ScrollProgress>({\n progress: 0,\n velocity: 0,\n scroll: 0,\n direction: 0,\n inView: false,\n })\n\n const onChangeRef = useRef(options.onChange)\n onChangeRef.current = options.onChange\n\n const { axis = 'y', container = null } = options\n const offsetKey = JSON.stringify(options.offset ?? null)\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n\n return trackScroll(\n element,\n (progress) => {\n progressRef.current = progress\n onChangeRef.current?.(progress)\n },\n {\n axis,\n container,\n ...(options.offset ? { offset: options.offset } : {}),\n },\n )\n // `offsetKey` stands in for the offset object, which is usually an inline literal and would\n // otherwise change identity on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [axis, container, offsetKey])\n\n return [ref, progressRef]\n}\n\n/** Track the window's (or a container's) overall scroll progress. */\nexport function useWindowScroll(\n onChange?: (info: { progress: number; scroll: number; velocity: number; max: number }) => void,\n options: { axis?: 'y' | 'x'; container?: HTMLElement | null } = {},\n): RefObject<{ progress: number; scroll: number; velocity: number; max: number }> {\n const stateRef = useRef({ progress: 0, scroll: 0, velocity: 0, max: 1 })\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n\n const { axis = 'y', container = null } = options\n\n useEffect(\n () =>\n trackContainerScroll(\n (info) => {\n stateRef.current = info\n onChangeRef.current?.(info)\n },\n { axis, container },\n ),\n [axis, container],\n )\n\n return stateRef\n}\n\n/**\n * Track pointer position over an element.\n *\n * Returns a ref to the live state object, which is mutated in place by the tracker. Reading it\n * inside a `useTicker` callback is the intended use; it deliberately does not trigger renders.\n */\nexport function usePointer<El extends HTMLElement = HTMLDivElement>(\n options: { smoothing?: number; trackOutside?: boolean; enabled?: boolean } = {},\n): [RefObject<El | null>, RefObject<Readonly<PointerState> | null>] {\n const ref = useRef<El | null>(null)\n const stateRef = useRef<Readonly<PointerState> | null>(null)\n\n const { smoothing, trackOutside = false, enabled = true } = options\n\n useEffect(() => {\n const element = ref.current\n if (!element || !enabled) return\n\n const tracker = trackPointer(element, {\n ...(smoothing !== undefined ? { smoothing } : {}),\n trackOutside,\n })\n stateRef.current = tracker.state\n\n return () => {\n tracker.destroy()\n stateRef.current = null\n }\n }, [smoothing, trackOutside, enabled])\n\n return [ref, stateRef]\n}\n\n/**\n * Does the user prefer reduced motion?\n *\n * Returns `false` during SSR and on the first client render, then updates. That ordering is\n * deliberate: returning the real value during SSR is impossible (the server cannot know), and\n * returning it on the first client render would be a hydration mismatch.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n useEffect(() => onReducedMotionChange(setReduced), [])\n return reduced\n}\n\n/**\n * Live frame-budget telemetry — FPS, frame cost percentiles, and the adaptive quality scalar.\n *\n * Intended for a development overlay. Subscribing causes a re-render every frame, so this must not\n * ship in production render paths; the `enabled` flag exists so it can be gated on `NODE_ENV`.\n */\nexport function useFrameBudget(enabled = true): BudgetReport | null {\n const [report, setReport] = useState<BudgetReport | null>(null)\n\n useEffect(() => {\n if (!enabled) return\n const ticker = getTicker()\n // Sample at ~4 Hz rather than per frame. A number that changes 60 times a second is\n // unreadable, and rendering it 60 times a second distorts the very measurement being shown.\n let last = 0\n return ticker.onBudget((next) => {\n const now = performance.now()\n if (now - last < 250) return\n last = now\n setReport(next)\n })\n }, [enabled])\n\n return report\n}\n\n/** Merge several refs into one callback ref, for components that both forward and use a ref. */\nexport function useMergedRef<T>(\n ...refs: Array<React.Ref<T> | undefined | null>\n): (node: T | null) => void {\n return useCallback((node: T | null) => {\n for (const ref of refs) {\n if (!ref) continue\n if (typeof ref === 'function') ref(node)\n else (ref as React.MutableRefObject<T | null>).current = node\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, refs)\n}\n"]}
|