@mario_hahn/jumpy-ui 0.14.0 → 0.14.1

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.
@@ -0,0 +1,4304 @@
1
+ import {
2
+ JumpyAnimationDuration,
3
+ JumpyBorderWidth,
4
+ JumpyOpacity,
5
+ JumpyRadius,
6
+ JumpyShadowStyle,
7
+ JumpySize,
8
+ JumpySpacing,
9
+ JumpyTextStyle,
10
+ JumpyThemeScopeContext,
11
+ animationDurationValue,
12
+ borderWidthValue,
13
+ opacityValue,
14
+ radiusValue,
15
+ shadowValue,
16
+ sizeValue,
17
+ spacingValue,
18
+ textStyleValue,
19
+ themeToCSSVariables,
20
+ useJumpyTheme
21
+ } from "./chunk-KDFNGI6Y.js";
22
+ import {
23
+ cn
24
+ } from "./chunk-3DBKYKP2.js";
25
+
26
+ // components.js
27
+ import { autoUpdate, flip, FloatingFocusManager, FloatingOverlay, FloatingPortal, offset, shift, size, useFloating } from "@floating-ui/react";
28
+ import { Children, cloneElement, createContext as createContext2, forwardRef, isValidElement, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
29
+
30
+ // field-context.js
31
+ import { createContext } from "react";
32
+ var JumpyFieldContext = createContext(null);
33
+
34
+ // components.js
35
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
36
+ var JumpyTableContext = createContext2(null);
37
+ var JumpyExpandableContext = createContext2(null);
38
+ var JumpySideMenuContext = createContext2(null);
39
+ var JumpyToggleButtonGroupContext = createContext2(null);
40
+ var JumpySnackbarContext = createContext2({ addSnackbar: () => "", removeSnackbar: () => {
41
+ }, clearAll: () => {
42
+ } });
43
+ var KeyboardKeys = {
44
+ ARROW_DOWN: "ArrowDown",
45
+ ARROW_LEFT: "ArrowLeft",
46
+ ARROW_RIGHT: "ArrowRight",
47
+ ARROW_UP: "ArrowUp",
48
+ END: "End",
49
+ ENTER: "Enter",
50
+ ESCAPE: "Escape",
51
+ HOME: "Home",
52
+ SPACE: " ",
53
+ TAB: "Tab"
54
+ };
55
+ function ChevronDownIcon(props) {
56
+ const theme = useJumpyTheme();
57
+ return /* @__PURE__ */ jsx("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fontSize: sizeValue(theme, JumpySize.small), ...props.style || {} }, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M7.41 8.295 6 9.705l6 6 6-6-1.41-1.41-4.59 4.58z", clipRule: "evenodd", fill: "currentColor" }) });
58
+ }
59
+ function ChevronUpIcon(props) {
60
+ const theme = useJumpyTheme();
61
+ return /* @__PURE__ */ jsx("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fontSize: sizeValue(theme, JumpySize.small), ...props.style || {} }, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "m16.59 15.705 1.41-1.41-6-6-6 6 1.41 1.41 4.59-4.58z", clipRule: "evenodd", fill: "currentColor" }) });
62
+ }
63
+ function CircleConfirmIcon(props) {
64
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, children: /* @__PURE__ */ jsx("path", { fill: "currentColor", d: "M10 1.667a8.333 8.333 0 1 0 0 16.666 8.333 8.333 0 0 0 0-16.666Zm3.775 6.441-4.167 4.167a.833.833 0 0 1-1.178 0l-2.083-2.083a.833.833 0 1 1 1.178-1.179l1.494 1.494 3.577-3.577a.833.833 0 0 1 1.179 1.178Z" }) });
65
+ }
66
+ function CircleErrorIcon(props) {
67
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, children: /* @__PURE__ */ jsx("path", { fill: "currentColor", d: "M10 1.667a8.333 8.333 0 1 0 0 16.666A8.333 8.333 0 0 0 10 1.667Zm0 4.166c.46 0 .833.373.833.834v3.75a.833.833 0 0 1-1.666 0v-3.75c0-.46.373-.834.833-.834Zm0 8.75a1.042 1.042 0 1 1 0-2.083 1.042 1.042 0 0 1 0 2.083Z" }) });
68
+ }
69
+ function CircleClearIcon(props) {
70
+ return /* @__PURE__ */ jsxs("svg", { "data-jumpy-stroke-icon": "true", width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fill: "none", ...props.style || {} }, children: [
71
+ /* @__PURE__ */ jsx("path", { d: "M10 17.5a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
72
+ /* @__PURE__ */ jsx("path", { d: "m7.5 7.5 5 5M12.5 7.5l-5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
73
+ ] });
74
+ }
75
+ function supportsDisabledAttribute(tag) {
76
+ return ["button", "input", "select", "textarea", "option", "optgroup", "fieldset"].includes(tag);
77
+ }
78
+ function responsiveValue(value, defaults) {
79
+ if (value && typeof value === "object" && !Array.isArray(value)) {
80
+ return {
81
+ small: value.small ?? defaults.small,
82
+ medium: value.medium ?? value.small ?? defaults.medium,
83
+ large: value.large ?? value.medium ?? value.small ?? defaults.large,
84
+ xLarge: value.xLarge ?? value.large ?? value.medium ?? value.small ?? defaults.xLarge
85
+ };
86
+ }
87
+ return { small: value, medium: value, large: value, xLarge: value };
88
+ }
89
+ function gridSpacingValue(spacing, fallback = "1rem") {
90
+ const length = (value2) => typeof value2 === "number" ? `${value2}px` : value2;
91
+ if (Array.isArray(spacing)) return [length(spacing[0] ?? fallback), length(spacing[1] ?? fallback)];
92
+ const value = length(spacing ?? fallback);
93
+ return [value, value];
94
+ }
95
+ function responsiveGridSpacing(spacing) {
96
+ const defaults = { small: ["1rem", "1rem"], medium: ["1rem", "1rem"], large: ["1.5rem", "1.5rem"], xLarge: ["1.5rem", "1.5rem"] };
97
+ if (spacing == null || typeof spacing !== "object" || Array.isArray(spacing)) {
98
+ return {
99
+ small: gridSpacingValue(spacing ?? defaults.small, defaults.small[0]),
100
+ medium: gridSpacingValue(spacing ?? defaults.medium, defaults.medium[0]),
101
+ large: gridSpacingValue(spacing ?? defaults.large, defaults.large[0]),
102
+ xLarge: gridSpacingValue(spacing ?? defaults.xLarge, defaults.xLarge[0])
103
+ };
104
+ }
105
+ return {
106
+ small: gridSpacingValue(spacing.small ?? defaults.small, defaults.small[0]),
107
+ medium: gridSpacingValue(spacing.medium ?? spacing.small ?? defaults.medium, defaults.medium[0]),
108
+ large: gridSpacingValue(spacing.large ?? spacing.medium ?? spacing.small ?? defaults.large, defaults.large[0]),
109
+ xLarge: gridSpacingValue(spacing.xLarge ?? spacing.large ?? spacing.medium ?? spacing.small ?? defaults.xLarge, defaults.xLarge[0])
110
+ };
111
+ }
112
+ function isValueSelected(selectedValues, value) {
113
+ return value !== void 0 && Array.isArray(selectedValues) && selectedValues.includes(value);
114
+ }
115
+ function hasSelectedNestedItem(node, selectedValues) {
116
+ if (!Array.isArray(selectedValues) || !node) return false;
117
+ let found = false;
118
+ Children.forEach(node, (child) => {
119
+ if (found || !isValidElement(child)) return;
120
+ if (isValueSelected(selectedValues, child.props.value)) {
121
+ found = true;
122
+ return;
123
+ }
124
+ const nestedMenu = child.props.slots?.nestedMenu ?? child.props.nestedMenu;
125
+ if (hasSelectedNestedItem(nestedMenu ?? child.props.children, selectedValues)) found = true;
126
+ });
127
+ return found;
128
+ }
129
+ function focusSideMenuItem(itemRefs, direction) {
130
+ const items = itemRefs.map((ref) => ref?.current).filter((item) => item && item.dataset.disabled !== "true");
131
+ if (!items.length) return;
132
+ const currentIndex = Math.max(0, items.findIndex((item) => item === document.activeElement || item.contains(document.activeElement)));
133
+ const nextIndex = direction === "first" ? 0 : direction === "last" ? items.length - 1 : (currentIndex + direction + items.length) % items.length;
134
+ items[nextIndex]?.focus();
135
+ }
136
+ function transitionTimeToMs(value) {
137
+ return value.split(",").map((part) => part.trim()).reduce((max, part) => {
138
+ if (!part) return max;
139
+ const ms = part.endsWith("ms") ? Number.parseFloat(part) : Number.parseFloat(part) * 1e3;
140
+ return Number.isFinite(ms) ? Math.max(max, ms) : max;
141
+ }, 0);
142
+ }
143
+ function getWidthTransitionMs(element) {
144
+ const styles = window.getComputedStyle(element);
145
+ const properties = styles.transitionProperty.split(",").map((part) => part.trim());
146
+ const widthIndex = properties.findIndex((property) => property === "width" || property === "all");
147
+ if (widthIndex === -1) return 0;
148
+ return transitionTimeToMs(styles.transitionDuration) + transitionTimeToMs(styles.transitionDelay);
149
+ }
150
+ function splitNavigationItemProps(props) {
151
+ const linkKeys = /* @__PURE__ */ new Set(["href", "target", "rel", "download", "hrefLang", "ping", "referrerPolicy", "aria-current", "aria-disabled"]);
152
+ const wrapperProps = {};
153
+ const linkProps = {};
154
+ Object.entries(props).forEach(([key, value]) => {
155
+ if (linkKeys.has(key)) {
156
+ linkProps[key] = value;
157
+ return;
158
+ }
159
+ wrapperProps[key] = value;
160
+ });
161
+ return { wrapperProps, linkProps };
162
+ }
163
+ function clampNumber(value, min, max) {
164
+ return Math.min(Math.max(value, min), max);
165
+ }
166
+ function getButtonVariantStyles(theme, variant, status = "regular") {
167
+ const variantTokens = theme.actions[variant] || theme.actions.primary;
168
+ const tokens = variantTokens[status] || variantTokens.regular;
169
+ const elevated = variant === "primary";
170
+ const token = (state, slot, fallback) => variant === "primary" && status === "regular" ? `var(--jumpy-actions-primary-regular-${state}-${slot}, ${fallback})` : fallback;
171
+ return {
172
+ "--jumpy-action-default-surface": token("default", "surface", tokens.default.surface),
173
+ "--jumpy-action-default-text": token("default", "text", tokens.default.text),
174
+ "--jumpy-action-default-border": token("default", "border", tokens.default.border),
175
+ "--jumpy-action-default-icon": token("default", "icon", tokens.default.icon),
176
+ "--jumpy-action-hover-surface": token("hover", "surface", tokens.hover.surface),
177
+ "--jumpy-action-hover-text": token("hover", "text", tokens.hover.text),
178
+ "--jumpy-action-hover-icon": token("hover", "icon", tokens.hover.icon),
179
+ "--jumpy-action-hover-border": token("hover", "border", tokens.hover.border),
180
+ "--jumpy-action-pressed-surface": token("pressed", "surface", tokens.pressed.surface),
181
+ "--jumpy-action-pressed-text": token("pressed", "text", tokens.pressed.text),
182
+ "--jumpy-action-pressed-icon": token("pressed", "icon", tokens.pressed.icon),
183
+ "--jumpy-action-pressed-border": token("pressed", "border", tokens.pressed.border),
184
+ "--jumpy-action-focus-surface": token("focus", "surface", tokens.focus.surface),
185
+ "--jumpy-action-focus-text": token("focus", "text", tokens.focus.text),
186
+ "--jumpy-action-focus-icon": token("focus", "icon", tokens.focus.icon),
187
+ "--jumpy-action-focus-border": token("focus", "border", tokens.focus.border),
188
+ "--jumpy-action-focus-ring": token("focus", "focus-ring", tokens.focus.focusRing),
189
+ "--jumpy-action-disabled-surface": token("disabled", "surface", tokens.disabled.surface),
190
+ "--jumpy-action-disabled-text": token("disabled", "text", tokens.disabled.text),
191
+ "--jumpy-action-disabled-icon": token("disabled", "icon", tokens.disabled.icon),
192
+ "--jumpy-action-disabled-border": token("disabled", "border", tokens.disabled.border),
193
+ "--jumpy-action-hover-shadow": "none",
194
+ "--jumpy-action-focus-shadow": elevated ? shadowValue(theme, JumpyShadowStyle.positiveSmall) : "none",
195
+ "--jumpy-action-pressed-shadow": "none",
196
+ "--jumpy-action-default-shadow": elevated ? shadowValue(theme, JumpyShadowStyle.positiveMedium) : "none"
197
+ };
198
+ }
199
+ function getNoticeToneStyles(theme, tone) {
200
+ switch (tone) {
201
+ case "success":
202
+ return {
203
+ backgroundColor: theme.color.successSurface,
204
+ borderColor: theme.color.successBorder,
205
+ color: theme.color.successText
206
+ };
207
+ case "warning":
208
+ return {
209
+ backgroundColor: theme.color.warningSurface,
210
+ borderColor: theme.color.warningBorder,
211
+ color: theme.color.warningText
212
+ };
213
+ case "danger":
214
+ return {
215
+ backgroundColor: theme.color.dangerSurface,
216
+ borderColor: theme.color.dangerBorder,
217
+ color: theme.color.dangerText
218
+ };
219
+ default:
220
+ return {
221
+ backgroundColor: theme.color.infoSurface,
222
+ borderColor: theme.color.infoBorder,
223
+ color: theme.color.textPrimary
224
+ };
225
+ }
226
+ }
227
+ function getBannerStatusStyles(theme, status) {
228
+ switch (status) {
229
+ case "positive":
230
+ return { indicator: theme.color.positiveBase, indicatorSurface: theme.color.positiveUltralight, inlineSurface: theme.color.positiveExtralight };
231
+ case "warning":
232
+ return { indicator: theme.color.warningBase, indicatorSurface: theme.color.warningUltralight, inlineSurface: theme.color.warningExtralight };
233
+ case "alarm":
234
+ return { indicator: theme.color.alarmBase, indicatorSurface: theme.color.alarmUltralight, inlineSurface: theme.color.alarmExtralight };
235
+ case "info":
236
+ return { indicator: theme.color.neutralBase, indicatorSurface: theme.color.neutralMegalight, inlineSurface: `rgba(244, 247, 245, ${opacityValue(theme, JumpyOpacity.medium)})` };
237
+ default:
238
+ return { indicator: theme.color.neutralBase, indicatorSurface: "transparent", inlineSurface: "transparent" };
239
+ }
240
+ }
241
+ function JumpSeverityIcon({ status = "other", icon, style }) {
242
+ if (status === "other" && icon) return icon;
243
+ const common = {
244
+ viewBox: "0 0 24 24",
245
+ width: "100%",
246
+ height: "100%",
247
+ fill: "currentColor",
248
+ xmlns: "http://www.w3.org/2000/svg",
249
+ "aria-hidden": true,
250
+ focusable: false,
251
+ style
252
+ };
253
+ switch (status) {
254
+ case "positive":
255
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm4.3 7.8-5 5a1 1 0 0 1-1.4 0l-2.2-2.2a1 1 0 1 1 1.4-1.4l1.5 1.5 4.3-4.3a1 1 0 1 1 1.4 1.4Z" }) });
256
+ case "warning":
257
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M11.1 3.5a1 1 0 0 1 1.8 0l8.2 15A1 1 0 0 1 20.2 20H3.8a1 1 0 0 1-.9-1.5l8.2-15ZM12 8a1 1 0 0 0-1 1v4a1 1 0 1 0 2 0V9a1 1 0 0 0-1-1Zm0 9.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" }) });
258
+ case "alarm":
259
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm1 13a1 1 0 1 1-2 0V7a1 1 0 1 1 2 0v8Zm-1 4a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z" }) });
260
+ case "info":
261
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm1 15a1 1 0 1 1-2 0v-6a1 1 0 1 1 2 0v6Zm-1-8.5A1.25 1.25 0 1 1 12 6a1.25 1.25 0 0 1 0 2.5Z" }) });
262
+ default:
263
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }) });
264
+ }
265
+ }
266
+ function renderBannerSlot(slot, fallbackTag = "span", className, style) {
267
+ if (!slot) return null;
268
+ if (typeof slot === "string" || typeof slot === "number") {
269
+ const Tag = fallbackTag;
270
+ return /* @__PURE__ */ jsx(Tag, { className, style, children: slot });
271
+ }
272
+ if (isValidElement(slot)) {
273
+ return cloneElement(slot, {
274
+ className: cn(className, slot.props.className),
275
+ style: slot.props.style || style ? { ...style, ...slot.props.style } : void 0
276
+ });
277
+ }
278
+ return slot;
279
+ }
280
+ function getBadgeStatusStyles(theme, status) {
281
+ const alwaysLight = "var(--jumpy-color-non-color-always-ultralight, #FFFFFF)";
282
+ switch (status) {
283
+ case "positive":
284
+ return { backgroundColor: theme.color.positiveBase, color: alwaysLight };
285
+ case "warning":
286
+ return { backgroundColor: theme.color.warningBase, color: theme.color.nonColorAlwaysUltradark };
287
+ case "alarm":
288
+ return { backgroundColor: theme.color.alarmBase, color: alwaysLight };
289
+ case "active":
290
+ return { backgroundColor: "var(--jumpy-color-primary-base)", color: theme.color.textOnPrimary };
291
+ default:
292
+ return { backgroundColor: theme.color.neutralBase, color: alwaysLight };
293
+ }
294
+ }
295
+ function getBadgeSizeStyles(theme, size2) {
296
+ const labelSmall = textStyleValue(theme, JumpyTextStyle.labelSmallEmphasized);
297
+ const labelMedium = textStyleValue(theme, JumpyTextStyle.labelMediumEmphasized);
298
+ switch (size2) {
299
+ case "xSmall":
300
+ return {
301
+ width: sizeValue(theme, JumpySize.small3XS),
302
+ height: sizeValue(theme, JumpySize.small3XS),
303
+ minWidth: sizeValue(theme, JumpySize.small3XS),
304
+ padding: spacingValue(theme, JumpySpacing.zero),
305
+ fontSize: 0,
306
+ lineHeight: 0
307
+ };
308
+ case "medium":
309
+ return {
310
+ minWidth: sizeValue(theme, JumpySize.small),
311
+ height: sizeValue(theme, JumpySize.small),
312
+ padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`,
313
+ fontFamily: theme.fontFamily.familyBase,
314
+ fontSize: labelMedium.fontSize,
315
+ // A line box filling the whole-pixel content height keeps the baseline from rounding the digits upward.
316
+ lineHeight: `${sizeValue(theme, JumpySize.small) - spacingValue(theme, JumpySpacing.small3XS) * 2}px`,
317
+ fontWeight: labelMedium.fontWeight
318
+ };
319
+ default:
320
+ return {
321
+ minWidth: sizeValue(theme, JumpySize.small2XS),
322
+ height: sizeValue(theme, JumpySize.small2XS),
323
+ padding: `${spacingValue(theme, JumpySpacing.zero)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`,
324
+ fontFamily: theme.fontFamily.familyBase,
325
+ fontSize: labelSmall.fontSize,
326
+ lineHeight: `${sizeValue(theme, JumpySize.small2XS)}px`,
327
+ fontWeight: labelSmall.fontWeight
328
+ };
329
+ }
330
+ }
331
+ function getTagStatusStyles(theme, status) {
332
+ switch (status) {
333
+ case "positive":
334
+ return { backgroundColor: theme.color.successSurface, color: theme.color.successText, borderColor: theme.color.successBorder };
335
+ case "warning":
336
+ return { backgroundColor: theme.color.warningSurface, color: theme.color.warningText, borderColor: theme.color.warningBorder };
337
+ case "alarm":
338
+ return { backgroundColor: theme.color.dangerSurface, color: theme.color.dangerText, borderColor: theme.color.dangerBorder };
339
+ default:
340
+ return { backgroundColor: theme.color.surfaceField, color: theme.color.textPrimary, borderColor: theme.color.borderSubtle };
341
+ }
342
+ }
343
+ function getTypographyStyle(theme, variant = "bodyMedium") {
344
+ const style = textStyleValue(theme, JumpyTextStyle[variant] || JumpyTextStyle.bodyMedium);
345
+ return {
346
+ fontFamily: theme.fontFamily.familyBase,
347
+ fontSize: style.fontSize,
348
+ lineHeight: style.lineHeight,
349
+ fontWeight: style.fontWeight,
350
+ letterSpacing: style.letterSpacing
351
+ };
352
+ }
353
+ function getCardStateStyles(theme, selected = false) {
354
+ const inactive = theme.card.inactive;
355
+ const active = theme.card.active;
356
+ const tokens = selected ? active : inactive;
357
+ const state = selected ? "active" : "inactive";
358
+ const cardToken = (interaction, slot, fallback) => `var(--jumpy-card-${state}-${interaction}-${slot}, ${fallback})`;
359
+ return {
360
+ "--jumpy-card-default-surface": cardToken("default", "surface", tokens.default.surface),
361
+ "--jumpy-card-default-border": cardToken("default", "border", tokens.default.border),
362
+ "--jumpy-card-default-text": cardToken("default", "text", tokens.default.text),
363
+ "--jumpy-card-default-icon": cardToken("default", "icon", tokens.default.icon),
364
+ "--jumpy-card-hover-surface": cardToken("hover", "surface", tokens.hover.surface),
365
+ "--jumpy-card-hover-border": cardToken("hover", "border", tokens.hover.border),
366
+ "--jumpy-card-hover-text": cardToken("hover", "text", tokens.hover.text),
367
+ "--jumpy-card-hover-icon": cardToken("hover", "icon", tokens.hover.icon),
368
+ "--jumpy-card-pressed-surface": cardToken("pressed", "surface", tokens.pressed.surface),
369
+ "--jumpy-card-pressed-border": cardToken("pressed", "border", tokens.pressed.border),
370
+ "--jumpy-card-pressed-text": cardToken("pressed", "text", tokens.pressed.text),
371
+ "--jumpy-card-pressed-icon": cardToken("pressed", "icon", tokens.pressed.icon),
372
+ "--jumpy-card-focus-surface": cardToken("focus", "surface", tokens.focus.surface),
373
+ "--jumpy-card-focus-border": cardToken("focus", "border", tokens.focus.border),
374
+ "--jumpy-card-focus-ring": cardToken("focus-focus", "ring", tokens.focus.focusRing),
375
+ "--jumpy-card-focus-text": cardToken("focus", "text", tokens.focus.text),
376
+ "--jumpy-card-focus-icon": cardToken("focus", "icon", tokens.focus.icon),
377
+ "--jumpy-card-disabled-surface": cardToken("disabled", "surface", tokens.disabled.surface),
378
+ "--jumpy-card-disabled-border": cardToken("disabled", "border", tokens.disabled.border),
379
+ "--jumpy-card-disabled-text": cardToken("disabled", "text", tokens.disabled.text),
380
+ "--jumpy-card-disabled-icon": cardToken("disabled", "icon", tokens.disabled.icon)
381
+ };
382
+ }
383
+ function getListItemStateStyles(theme, selected = false) {
384
+ const tokens = selected ? theme.listItem.active : theme.listItem.inactive;
385
+ return {
386
+ "--jumpy-list-item-default-surface": tokens.default.surface,
387
+ "--jumpy-list-item-default-text": tokens.default.text,
388
+ "--jumpy-list-item-default-subtext": tokens.default.subtext,
389
+ "--jumpy-list-item-default-icon": tokens.default.icon,
390
+ "--jumpy-list-item-hover-surface": tokens.hover.surface,
391
+ "--jumpy-list-item-hover-text": tokens.hover.text,
392
+ "--jumpy-list-item-hover-subtext": tokens.hover.subtext,
393
+ "--jumpy-list-item-hover-icon": tokens.hover.icon,
394
+ "--jumpy-list-item-pressed-surface": tokens.pressed.surface,
395
+ "--jumpy-list-item-pressed-text": tokens.pressed.text,
396
+ "--jumpy-list-item-pressed-subtext": tokens.pressed.subtext,
397
+ "--jumpy-list-item-pressed-icon": tokens.pressed.icon,
398
+ "--jumpy-list-item-focus-surface": tokens.focus.surface,
399
+ "--jumpy-list-item-focus-text": tokens.focus.text,
400
+ "--jumpy-list-item-focus-subtext": tokens.focus.subtext,
401
+ "--jumpy-list-item-focus-icon": tokens.focus.icon,
402
+ "--jumpy-list-item-focus-ring": tokens.focus.focusRing,
403
+ "--jumpy-list-item-disabled-surface": tokens.disabled.surface,
404
+ "--jumpy-list-item-disabled-text": tokens.disabled.text,
405
+ "--jumpy-list-item-disabled-subtext": tokens.disabled.subtext,
406
+ "--jumpy-list-item-disabled-icon": tokens.disabled.icon
407
+ };
408
+ }
409
+ function getButtonSpacing(theme, variant, hasLeading, hasTrailing) {
410
+ const gap = spacingValue(theme, JumpySpacing.smallXS);
411
+ const verticalPadding = spacingValue(theme, JumpySpacing.small2XS);
412
+ if (variant === "borderless") {
413
+ return {
414
+ gap,
415
+ paddingLeft: spacingValue(theme, JumpySpacing.zero),
416
+ paddingRight: spacingValue(theme, JumpySpacing.zero),
417
+ paddingTop: verticalPadding,
418
+ paddingBottom: verticalPadding
419
+ };
420
+ }
421
+ return {
422
+ gap,
423
+ paddingLeft: hasLeading ? spacingValue(theme, JumpySpacing.medium) : spacingValue(theme, JumpySpacing.medium) + spacingValue(theme, JumpySpacing.small3XS),
424
+ paddingRight: hasTrailing ? spacingValue(theme, JumpySpacing.medium) : spacingValue(theme, JumpySpacing.medium) + spacingValue(theme, JumpySpacing.small3XS),
425
+ paddingTop: verticalPadding,
426
+ paddingBottom: verticalPadding
427
+ };
428
+ }
429
+ function getFieldInputStyles(theme, disabled) {
430
+ const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
431
+ return {
432
+ display: "block",
433
+ width: "100%",
434
+ boxSizing: "border-box",
435
+ color: "var(--jumpy-cmp-base-input-value-color)",
436
+ padding: 0,
437
+ margin: 0,
438
+ border: 0,
439
+ outline: 0,
440
+ background: "transparent",
441
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
442
+ fontFamily: theme.fontFamily.familyBase,
443
+ fontSize: bodyMedium.fontSize,
444
+ lineHeight: `${bodyMedium.fontSize * bodyMedium.lineHeight}px`,
445
+ transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingRelease}`
446
+ };
447
+ }
448
+ function getFieldShellStyles(theme, disabled, status = "regular", focused = false, readOnly = false) {
449
+ const outlineWidth = borderWidthValue(theme, focused && !readOnly ? JumpyBorderWidth.medium : JumpyBorderWidth.small);
450
+ const easing = focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease;
451
+ return {
452
+ display: "flex",
453
+ alignItems: "center",
454
+ width: "100%",
455
+ minHeight: sizeValue(theme, JumpySize.largeXL),
456
+ height: sizeValue(theme, JumpySize.largeXL),
457
+ boxSizing: "border-box",
458
+ borderRadius: radiusValue(theme, JumpyRadius.small),
459
+ border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid transparent`,
460
+ outline: readOnly ? "none" : `${outlineWidth}px solid var(--jumpy-cmp-base-input-border-color)`,
461
+ outlineOffset: readOnly ? void 0 : -outlineWidth,
462
+ backgroundColor: "var(--jumpy-cmp-base-input-surface-color)",
463
+ paddingLeft: spacingValue(theme, JumpySpacing.large),
464
+ paddingRight: spacingValue(theme, JumpySpacing.small3XS),
465
+ paddingTop: spacingValue(theme, JumpySpacing.small3XS),
466
+ paddingBottom: spacingValue(theme, JumpySpacing.small3XS),
467
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
468
+ gap: spacingValue(theme, JumpySpacing.medium),
469
+ cursor: disabled || readOnly ? "default" : "text",
470
+ pointerEvents: disabled ? "none" : void 0,
471
+ transition: `background-color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}, outline-color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}, outline-width ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}`
472
+ };
473
+ }
474
+ function renderStandaloneField(theme, disabled, className, style, inputElement, status = "regular", readOnly = false) {
475
+ return /* @__PURE__ */ jsx(
476
+ "div",
477
+ {
478
+ "data-jumpy-component": "field-control",
479
+ "data-disabled": disabled ? "true" : "false",
480
+ "data-readonly": readOnly ? "true" : "false",
481
+ "data-status": status,
482
+ className: cn(className),
483
+ style: {
484
+ ...getFieldShellStyles(theme, disabled, status, false, readOnly),
485
+ ...style || {}
486
+ },
487
+ children: inputElement
488
+ }
489
+ );
490
+ }
491
+ function JumpBackground({ snow = true }) {
492
+ const theme = useJumpyTheme();
493
+ return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none fixed inset-0 z-0 overflow-hidden", style: { pointerEvents: "none" }, children: [
494
+ /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 top-[-18rem] h-[34rem] w-[34rem] -translate-x-1/2 rounded-full blur-[120px]", style: { backgroundColor: theme.color.infoSurface } }),
495
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-24 right-[-10rem] h-[28rem] w-[28rem] rounded-full blur-[110px]", style: { backgroundColor: theme.color.borderSubtle } }),
496
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[radial-gradient(rgba(26,28,31,0.16)_1px,transparent_1px)] bg-[size:22px_22px]", style: { opacity: theme.opacity.subtle } }),
497
+ snow && /* @__PURE__ */ jsx("div", { className: "snowfall", "aria-hidden": "true" })
498
+ ] });
499
+ }
500
+ function JumpBadge({ as, size: size2 = "medium", status = "regular", hidden = false, children, className = "", ...props }) {
501
+ const theme = useJumpyTheme();
502
+ const visible = !hidden;
503
+ const Root = as || "div";
504
+ return /* @__PURE__ */ jsx(
505
+ Root,
506
+ {
507
+ ...props,
508
+ "data-jumpy-component": "badge",
509
+ "data-size": size2,
510
+ "data-status": status,
511
+ "data-visible": visible ? "true" : "false",
512
+ role: props.role || "status",
513
+ "aria-live": props["aria-live"] || "polite",
514
+ "aria-hidden": hidden || void 0,
515
+ className: cn("inline-flex items-center justify-center box-border", className),
516
+ style: {
517
+ display: "inline-flex",
518
+ alignItems: "center",
519
+ justifyContent: "center",
520
+ boxSizing: "border-box",
521
+ ...getBadgeSizeStyles(theme, size2),
522
+ ...getBadgeStatusStyles(theme, status),
523
+ borderRadius: radiusValue(theme, JumpyRadius.full),
524
+ letterSpacing: "normal",
525
+ opacity: visible ? opacityValue(theme, JumpyOpacity.solid) : opacityValue(theme, JumpyOpacity.transparent),
526
+ scale: visible ? 1 : 0.25,
527
+ width: size2 === "xSmall" ? sizeValue(theme, JumpySize.small3XS) : "fit-content",
528
+ transition: `width ${animationDurationValue(theme, JumpyAnimationDuration.medium)}s ${theme.animation.timeEasingDrift}, opacity ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${visible ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}, scale ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${visible ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`,
529
+ ...props.style || {}
530
+ },
531
+ children: size2 === "xSmall" ? null : /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "badge-content", style: { display: "inline-block", lineHeight: "inherit", fontVariantNumeric: "tabular-nums" }, children })
532
+ }
533
+ );
534
+ }
535
+ function BrandMark({ compact = false, label = "Jumpy UI" }) {
536
+ const theme = useJumpyTheme();
537
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", !compact && "justify-center"), children: [
538
+ /* @__PURE__ */ jsx("div", { className: "grid h-10 w-10 place-items-center rounded-xl text-xl font-black", style: { backgroundColor: theme.color.surfaceCard, color: theme.color.surfacePrimary, borderRadius: theme.radius.medium }, children: "J" }),
539
+ /* @__PURE__ */ jsx("span", { className: "block text-lg font-extrabold tracking-tight", style: { color: theme.color.textPrimary, fontFamily: theme.typography.fontFamily }, children: label })
540
+ ] });
541
+ }
542
+ function JumpCard({ as, ref, children, className = "", variant = "filled", interactive = false, selected = false, disabled = false, dragged = false, innerClasses = {}, ...props }) {
543
+ const theme = useJumpyTheme();
544
+ const Component = as || "div";
545
+ const cardRef = useRef(null);
546
+ const [pressScaleLimit, setPressScaleLimit] = useState(1);
547
+ const pressDistance = spacingValue(theme, JumpySpacing.small3XS);
548
+ useLayoutEffect(() => {
549
+ const element = cardRef.current;
550
+ if (!element || !interactive || disabled) return;
551
+ const measure = () => {
552
+ const longestSide = Math.max(element.offsetWidth, element.offsetHeight);
553
+ setPressScaleLimit(longestSide > 0 ? Math.max(0, 1 - pressDistance / longestSide) : 1);
554
+ };
555
+ measure();
556
+ const observer = new ResizeObserver(measure);
557
+ observer.observe(element);
558
+ return () => observer.disconnect();
559
+ }, [Component, interactive, disabled, pressDistance]);
560
+ const isButton = Component === "button";
561
+ const activity = selected ? "active" : "inactive";
562
+ const draggedStyles = dragged && !disabled ? {
563
+ borderColor: `var(--jumpy-card-${activity}-dragged-border)`,
564
+ backgroundColor: `var(--jumpy-card-${activity}-dragged-surface)`
565
+ } : {};
566
+ const hasPaddingUtility = /(?:^|\s)(?:[a-z]+:)*p(?:[trblxy])?-/.test(className);
567
+ return /* @__PURE__ */ jsxs(
568
+ Component,
569
+ {
570
+ ...props,
571
+ ref: (node) => {
572
+ cardRef.current = node;
573
+ if (typeof ref === "function") return ref(node);
574
+ if (ref) ref.current = node;
575
+ },
576
+ type: isButton ? props.type || "button" : props.type,
577
+ disabled: isButton ? disabled : void 0,
578
+ "data-jumpy-component": "card",
579
+ "data-variant": variant,
580
+ "data-interactive": interactive ? "true" : "false",
581
+ "data-selected": selected ? "true" : "false",
582
+ "data-dragged": dragged ? "true" : "false",
583
+ "data-disabled": disabled ? "true" : "false",
584
+ tabIndex: interactive && !disabled ? props.tabIndex ?? 0 : props.tabIndex,
585
+ "aria-disabled": disabled || void 0,
586
+ className: cn(className),
587
+ style: {
588
+ ...getCardStateStyles(theme, selected),
589
+ "--jumpy-card-press-scale-limit": pressScaleLimit,
590
+ display: "block",
591
+ position: "relative",
592
+ boxSizing: "border-box",
593
+ overflow: "hidden",
594
+ appearance: isButton ? "none" : void 0,
595
+ WebkitAppearance: isButton ? "none" : void 0,
596
+ outline: "none",
597
+ border: 0,
598
+ borderRadius: theme.sizing.cardRadius,
599
+ padding: props.style?.padding === void 0 && !hasPaddingUtility ? spacingValue(theme, JumpySpacing.large) : void 0,
600
+ backgroundColor: "var(--jumpy-card-default-surface)",
601
+ boxShadow: variant === "filled" ? theme.shadow.card : "none",
602
+ color: "var(--jumpy-card-default-text, var(--jumpy-color-text-primary))",
603
+ ...draggedStyles,
604
+ ...props.style || {}
605
+ },
606
+ children: [
607
+ interactive && selected ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "checkmark", className: innerClasses.checkmark?.className, style: { position: "absolute", right: "var(--jumpy-cmp-card-checkmark-right)", top: "var(--jumpy-cmp-card-checkmark-top)", display: "flex", color: "var(--jumpy-card-default-icon)", ...innerClasses.checkmark?.style || {} }, children: /* @__PURE__ */ jsx(CircleConfirmIcon, { "data-jumpy-slot": "checkmark-icon", className: innerClasses.checkmarkIcon?.className, style: { width: sizeValue(theme, JumpySize.small), height: sizeValue(theme, JumpySize.small), ...innerClasses.checkmarkIcon?.style || {} } }) }) : null,
608
+ children
609
+ ]
610
+ }
611
+ );
612
+ }
613
+ function JumpGrid({ as, columns = { small: 8, medium: 12, large: 12, xLarge: 12 }, spacing = { small: ["1rem", "1rem"], medium: ["1rem", "1rem"], large: ["1.5rem", "1.5rem"], xLarge: ["1.5rem", "1.5rem"] }, children, className = "", ...props }) {
614
+ const Component = as || "div";
615
+ const resolvedColumns = responsiveValue(columns, { small: 8, medium: 12, large: 12, xLarge: 12 });
616
+ const resolvedSpacing = responsiveGridSpacing(spacing);
617
+ return /* @__PURE__ */ jsx(
618
+ Component,
619
+ {
620
+ ...props,
621
+ "data-jumpy-component": "grid",
622
+ className: cn(className),
623
+ style: {
624
+ "--jumpy-cmp-grid-columns-small": resolvedColumns.small,
625
+ "--jumpy-cmp-grid-columns-medium": resolvedColumns.medium,
626
+ "--jumpy-cmp-grid-columns-large": resolvedColumns.large,
627
+ "--jumpy-cmp-grid-columns-xLarge": resolvedColumns.xLarge,
628
+ "--jumpy-cmp-grid-spacing-row-small": resolvedSpacing.small[0],
629
+ "--jumpy-cmp-grid-spacing-column-small": resolvedSpacing.small[1],
630
+ "--jumpy-cmp-grid-spacing-row-medium": resolvedSpacing.medium[0],
631
+ "--jumpy-cmp-grid-spacing-column-medium": resolvedSpacing.medium[1],
632
+ "--jumpy-cmp-grid-spacing-row-large": resolvedSpacing.large[0],
633
+ "--jumpy-cmp-grid-spacing-column-large": resolvedSpacing.large[1],
634
+ "--jumpy-cmp-grid-spacing-row-xLarge": resolvedSpacing.xLarge[0],
635
+ "--jumpy-cmp-grid-spacing-column-xLarge": resolvedSpacing.xLarge[1],
636
+ ...props.style || {}
637
+ },
638
+ children
639
+ }
640
+ );
641
+ }
642
+ function JumpGridItem({ as, span = 1, horizontalPlacement = "stretch", verticalPlacement = "stretch", children, className = "", ...props }) {
643
+ const Component = as || "div";
644
+ const resolvedSpan = responsiveValue(span, { small: 1, medium: 1, large: 1, xLarge: 1 });
645
+ return /* @__PURE__ */ jsx(
646
+ Component,
647
+ {
648
+ ...props,
649
+ "data-jumpy-component": "grid-item",
650
+ "data-horizontal-placement": horizontalPlacement,
651
+ "data-vertical-placement": verticalPlacement,
652
+ className: cn(className),
653
+ style: {
654
+ "--jumpy-cmp-grid-item-span-small": resolvedSpan.small,
655
+ "--jumpy-cmp-grid-item-span-medium": resolvedSpan.medium,
656
+ "--jumpy-cmp-grid-item-span-large": resolvedSpan.large,
657
+ "--jumpy-cmp-grid-item-span-xLarge": resolvedSpan.xLarge,
658
+ ...props.style || {}
659
+ },
660
+ children
661
+ }
662
+ );
663
+ }
664
+ function JumpList({ as, children, className = "", ...props }) {
665
+ const Component = as || "div";
666
+ return /* @__PURE__ */ jsx(
667
+ Component,
668
+ {
669
+ ...props,
670
+ "data-jumpy-component": "list",
671
+ role: props.role || "list",
672
+ className: cn(className),
673
+ style: {
674
+ display: "block",
675
+ width: "100%",
676
+ height: "fit-content",
677
+ overflowX: "hidden",
678
+ overflowY: "auto",
679
+ ...props.style || {}
680
+ },
681
+ children
682
+ }
683
+ );
684
+ }
685
+ function JumpListItem({ as, children, className = "", selected = false, disabled = false, readOnly = false, focusedView = false, leading, trailing, metadata, description, slots = {}, nestedCollapsed, defaultNestedCollapsed = true, onNestedCollapsedChange, truncateTexts = false, innerClasses = {}, ref, ...props }) {
686
+ const theme = useJumpyTheme();
687
+ const Component = as || "button";
688
+ const isButton = Component === "button";
689
+ const componentName = props["data-jumpy-component"] || "list-item";
690
+ const resolvedLeading = slots.leading ?? leading;
691
+ const resolvedTrailing = slots.trailing ?? trailing;
692
+ const resolvedMetadata = slots.metadata ?? metadata;
693
+ const resolvedDescription = slots.description ?? description;
694
+ const resolvedSelector = slots.selector;
695
+ const resolvedNestedList = slots.nestedList;
696
+ const [internalNestedCollapsed, setInternalNestedCollapsed] = useState(defaultNestedCollapsed);
697
+ const isNestedCollapsed = nestedCollapsed ?? internalNestedCollapsed;
698
+ const hasNestedList = Boolean(resolvedNestedList);
699
+ const isDisabled = disabled || readOnly;
700
+ const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
701
+ const bodySmall = textStyleValue(theme, JumpyTextStyle.bodySmall);
702
+ const iconSize = sizeValue(theme, JumpySize.small2XS);
703
+ const handleExpandedChange = () => {
704
+ const nextCollapsed = !isNestedCollapsed;
705
+ if (nestedCollapsed === void 0) setInternalNestedCollapsed(nextCollapsed);
706
+ onNestedCollapsedChange?.(nextCollapsed);
707
+ };
708
+ const handleClick = (event) => {
709
+ if (isDisabled) return;
710
+ if (hasNestedList) handleExpandedChange();
711
+ props.onClick?.(event);
712
+ };
713
+ const handleKeyDown = (event) => {
714
+ if ((event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) && hasNestedList && !isDisabled) {
715
+ event.preventDefault();
716
+ handleExpandedChange();
717
+ }
718
+ props.onKeyDown?.(event);
719
+ };
720
+ return /* @__PURE__ */ jsxs(
721
+ Component,
722
+ {
723
+ ...props,
724
+ ref,
725
+ type: isButton ? props.type || "button" : props.type,
726
+ disabled: isButton ? disabled : void 0,
727
+ "aria-disabled": !isButton && isDisabled ? true : void 0,
728
+ "aria-current": selected ? props["aria-current"] || "true" : props["aria-current"],
729
+ "aria-selected": props.role === "option" || props.role === "treeitem" ? selected : props["aria-selected"],
730
+ "aria-expanded": hasNestedList ? !isNestedCollapsed : props["aria-expanded"],
731
+ onClick: handleClick,
732
+ onKeyDown: handleKeyDown,
733
+ "data-jumpy-component": componentName,
734
+ "data-selected": selected ? "true" : "false",
735
+ "data-disabled": disabled ? "true" : "false",
736
+ "data-readonly": readOnly ? "true" : "false",
737
+ "data-focused-view": focusedView ? "true" : void 0,
738
+ role: props.role || "listitem",
739
+ className: cn("text-left", className),
740
+ style: {
741
+ ...getListItemStateStyles(theme, selected),
742
+ display: "grid",
743
+ textAlign: "start",
744
+ gridTemplateColumns: `${resolvedSelector ? "min-content " : ""}${resolvedLeading ? "min-content " : ""}minmax(0, 1fr) ${resolvedMetadata ? "minmax(0, min-content) " : ""}${resolvedTrailing || hasNestedList ? "min-content" : ""}`,
745
+ gridTemplateRows: resolvedDescription ? "auto auto" : "auto",
746
+ alignItems: "center",
747
+ gap: spacingValue(theme, JumpySpacing.large),
748
+ width: "100%",
749
+ minHeight: sizeValue(theme, JumpySize.large) + spacingValue(theme, JumpySpacing.large),
750
+ boxSizing: "border-box",
751
+ padding: `${spacingValue(theme, JumpySpacing.medium)}px ${spacingValue(theme, JumpySpacing.large)}px`,
752
+ border: 0,
753
+ borderRadius: radiusValue(theme, JumpyRadius.small),
754
+ backgroundColor: componentName === "side-menu-item" ? "var(--jumpy-list-item-surface, var(--jumpy-list-item-default-surface))" : "transparent",
755
+ color: "var(--jumpy-list-item-color, var(--jumpy-list-item-default-text))",
756
+ cursor: disabled ? "not-allowed" : readOnly ? "default" : "pointer",
757
+ fontFamily: theme.fontFamily.familyBase,
758
+ fontSize: bodyMedium.fontSize,
759
+ lineHeight: bodyMedium.lineHeight,
760
+ fontWeight: bodyMedium.fontWeight,
761
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
762
+ pointerEvents: readOnly ? "none" : void 0,
763
+ position: "relative",
764
+ zIndex: 1,
765
+ ...props.style || {}
766
+ },
767
+ children: [
768
+ resolvedSelector ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "selector", "aria-hidden": props.role === "option" || props.role === "treeitem" ? "true" : void 0, className: innerClasses.selector?.className, style: { display: "inline-flex", pointerEvents: "none", color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.selector?.style || {} }, children: resolvedSelector }) : null,
769
+ resolvedLeading ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "leading", "aria-hidden": "true", className: innerClasses.leading?.className, style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.leading?.style || {} }, children: resolvedLeading }) : null,
770
+ /* @__PURE__ */ jsxs("span", { style: { display: "grid", minWidth: 0 }, children: [
771
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "label", "data-truncate": truncateTexts ? "true" : void 0, "data-text": typeof children === "string" ? children : void 0, className: innerClasses.label?.className, style: { minWidth: 0, maxWidth: "100%", overflowWrap: "anywhere", wordBreak: "break-word", fontWeight: bodyMedium.fontWeight, ...innerClasses.label?.style || {} }, children }),
772
+ resolvedDescription ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "description", className: innerClasses.description?.className, style: { color: selected ? theme.listItem.active.default.subtext : theme.listItem.inactive.default.subtext, fontSize: bodySmall.fontSize, lineHeight: bodySmall.lineHeight, ...innerClasses.description?.style || {} }, children: resolvedDescription }) : null
773
+ ] }),
774
+ resolvedMetadata ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "metadata", className: innerClasses.metadata?.className, style: { color: selected ? theme.listItem.active.default.subtext : theme.listItem.inactive.default.subtext, whiteSpace: "nowrap", fontSize: bodySmall.fontSize, lineHeight: bodySmall.lineHeight, ...innerClasses.metadata?.style || {} }, children: resolvedMetadata }) : null,
775
+ resolvedTrailing || hasNestedList ? /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "trailing", "aria-hidden": "true", className: innerClasses.trailing?.className, style: { display: "inline-flex", color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.trailing?.style || {} }, children: [
776
+ resolvedTrailing,
777
+ hasNestedList ? /* @__PURE__ */ jsx(JumpIconButton, { as: "span", variant: "secondary", alternateHover: true, tabIndex: -1, "aria-hidden": "true", children: isNestedCollapsed ? /* @__PURE__ */ jsx(ChevronDownIcon, {}) : /* @__PURE__ */ jsx(ChevronUpIcon, {}) }) : null
778
+ ] }) : null,
779
+ hasNestedList ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "nested-list", "aria-hidden": isNestedCollapsed ? "true" : void 0, style: { gridColumn: "1 / -1", display: isNestedCollapsed ? "none" : "block" }, children: resolvedNestedList }) : null
780
+ ]
781
+ }
782
+ );
783
+ }
784
+ function JumpSideMenu({ as, children, className = "", collapsed = false, selected, disabled = false, disableKeyboardNavigation = false, renderMode, getItemProps, onChange, onKeyDown, onTransitionEnd, ...props }) {
785
+ const Component = as || "ul";
786
+ const theme = useJumpyTheme();
787
+ const elementRef = useRef(null);
788
+ const itemRefs = useRef([]);
789
+ const previousCollapsed = useRef(collapsed);
790
+ const releaseTimer = useRef(void 0);
791
+ const [isResizing, setIsResizing] = useState(false);
792
+ useLayoutEffect(() => {
793
+ if (previousCollapsed.current === collapsed) return void 0;
794
+ previousCollapsed.current = collapsed;
795
+ const element = elementRef.current;
796
+ if (!element) return void 0;
797
+ const transitionMs = getWidthTransitionMs(element);
798
+ if (!transitionMs) return void 0;
799
+ setIsResizing(true);
800
+ window.clearTimeout(releaseTimer.current);
801
+ releaseTimer.current = window.setTimeout(() => setIsResizing(false), transitionMs + 100);
802
+ return void 0;
803
+ }, [collapsed]);
804
+ useEffect(() => () => window.clearTimeout(releaseTimer.current), []);
805
+ const selectItem = (value) => {
806
+ if (disabled || value === void 0) return;
807
+ onChange?.([value]);
808
+ };
809
+ const handleKeyDown = (event) => {
810
+ if (!disableKeyboardNavigation) {
811
+ if (event.key === KeyboardKeys.ARROW_DOWN || event.key === KeyboardKeys.ARROW_RIGHT) {
812
+ event.preventDefault();
813
+ focusSideMenuItem(itemRefs.current, 1);
814
+ } else if (event.key === KeyboardKeys.ARROW_UP || event.key === KeyboardKeys.ARROW_LEFT) {
815
+ event.preventDefault();
816
+ focusSideMenuItem(itemRefs.current, -1);
817
+ } else if (event.key === KeyboardKeys.HOME) {
818
+ event.preventDefault();
819
+ focusSideMenuItem(itemRefs.current, "first");
820
+ } else if (event.key === KeyboardKeys.END) {
821
+ event.preventDefault();
822
+ focusSideMenuItem(itemRefs.current, "last");
823
+ }
824
+ }
825
+ onKeyDown?.(event);
826
+ };
827
+ const handleTransitionEnd = (event) => {
828
+ onTransitionEnd?.(event);
829
+ if (event.target === event.currentTarget && event.propertyName === "width") {
830
+ window.clearTimeout(releaseTimer.current);
831
+ setIsResizing(false);
832
+ }
833
+ };
834
+ return /* @__PURE__ */ jsx(JumpySideMenuContext.Provider, { value: { collapsed, disabled, selected, onSelect: selectItem, containerExpanded: isResizing || !collapsed, renderMode, getItemProps, disableKeyboardNavigation, itemRefs }, children: /* @__PURE__ */ jsx(
835
+ Component,
836
+ {
837
+ ...props,
838
+ ref: elementRef,
839
+ "data-jumpy-component": "side-menu",
840
+ "data-menu-collapsed": collapsed ? "true" : void 0,
841
+ "data-menu-resizing": isResizing || props["data-menu-resizing"] === "true" ? "true" : void 0,
842
+ "aria-disabled": disabled || void 0,
843
+ onKeyDown: handleKeyDown,
844
+ onTransitionEnd: handleTransitionEnd,
845
+ className: cn(className),
846
+ style: {
847
+ display: "block",
848
+ width: "fit-content",
849
+ height: "fit-content",
850
+ overflowX: "hidden",
851
+ overflowY: "auto",
852
+ margin: 0,
853
+ padding: 0,
854
+ listStyle: "none",
855
+ transition: `width ${animationDurationValue(theme, collapsed ? JumpyAnimationDuration.small2XS : JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingDrift}`,
856
+ ...collapsed && !isResizing ? { minWidth: "min-content" } : {},
857
+ ...props.style || {}
858
+ },
859
+ children
860
+ }
861
+ ) });
862
+ }
863
+ function JumpSideMenuItem({ as, children, value, selected = false, disabled = false, className = "", icon, badge, collapsed, slots, metadata, description, nestedMenu, nestedCollapsed, defaultNestedCollapsed = false, onNestedCollapsedChange, renderMode, focusedView, truncateTexts = false, tooltipProps, innerClasses = {}, wrapperProps = {}, expanderIconProps = {}, onClick, onKeyDown, ...props }) {
864
+ const theme = useJumpyTheme();
865
+ const sideMenu = useContext(JumpySideMenuContext);
866
+ const itemRef = useRef(null);
867
+ const wrapperItemRef = useRef(null);
868
+ const isCollapsed = sideMenu ? !sideMenu.containerExpanded : collapsed ?? false;
869
+ const resolvedRenderMode = sideMenu?.renderMode ?? renderMode ?? "navigation";
870
+ const selectedValues = sideMenu?.selected;
871
+ const resolvedNestedMenu = slots?.nestedMenu ?? nestedMenu;
872
+ const resolvedMetadata = slots?.metadata ?? metadata;
873
+ const isSelected = isValueSelected(selectedValues, value) || selected;
874
+ const isDisabled = disabled || sideMenu?.disabled;
875
+ const resolvedTabIndex = focusedView ? 0 : sideMenu && !sideMenu.disableKeyboardNavigation && Array.isArray(selectedValues) ? isSelected ? 0 : -1 : props.tabIndex;
876
+ const hasNested = Boolean(resolvedNestedMenu);
877
+ const hasSelectedChildren = hasSelectedNestedItem(resolvedNestedMenu, selectedValues);
878
+ const [internalNestedCollapsed, setInternalNestedCollapsed] = useState(defaultNestedCollapsed);
879
+ const isNestedCollapsed = nestedCollapsed ?? internalNestedCollapsed;
880
+ const iconNode = typeof icon === "function" ? icon({ selected: isSelected }) : icon;
881
+ const badgeNode = typeof badge === "function" ? badge({ selected: isSelected, collapsed: isCollapsed }) : badge;
882
+ const handleItemClick = (event) => {
883
+ if (isDisabled) return;
884
+ if (hasNested) {
885
+ const nextCollapsed = !isNestedCollapsed;
886
+ onNestedCollapsedChange?.(nextCollapsed);
887
+ if (nestedCollapsed === void 0) setInternalNestedCollapsed(nextCollapsed);
888
+ } else {
889
+ sideMenu?.onSelect?.(value);
890
+ }
891
+ onClick?.(event);
892
+ };
893
+ const handleItemKeyDown = (event) => {
894
+ if ((event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) && !isDisabled) {
895
+ event.preventDefault();
896
+ handleItemClick(event);
897
+ }
898
+ onKeyDown?.(event);
899
+ };
900
+ const itemInteractionProps = sideMenu?.getItemProps?.({ onClick: handleItemClick, onKeyDown: handleItemKeyDown }) ?? {};
901
+ const { onClick: interactionOnClick, onKeyDown: interactionOnKeyDown, ...restItemInteractionProps } = itemInteractionProps;
902
+ const { wrapperProps: splitWrapperProps, linkProps } = resolvedRenderMode === "navigation" ? splitNavigationItemProps(props) : { wrapperProps: {}, linkProps: props };
903
+ useEffect(() => {
904
+ if (!sideMenu?.itemRefs) return void 0;
905
+ const registryItem = isCollapsed && resolvedRenderMode === "navigation" ? wrapperItemRef : itemRef;
906
+ sideMenu.itemRefs.current.push(registryItem);
907
+ return () => {
908
+ sideMenu.itemRefs.current = sideMenu.itemRefs.current.filter((item) => item !== registryItem);
909
+ };
910
+ }, [isCollapsed, resolvedRenderMode, sideMenu?.itemRefs]);
911
+ const withTooltip = (node) => {
912
+ if (!tooltipProps?.content) return node;
913
+ const { content, ...restTooltipProps } = tooltipProps;
914
+ return /* @__PURE__ */ jsx(JumpTooltip, { ...restTooltipProps, content, children: node });
915
+ };
916
+ const trailingNode = hasNested ? /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "expander", "aria-hidden": "true", className: innerClasses.expander?.className, style: { display: "inline-flex", alignItems: "center", gap: spacingValue(theme, JumpySpacing.small), ...innerClasses.expander?.style || {} }, children: [
917
+ badgeNode,
918
+ /* @__PURE__ */ jsx("span", { ...expanderIconProps, "data-jumpy-slot": "expander-icon", className: cn(innerClasses.expanderIcon?.className, expanderIconProps.className), style: { display: "inline-flex", transform: isNestedCollapsed ? "rotate(0deg)" : "rotate(180deg)", transition: `transform ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingDrift}`, ...innerClasses.expanderIcon?.style || {}, ...expanderIconProps.style || {} }, children: /* @__PURE__ */ jsx(ChevronDownIcon, {}) })
919
+ ] }) : badgeNode;
920
+ if (isCollapsed) {
921
+ const Component = as || (resolvedRenderMode === "navigation" ? "a" : "button");
922
+ const isButton = Component === "button";
923
+ const tokens = isSelected ? theme.listItem.active.default : theme.listItem.inactive.default;
924
+ const labelStyle = textStyleValue(theme, isSelected ? JumpyTextStyle.labelMediumEmphasized : JumpyTextStyle.labelMedium);
925
+ const collapsedWrapperInteractiveProps = resolvedRenderMode === "navigation" ? {
926
+ ref: wrapperItemRef,
927
+ tabIndex: resolvedTabIndex,
928
+ onClick: (event) => {
929
+ splitWrapperProps.onClick?.(event);
930
+ wrapperProps.onClick?.(event);
931
+ if (!event.defaultPrevented && event.target === event.currentTarget) itemRef.current?.click();
932
+ },
933
+ onKeyDown: (event) => {
934
+ splitWrapperProps.onKeyDown?.(event);
935
+ wrapperProps.onKeyDown?.(event);
936
+ if (!event.defaultPrevented && (event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE)) {
937
+ event.preventDefault();
938
+ itemRef.current?.click();
939
+ }
940
+ }
941
+ } : {};
942
+ return /* @__PURE__ */ jsxs("li", { ...splitWrapperProps, ...wrapperProps, ...collapsedWrapperInteractiveProps, "data-jumpy-component": "side-menu-item-wrapper", "aria-expanded": hasNested ? !isNestedCollapsed : void 0, className: cn(innerClasses.selector?.className, splitWrapperProps.className, wrapperProps.className), style: { display: "block", margin: 0, padding: 0, ...splitWrapperProps.style || {}, ...innerClasses.selector?.style || {}, ...wrapperProps.style || {} }, children: [
943
+ withTooltip(
944
+ /* @__PURE__ */ jsxs(
945
+ Component,
946
+ {
947
+ ...linkProps,
948
+ ref: itemRef,
949
+ ...restItemInteractionProps,
950
+ onClick: interactionOnClick ?? handleItemClick,
951
+ onKeyDown: interactionOnKeyDown ?? handleItemKeyDown,
952
+ type: isButton ? linkProps.type || "button" : linkProps.type,
953
+ disabled: isButton ? isDisabled : void 0,
954
+ "aria-disabled": !isButton && isDisabled ? true : void 0,
955
+ "aria-current": isSelected ? linkProps["aria-current"] || "page" : linkProps["aria-current"],
956
+ role: !isButton && !linkProps.href ? linkProps.role || "link" : linkProps.role,
957
+ tabIndex: resolvedRenderMode === "navigation" ? -1 : resolvedTabIndex,
958
+ "data-jumpy-component": "side-menu-item",
959
+ "data-collapsed": "true",
960
+ "data-selected": isSelected ? "true" : "false",
961
+ "data-disabled": isDisabled ? "true" : "false",
962
+ "data-value": !hasNested ? value : void 0,
963
+ "data-has-selected-children": hasSelectedChildren ? "true" : void 0,
964
+ "data-focused-view": focusedView ? "true" : void 0,
965
+ className: cn(className, innerClasses.collapsedLabel?.className),
966
+ style: {
967
+ position: "relative",
968
+ display: "flex",
969
+ flexDirection: "column",
970
+ alignItems: "center",
971
+ width: "100%",
972
+ minWidth: sizeValue(theme, JumpySize.large8XL) - spacingValue(theme, JumpySpacing.large3XL),
973
+ boxSizing: "border-box",
974
+ border: 0,
975
+ borderRadius: 0,
976
+ backgroundColor: "transparent",
977
+ color: tokens.text,
978
+ cursor: isDisabled ? "not-allowed" : "pointer",
979
+ padding: `${spacingValue(theme, JumpySpacing.smallXS)}px 0`,
980
+ opacity: isDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
981
+ ...linkProps.style || {}
982
+ },
983
+ children: [
984
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "side-menu-decorator", className: innerClasses.decoratorContainer?.className, style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large2XL)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`, color: tokens.icon, ...innerClasses.decoratorContainer?.style || {} }, children: [
985
+ iconNode,
986
+ badgeNode ? /* @__PURE__ */ jsx("span", { style: { position: "absolute", top: 0, right: spacingValue(theme, JumpySpacing.large), display: "inline-flex" }, children: badgeNode }) : null
987
+ ] }),
988
+ children ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "label", "data-text": typeof children === "string" ? children : void 0, className: innerClasses.label?.className, style: { boxSizing: "border-box", paddingInline: spacingValue(theme, JumpySpacing.large), overflowWrap: "anywhere", textAlign: "center", fontFamily: theme.fontFamily.familyBase, fontSize: labelStyle.fontSize, lineHeight: labelStyle.lineHeight, fontWeight: labelStyle.fontWeight, ...truncateTexts ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: "100%" } : {}, ...innerClasses.label?.style || {} }, children }) : null
989
+ ]
990
+ }
991
+ )
992
+ ),
993
+ hasNested && !isNestedCollapsed ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "nested-menu", children: resolvedNestedMenu }) : null
994
+ ] });
995
+ }
996
+ const expandedItem = /* @__PURE__ */ jsx(
997
+ JumpListItem,
998
+ {
999
+ ...linkProps,
1000
+ ref: itemRef,
1001
+ ...restItemInteractionProps,
1002
+ as: as || (resolvedRenderMode === "navigation" ? "a" : "li"),
1003
+ onClick: interactionOnClick ?? handleItemClick,
1004
+ onKeyDown: interactionOnKeyDown ?? handleItemKeyDown,
1005
+ selected: isSelected,
1006
+ disabled: isDisabled,
1007
+ leading: iconNode,
1008
+ trailing: trailingNode,
1009
+ metadata: resolvedMetadata,
1010
+ description,
1011
+ truncateTexts,
1012
+ innerClasses,
1013
+ "aria-expanded": hasNested ? !isNestedCollapsed : void 0,
1014
+ role: linkProps.role || (resolvedRenderMode === "navigation" ? "link" : void 0),
1015
+ tabIndex: resolvedTabIndex,
1016
+ "data-jumpy-component": "side-menu-item",
1017
+ "data-value": !hasNested ? value : void 0,
1018
+ "data-has-selected-children": hasSelectedChildren ? "true" : void 0,
1019
+ "data-focused-view": focusedView ? "true" : void 0,
1020
+ className: cn(className, innerClasses.selector?.className),
1021
+ style: {
1022
+ "--jumpy-side-menu-item-indicator-scale": isSelected ? 1 : 0.5,
1023
+ paddingLeft: spacingValue(theme, JumpySpacing.large2XL),
1024
+ paddingRight: spacingValue(theme, JumpySpacing.large2XL),
1025
+ paddingTop: spacingValue(theme, JumpySpacing.large),
1026
+ paddingBottom: spacingValue(theme, JumpySpacing.large),
1027
+ ...truncateTexts ? { overflow: "hidden" } : {},
1028
+ ...innerClasses.selector?.style || {},
1029
+ ...linkProps.style || {}
1030
+ },
1031
+ children
1032
+ }
1033
+ );
1034
+ const expandedNestedMenu = hasNested && !isNestedCollapsed ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "nested-menu", children: resolvedNestedMenu }) : null;
1035
+ if (resolvedRenderMode === "navigation") {
1036
+ return withTooltip(
1037
+ /* @__PURE__ */ jsxs("li", { ...splitWrapperProps, ...wrapperProps, "data-jumpy-component": "side-menu-item-wrapper", "aria-expanded": hasNested ? !isNestedCollapsed : void 0, className: cn(splitWrapperProps.className, wrapperProps.className), style: { display: "block", margin: 0, padding: 0, ...splitWrapperProps.style || {}, ...wrapperProps.style || {} }, children: [
1038
+ withTooltip(expandedItem),
1039
+ expandedNestedMenu
1040
+ ] })
1041
+ );
1042
+ }
1043
+ return withTooltip(
1044
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1045
+ expandedItem,
1046
+ expandedNestedMenu
1047
+ ] })
1048
+ );
1049
+ }
1050
+ function JumpTypography({ as, variant = "bodyMedium", color = "primary", className = "", children, ...props }) {
1051
+ const theme = useJumpyTheme();
1052
+ const Component = as || "p";
1053
+ const colorValue = color === "secondary" ? theme.color.textSecondary : color === "primary" ? theme.color.textPrimary : color;
1054
+ return /* @__PURE__ */ jsx(
1055
+ Component,
1056
+ {
1057
+ ...props,
1058
+ "data-jumpy-component": "typography",
1059
+ className: cn(className),
1060
+ style: {
1061
+ ...getTypographyStyle(theme, variant),
1062
+ color: colorValue,
1063
+ margin: 0,
1064
+ ...props.style || {}
1065
+ },
1066
+ children
1067
+ }
1068
+ );
1069
+ }
1070
+ function JumpHyperlink({ as, variant = "inline", disabled = false, className = "", children, ...props }) {
1071
+ const theme = useJumpyTheme();
1072
+ const Component = as || "a";
1073
+ if (variant !== "inline") {
1074
+ return /* @__PURE__ */ jsx(JumpLinkButton, { ...props, href: props.href || "#", variant: "borderless", className, children });
1075
+ }
1076
+ return /* @__PURE__ */ jsx(
1077
+ Component,
1078
+ {
1079
+ ...props,
1080
+ "data-jumpy-component": "hyperlink",
1081
+ "aria-disabled": disabled || void 0,
1082
+ tabIndex: disabled ? -1 : props.tabIndex,
1083
+ className: cn(className),
1084
+ style: {
1085
+ display: "inline",
1086
+ borderRadius: radiusValue(theme, JumpyRadius.small),
1087
+ backgroundColor: "transparent",
1088
+ color: disabled ? theme.actions.tertiary.regular.disabled.text : theme.actions.tertiary.regular.default.text,
1089
+ font: "inherit",
1090
+ textDecoration: "none",
1091
+ cursor: disabled ? "default" : "pointer",
1092
+ pointerEvents: disabled ? "none" : void 0,
1093
+ ...props.style || {}
1094
+ },
1095
+ children
1096
+ }
1097
+ );
1098
+ }
1099
+ function JumpDivider({ as, direction = "horizontal", captionPosition = "center", className = "", children, ...props }) {
1100
+ const theme = useJumpyTheme();
1101
+ const Component = as || "div";
1102
+ const isVertical = direction === "vertical";
1103
+ return /* @__PURE__ */ jsx(
1104
+ Component,
1105
+ {
1106
+ ...props,
1107
+ "data-jumpy-component": "divider",
1108
+ role: "separator",
1109
+ "aria-orientation": direction,
1110
+ "aria-label": children ? String(children) : props["aria-label"],
1111
+ className: cn(className),
1112
+ style: {
1113
+ display: isVertical ? "inline-flex" : "flex",
1114
+ alignItems: "center",
1115
+ justifyContent: captionPosition === "start" ? "flex-start" : captionPosition === "end" ? "flex-end" : "center",
1116
+ alignSelf: isVertical ? "stretch" : void 0,
1117
+ width: isVertical ? borderWidthValue(theme, JumpyBorderWidth.small) : "100%",
1118
+ minHeight: isVertical ? sizeValue(theme, JumpySize.large) : void 0,
1119
+ border: 0,
1120
+ borderTop: isVertical ? 0 : `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`,
1121
+ borderLeft: isVertical ? `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` : 0,
1122
+ color: theme.color.textSecondary,
1123
+ ...props.style || {}
1124
+ },
1125
+ children: children ? /* @__PURE__ */ jsx("span", { style: { backgroundColor: theme.color.surfaceCard, padding: `0 ${spacingValue(theme, JumpySpacing.smallXS)}px`, transform: isVertical ? "rotate(-90deg)" : void 0 }, children }) : null
1126
+ }
1127
+ );
1128
+ }
1129
+ function JumpAvatar({ as, size: size2 = "medium", disableInteraction = false, disabled = false, className = "", children, ...props }) {
1130
+ const theme = useJumpyTheme();
1131
+ const Component = as || (disableInteraction ? "span" : "button");
1132
+ const isButton = Component === "button";
1133
+ const sizes = { small: JumpySize.large, medium: JumpySize.large2XL, large: JumpySize.large4XL };
1134
+ const avatarSize = sizeValue(theme, sizes[size2] || JumpySize.large2XL);
1135
+ return /* @__PURE__ */ jsx(
1136
+ Component,
1137
+ {
1138
+ ...props,
1139
+ type: isButton ? props.type || "button" : props.type,
1140
+ disabled: isButton ? disabled : void 0,
1141
+ "aria-disabled": !isButton && disabled ? true : void 0,
1142
+ "data-jumpy-component": "avatar",
1143
+ className: cn(className),
1144
+ style: {
1145
+ display: "inline-grid",
1146
+ placeItems: "center",
1147
+ width: avatarSize,
1148
+ height: avatarSize,
1149
+ borderRadius: radiusValue(theme, JumpyRadius.full),
1150
+ border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`,
1151
+ backgroundColor: theme.color.infoSurface,
1152
+ color: theme.color.surfacePrimary,
1153
+ fontFamily: theme.fontFamily.familyBase,
1154
+ fontWeight: theme.fontFamily.bodyMediumEmphasized.fontWeight,
1155
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
1156
+ cursor: disableInteraction || disabled ? "default" : "pointer",
1157
+ overflow: "hidden",
1158
+ ...props.style || {}
1159
+ },
1160
+ children
1161
+ }
1162
+ );
1163
+ }
1164
+ function JumpBanner({ as, status: statusProp = "other", variant = "default", direction = "horizontal", hideStatus = false, floating = false, title, actions, slots, innerClasses = {}, className = "", children, ...props }) {
1165
+ const theme = useJumpyTheme();
1166
+ const Component = as || "div";
1167
+ const status = variant === "inline" && statusProp === "other" ? "info" : statusProp;
1168
+ const statusTone = getBannerStatusStyles(theme, status);
1169
+ const urgent = status === "alarm";
1170
+ const bannerTitle = slots?.title ?? title;
1171
+ const bannerActions = slots?.actions ?? actions;
1172
+ const bannerIcon = slots?.icon;
1173
+ const contentContainerVertical = direction === "vertical";
1174
+ const contentTitle = textStyleValue(theme, JumpyTextStyle.titleMediumEmphasized);
1175
+ const contentDescription = textStyleValue(theme, JumpyTextStyle.bodyMedium);
1176
+ return /* @__PURE__ */ jsxs(
1177
+ Component,
1178
+ {
1179
+ ...props,
1180
+ "data-jumpy-component": "banner",
1181
+ "data-variant": variant,
1182
+ "data-status": status,
1183
+ "data-floating": floating ? "true" : "false",
1184
+ role: urgent ? "alert" : "status",
1185
+ "aria-live": urgent ? "assertive" : "polite",
1186
+ className: cn(className),
1187
+ style: {
1188
+ "--jumpy-banner-status-icon": statusTone.indicator,
1189
+ "--jumpy-banner-status-surface": statusTone.indicatorSurface,
1190
+ "--jumpy-banner-inline-surface": statusTone.inlineSurface,
1191
+ ...props.style || {}
1192
+ },
1193
+ children: [
1194
+ !hideStatus ? /* @__PURE__ */ jsx(
1195
+ "span",
1196
+ {
1197
+ "data-jumpy-slot": "banner-status",
1198
+ className: innerClasses.status?.className,
1199
+ "aria-hidden": "true",
1200
+ style: {
1201
+ ...innerClasses.status?.style || {}
1202
+ },
1203
+ children: /* @__PURE__ */ jsx(JumpSeverityIcon, { status, icon: bannerIcon })
1204
+ }
1205
+ ) : null,
1206
+ /* @__PURE__ */ jsxs(
1207
+ "div",
1208
+ {
1209
+ "data-jumpy-slot": "banner-content-container",
1210
+ "data-direction": direction,
1211
+ className: innerClasses.contentContainer?.className,
1212
+ style: {
1213
+ ...innerClasses.contentContainer?.style || {}
1214
+ },
1215
+ children: [
1216
+ /* @__PURE__ */ jsxs(
1217
+ "div",
1218
+ {
1219
+ "data-jumpy-slot": "banner-content",
1220
+ className: innerClasses.content?.className,
1221
+ style: {
1222
+ ...innerClasses.content?.style || {}
1223
+ },
1224
+ children: [
1225
+ renderBannerSlot(bannerTitle, "span", innerClasses.contentTitle?.className, {
1226
+ margin: 0,
1227
+ fontFamily: theme.fontFamily.familyBase,
1228
+ fontSize: contentTitle.fontSize,
1229
+ lineHeight: contentTitle.lineHeight,
1230
+ fontWeight: contentTitle.fontWeight,
1231
+ letterSpacing: contentTitle.letterSpacing,
1232
+ color: "inherit",
1233
+ ...innerClasses.contentTitle?.style || {}
1234
+ }),
1235
+ renderBannerSlot(children, "p", innerClasses.contentDescription?.className, {
1236
+ margin: 0,
1237
+ fontFamily: theme.fontFamily.familyBase,
1238
+ fontSize: contentDescription.fontSize,
1239
+ lineHeight: contentDescription.lineHeight,
1240
+ fontWeight: contentDescription.fontWeight,
1241
+ letterSpacing: contentDescription.letterSpacing,
1242
+ color: "inherit",
1243
+ ...innerClasses.contentDescription?.style || {}
1244
+ })
1245
+ ]
1246
+ }
1247
+ ),
1248
+ contentContainerVertical && bannerActions ? /* @__PURE__ */ jsx(
1249
+ "div",
1250
+ {
1251
+ "data-jumpy-slot": "banner-actions-container",
1252
+ className: innerClasses.actionsContainer?.className,
1253
+ style: {
1254
+ ...innerClasses.actionsContainer?.style || {}
1255
+ },
1256
+ children: bannerActions
1257
+ }
1258
+ ) : bannerActions
1259
+ ]
1260
+ }
1261
+ )
1262
+ ]
1263
+ }
1264
+ );
1265
+ }
1266
+ function JumpSkeletonLoader({ as, variant = "text", lines = 1, width, height, disableAnimation = false, className = "", children, ...props }) {
1267
+ const theme = useJumpyTheme();
1268
+ const Component = as || "div";
1269
+ if (children) return children;
1270
+ const defaultHeight = variant === "text" ? theme.fontFamily.bodyMedium.fontSize * theme.fontFamily.bodyMedium.lineHeight : variant === "icon" || variant === "avatar" ? sizeValue(theme, JumpySize.large) : sizeValue(theme, JumpySize.large2XL);
1271
+ const radius = variant === "avatar" ? radiusValue(theme, JumpyRadius.full) : radiusValue(theme, JumpyRadius.small);
1272
+ const items = Array.from({ length: variant === "text" ? lines : 1 });
1273
+ return /* @__PURE__ */ jsx("div", { "data-jumpy-component": "skeleton-loader-group", className: cn(className), style: { display: "grid", gap: spacingValue(theme, JumpySpacing.smallXS), width: width || "100%", ...props.style || {} }, children: items.map((_, index) => /* @__PURE__ */ jsx(
1274
+ Component,
1275
+ {
1276
+ "aria-hidden": "true",
1277
+ "data-jumpy-component": "skeleton-loader",
1278
+ style: {
1279
+ width: index === items.length - 1 && items.length > 1 ? "72%" : width || "100%",
1280
+ height: height || defaultHeight,
1281
+ borderRadius: radius,
1282
+ backgroundColor: theme.color.borderSubtle,
1283
+ opacity: disableAnimation ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.medium)
1284
+ }
1285
+ },
1286
+ `skeleton-${lines}-${index}`
1287
+ )) });
1288
+ }
1289
+ var JumpSearchField = forwardRef(function JumpSearchField2({ label = "Search", placeholder = "Search", className = "", ...props }, ref) {
1290
+ const ariaLabelledBy = props["aria-labelledby"];
1291
+ const ariaLabel = ariaLabelledBy ? void 0 : props["aria-label"] || label || placeholder;
1292
+ return /* @__PURE__ */ jsx(JumpBaseInput, { className: cn("jumpy-search-field", className), leading: /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M16.071 14.5q1.509-1.95 1.508-4.5c0-4.1-3.418-7.5-7.54-7.5C5.919 2.5 2.5 5.9 2.5 10s3.418 7.5 7.54 7.5c1.709 0 3.317-.6 4.524-1.5l5.529 5.5 1.407-1.4zm-6.031 1c-3.016 0-5.53-2.5-5.53-5.5s2.515-5.5 5.53-5.5S15.569 7 15.569 10s-2.513 5.5-5.53 5.5z" }) }), children: /* @__PURE__ */ jsx(JumpInput, { ...props, ref, type: "search", placeholder, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }) });
1293
+ });
1294
+ function JumpTooltip({ content, children, className = "", ...props }) {
1295
+ const theme = useJumpyTheme();
1296
+ const [visible, setVisible] = useState(false);
1297
+ return /* @__PURE__ */ jsxs("span", { ...props, "data-jumpy-component": "tooltip", className: cn(className), style: { position: "relative", display: "inline-flex", ...props.style || {} }, onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false), onFocus: () => setVisible(true), onBlur: () => setVisible(false), children: [
1298
+ children,
1299
+ visible ? /* @__PURE__ */ jsx("span", { role: "tooltip", style: { position: "absolute", left: "50%", bottom: `calc(100% + ${spacingValue(theme, JumpySpacing.smallXS)}px)`, transform: "translateX(-50%)", zIndex: 30, width: "max-content", maxWidth: 240, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: theme.color.textPrimary, color: theme.color.surfaceCard, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`, boxShadow: theme.shadow.positiveSmall, fontSize: theme.fontFamily.bodySmall.fontSize, lineHeight: theme.fontFamily.bodySmall.lineHeight }, children: content }) : null
1300
+ ] });
1301
+ }
1302
+ function JumpPopover({ open, defaultOpen = false, onOpenChange, trigger, children, className = "", ...props }) {
1303
+ const theme = useJumpyTheme();
1304
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
1305
+ const visible = open ?? internalOpen;
1306
+ const setVisible = (next) => {
1307
+ setInternalOpen(next);
1308
+ onOpenChange?.(next);
1309
+ };
1310
+ return /* @__PURE__ */ jsxs("span", { "data-jumpy-component": "popover-root", className: cn(className), style: { position: "relative", display: "inline-flex", ...props.style || {} }, children: [
1311
+ /* @__PURE__ */ jsx("span", { onClick: () => setVisible(!visible), children: trigger }),
1312
+ visible ? /* @__PURE__ */ jsx("div", { role: "dialog", "data-jumpy-component": "popover", style: { position: "absolute", top: `calc(100% + ${spacingValue(theme, JumpySpacing.smallXS)}px)`, left: 0, zIndex: 30, minWidth: 220, borderRadius: radiusValue(theme, JumpyRadius.large), border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`, backgroundColor: theme.color.surfaceCard, color: theme.color.textPrimary, boxShadow: theme.shadow.positiveMedium, padding: spacingValue(theme, JumpySpacing.large) }, children }) : null
1313
+ ] });
1314
+ }
1315
+ function JumpMenu({ trigger, triggerProps = {}, open, defaultOpen = false, onOpenChange, header, footer, children, className = "", placement = "bottom-start", closeOnSelect = true, ...props }) {
1316
+ const theme = useJumpyTheme();
1317
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
1318
+ const isOpen = open ?? internalOpen;
1319
+ const menuRef = useRef(null);
1320
+ const insideEventRef = useRef(null);
1321
+ const menuOffset = spacingValue(theme, JumpySpacing.small3XS);
1322
+ const menuPadding = spacingValue(theme, JumpySpacing.medium);
1323
+ const menuMinWidth = sizeValue(theme, JumpySize.large8XL);
1324
+ const { refs, floatingStyles } = useFloating({
1325
+ open: isOpen,
1326
+ onOpenChange: setOpen,
1327
+ placement,
1328
+ strategy: "fixed",
1329
+ whileElementsMounted: autoUpdate,
1330
+ middleware: [
1331
+ offset(menuOffset),
1332
+ flip({ padding: menuPadding }),
1333
+ size({
1334
+ padding: menuPadding,
1335
+ apply({ rects, availableHeight, elements }) {
1336
+ Object.assign(elements.floating.style, {
1337
+ minWidth: `${Math.max(menuMinWidth, rects.reference.width)}px`,
1338
+ maxHeight: `${Math.max(sizeValue(theme, JumpySize.large2XL), availableHeight)}px`
1339
+ });
1340
+ }
1341
+ })
1342
+ ]
1343
+ });
1344
+ function setOpen(next) {
1345
+ setInternalOpen(next);
1346
+ onOpenChange?.(next);
1347
+ }
1348
+ function getMenuItems() {
1349
+ return Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') || []).filter((item) => item.getAttribute("aria-disabled") !== "true" && !item.disabled);
1350
+ }
1351
+ function focusMenuItem(direction) {
1352
+ const menuItems = getMenuItems();
1353
+ if (menuItems.length === 0) return;
1354
+ const activeIndex = menuItems.indexOf(document.activeElement);
1355
+ const nextIndex = direction === "first" ? 0 : direction === "last" ? menuItems.length - 1 : (activeIndex + direction + menuItems.length) % menuItems.length;
1356
+ menuItems[nextIndex]?.focus();
1357
+ }
1358
+ function openAndFocusFirst() {
1359
+ setOpen(true);
1360
+ window.requestAnimationFrame(() => focusMenuItem("first"));
1361
+ }
1362
+ function handleTriggerKeyDown(event) {
1363
+ if (event.key === KeyboardKeys.ARROW_DOWN || event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) {
1364
+ event.preventDefault();
1365
+ openAndFocusFirst();
1366
+ }
1367
+ if (event.key === KeyboardKeys.ARROW_UP) {
1368
+ event.preventDefault();
1369
+ setOpen(true);
1370
+ window.requestAnimationFrame(() => focusMenuItem("last"));
1371
+ }
1372
+ }
1373
+ function handleMenuKeyDown(event) {
1374
+ if (event.key === KeyboardKeys.ESCAPE) {
1375
+ event.preventDefault();
1376
+ setOpen(false);
1377
+ refs.reference.current?.focus?.();
1378
+ }
1379
+ if (event.key === KeyboardKeys.ARROW_DOWN) {
1380
+ event.preventDefault();
1381
+ focusMenuItem(1);
1382
+ }
1383
+ if (event.key === KeyboardKeys.ARROW_UP) {
1384
+ event.preventDefault();
1385
+ focusMenuItem(-1);
1386
+ }
1387
+ if (event.key === KeyboardKeys.HOME) {
1388
+ event.preventDefault();
1389
+ focusMenuItem("first");
1390
+ }
1391
+ if (event.key === KeyboardKeys.END) {
1392
+ event.preventDefault();
1393
+ focusMenuItem("last");
1394
+ }
1395
+ props.onKeyDown?.(event);
1396
+ }
1397
+ useEffect(() => {
1398
+ if (!isOpen) return void 0;
1399
+ function dismissOutside(event) {
1400
+ if (insideEventRef.current !== event) setOpen(false);
1401
+ }
1402
+ document.addEventListener("pointerdown", dismissOutside);
1403
+ document.addEventListener("focusin", dismissOutside);
1404
+ return () => {
1405
+ document.removeEventListener("pointerdown", dismissOutside);
1406
+ document.removeEventListener("focusin", dismissOutside);
1407
+ };
1408
+ });
1409
+ function markInteractionInside(event) {
1410
+ insideEventRef.current = event.nativeEvent;
1411
+ }
1412
+ function handleMenuClick(event) {
1413
+ props.onClick?.(event);
1414
+ if (!closeOnSelect || event.defaultPrevented) return;
1415
+ const item = event.target.closest?.('[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]');
1416
+ if (!item || item.disabled || item.getAttribute("aria-disabled") === "true" || item.closest("[aria-haspopup]")) return;
1417
+ setOpen(false);
1418
+ refs.domReference.current?.focus?.();
1419
+ }
1420
+ return /* @__PURE__ */ jsxs("span", { "data-jumpy-component": "menu-root", className: cn(className), onPointerDown: markInteractionInside, onFocus: markInteractionInside, style: { display: "inline-flex", ...props.style || {} }, children: [
1421
+ /* @__PURE__ */ jsx("span", { ...triggerProps, ref: refs.setReference, "aria-haspopup": "menu", "aria-expanded": isOpen, tabIndex: 0, onClick: () => setOpen(!isOpen), onKeyDown: handleTriggerKeyDown, children: trigger }),
1422
+ isOpen ? /* @__PURE__ */ jsx(JumpyPortal, { children: /* @__PURE__ */ jsxs(
1423
+ "div",
1424
+ {
1425
+ ...props,
1426
+ ref: (node) => {
1427
+ refs.setFloating(node);
1428
+ menuRef.current = node;
1429
+ },
1430
+ "data-jumpy-component": "menu",
1431
+ role: "menu",
1432
+ onClick: handleMenuClick,
1433
+ onKeyDown: handleMenuKeyDown,
1434
+ style: {
1435
+ ...floatingStyles,
1436
+ zIndex: JUMPY_POPUP_Z_INDEX,
1437
+ maxWidth: "min(420px, calc(100vw - 32px))",
1438
+ overflowY: "auto",
1439
+ ...props.style || {}
1440
+ },
1441
+ children: [
1442
+ header ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "menu-header", children: header }) : null,
1443
+ children,
1444
+ footer ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "menu-footer", children: footer }) : null
1445
+ ]
1446
+ }
1447
+ ) }) : null
1448
+ ] });
1449
+ }
1450
+ function JumpMenuItem({ as, children, selected = false, disabled = false, readOnly = false, focusedView = false, leading, trailing, metadata, description, slots = {}, nestedMenu, onClick, ...props }) {
1451
+ const Component = as || "button";
1452
+ const resolvedSlots = {
1453
+ ...slots,
1454
+ leading: slots.leading ?? leading,
1455
+ trailing: nestedMenu ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u203A" }) : slots.trailing ?? trailing,
1456
+ metadata: slots.metadata ?? metadata,
1457
+ description: slots.description ?? description
1458
+ };
1459
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1460
+ /* @__PURE__ */ jsx(
1461
+ JumpListItem,
1462
+ {
1463
+ ...props,
1464
+ as: Component,
1465
+ role: props.role || "menuitem",
1466
+ selected,
1467
+ disabled,
1468
+ readOnly,
1469
+ focusedView,
1470
+ slots: resolvedSlots,
1471
+ "data-jumpy-component": "menu-item",
1472
+ onClick: (event) => {
1473
+ if (disabled || readOnly) return;
1474
+ onClick?.(event);
1475
+ },
1476
+ children
1477
+ }
1478
+ ),
1479
+ nestedMenu || null
1480
+ ] });
1481
+ }
1482
+ function JumpToggleButton({ as, selected, value, disabled = false, onChange, leading, children, className = "", ...props }) {
1483
+ const theme = useJumpyTheme();
1484
+ const group = useContext(JumpyToggleButtonGroupContext);
1485
+ const Component = as || "button";
1486
+ const isButton = Component === "button";
1487
+ const isDisabled = disabled || group?.disabled || false;
1488
+ const isSelected = group && value !== void 0 ? group.value.includes(value) : Boolean(selected);
1489
+ const textStyle = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
1490
+ function handleClick(event) {
1491
+ if (isDisabled) return;
1492
+ if (group && value !== void 0) group.onChange(value);
1493
+ onChange?.(!isSelected);
1494
+ props.onClick?.(event);
1495
+ }
1496
+ return /* @__PURE__ */ jsxs(
1497
+ Component,
1498
+ {
1499
+ ...props,
1500
+ type: isButton ? props.type || "button" : props.type,
1501
+ disabled: isButton ? isDisabled : void 0,
1502
+ "aria-disabled": !isButton && isDisabled ? true : void 0,
1503
+ "aria-pressed": isSelected,
1504
+ "data-jumpy-component": "toggle-button",
1505
+ "data-selected": isSelected ? "true" : "false",
1506
+ "data-disabled": isDisabled ? "true" : "false",
1507
+ className: cn(className),
1508
+ onClick: handleClick,
1509
+ style: {
1510
+ boxSizing: "border-box",
1511
+ position: "relative",
1512
+ display: "inline-flex",
1513
+ alignItems: "center",
1514
+ justifyContent: "center",
1515
+ width: "fit-content",
1516
+ height: sizeValue(theme, JumpySize.large),
1517
+ gap: spacingValue(theme, JumpySpacing.small3XS),
1518
+ padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`,
1519
+ outline: "none",
1520
+ border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-cmp-toggle-button-border-color)`,
1521
+ borderRadius: "var(--jumpy-cmp-toggle-button-border-radius)",
1522
+ backgroundColor: "var(--jumpy-cmp-toggle-button-surface)",
1523
+ color: "var(--jumpy-cmp-toggle-button-text)",
1524
+ cursor: isDisabled ? "not-allowed" : "pointer",
1525
+ userSelect: "none",
1526
+ fontFamily: theme.fontFamily.familyBase,
1527
+ fontSize: textStyle.fontSize,
1528
+ lineHeight: textStyle.lineHeight,
1529
+ fontWeight: textStyle.fontWeight,
1530
+ opacity: isDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
1531
+ ...props.style || {}
1532
+ },
1533
+ children: [
1534
+ leading ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { display: "inline-flex", color: "var(--jumpy-cmp-toggle-button-icon-fill)" }, children: typeof leading === "function" ? leading({ selected: isSelected }) : leading }) : null,
1535
+ children ? /* @__PURE__ */ jsx("span", { style: { display: "inline-flex", alignItems: "center", justifyContent: "center", margin: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px`, whiteSpace: "nowrap" }, children }) : null
1536
+ ]
1537
+ }
1538
+ );
1539
+ }
1540
+ function JumpToggleButtonGroup({ as, selected, defaultSelected = [], onChange, exclusive = false, disabled = false, children, className = "", ...props }) {
1541
+ const theme = useJumpyTheme();
1542
+ const Component = as || "div";
1543
+ const [internalSelected, setInternalSelected] = useState(defaultSelected);
1544
+ const currentSelected = selected ?? internalSelected;
1545
+ function handleToggle(toggledValue) {
1546
+ const nextSelected = currentSelected.includes(toggledValue) ? currentSelected.filter((item) => item !== toggledValue) : exclusive ? [toggledValue] : [...currentSelected, toggledValue];
1547
+ setInternalSelected(nextSelected);
1548
+ onChange?.(nextSelected);
1549
+ }
1550
+ return /* @__PURE__ */ jsx(JumpyToggleButtonGroupContext.Provider, { value: { value: currentSelected, onChange: handleToggle, exclusive, disabled }, children: /* @__PURE__ */ jsx(
1551
+ Component,
1552
+ {
1553
+ ...props,
1554
+ "data-jumpy-component": "toggle-button-group",
1555
+ role: props.role || "toolbar",
1556
+ className: cn(className),
1557
+ style: { display: "inline-flex", flexDirection: "row", alignItems: "center", width: "fit-content", gap: spacingValue(theme, JumpySpacing.small3XS), flexWrap: "wrap", userSelect: "none", ...props.style || {} },
1558
+ children
1559
+ }
1560
+ ) });
1561
+ }
1562
+ function JumpPinInput({ length = 6, value, defaultValue = "", onChange, onComplete, name, mask = false, type = "numeric", error = false, disabled = false, helperText, ariaLabels = {}, className = "", innerClasses = {}, ...props }) {
1563
+ const theme = useJumpyTheme();
1564
+ const normalizedLength = clampNumber(length, 4, 8);
1565
+ const [internalValue, setInternalValue] = useState(defaultValue);
1566
+ const currentValue = String(value ?? internalValue).slice(0, normalizedLength);
1567
+ const refs = useRef([]);
1568
+ const pattern = type === "alphanumeric" ? /^[a-z0-9]$/i : /^\d$/;
1569
+ const helperId = helperText ? `${name || props.id || "jumpy-pin-input"}-helper` : void 0;
1570
+ function commit(nextValue, focusIndex) {
1571
+ const compact = nextValue.slice(0, normalizedLength);
1572
+ setInternalValue(compact);
1573
+ onChange?.(compact);
1574
+ if (compact.length === normalizedLength) onComplete?.(compact);
1575
+ if (focusIndex !== void 0) refs.current[clampNumber(focusIndex, 0, normalizedLength - 1)]?.focus();
1576
+ }
1577
+ function replaceAt(index, char) {
1578
+ const cells = Array.from({ length: normalizedLength }, (_, cellIndex) => currentValue[cellIndex] || "");
1579
+ cells[index] = char;
1580
+ return cells.join("");
1581
+ }
1582
+ const supportingColor = disabled ? theme.color["jumpy-input-regular-disabled-supporting"] : error ? theme.color["jumpy-input-alarm-default-supporting"] : theme.color["jumpy-input-regular-default-supporting"];
1583
+ const textStyle = textStyleValue(theme, JumpyTextStyle.bodyLarge);
1584
+ return /* @__PURE__ */ jsxs("fieldset", { ...props, disabled, "data-jumpy-component": "pin-input", className: cn(className), style: { display: "inline-flex", flexDirection: "column", alignItems: "flex-start", border: 0, margin: 0, padding: 0, ...props.style || {} }, children: [
1585
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: currentValue }) : null,
1586
+ /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "pin-input-cells", className: cn(innerClasses.cells?.className), style: { display: "flex", gap: spacingValue(theme, JumpySpacing.small2XS), cursor: disabled ? "not-allowed" : "text", ...innerClasses.cells?.style || {} }, onClick: (event) => {
1587
+ if (event.target === event.currentTarget) refs.current[Math.min(currentValue.length, normalizedLength - 1)]?.focus();
1588
+ }, children: Array.from({ length: normalizedLength }, (_, index) => /* @__PURE__ */ jsx(
1589
+ "input",
1590
+ {
1591
+ ref: (node) => {
1592
+ refs.current[index] = node;
1593
+ },
1594
+ type: mask ? "password" : "text",
1595
+ inputMode: type === "alphanumeric" ? "text" : "numeric",
1596
+ autoComplete: index === 0 ? "one-time-code" : "off",
1597
+ autoCapitalize: "characters",
1598
+ "aria-label": ariaLabels.cell ? ariaLabels.cell(index + 1, normalizedLength) : `PIN digit ${index + 1} of ${normalizedLength}`,
1599
+ "aria-invalid": error || void 0,
1600
+ "aria-describedby": helperId,
1601
+ disabled,
1602
+ "data-jumpy-slot": "pin-input-cell",
1603
+ "data-status": error ? "alarm" : "regular",
1604
+ "data-disabled": disabled ? "true" : "false",
1605
+ maxLength: 1,
1606
+ value: currentValue[index] || "",
1607
+ onChange: (event) => {
1608
+ const nextRaw = event.target.value.slice(-1);
1609
+ if (!nextRaw) return;
1610
+ if (!pattern.test(nextRaw)) return;
1611
+ commit(replaceAt(index, nextRaw.toUpperCase()), index + 1);
1612
+ },
1613
+ onKeyDown: (event) => {
1614
+ if (event.key === "Backspace") {
1615
+ event.preventDefault();
1616
+ if (currentValue[index]) commit(replaceAt(index, ""), index);
1617
+ else commit(replaceAt(Math.max(index - 1, 0), ""), index - 1);
1618
+ }
1619
+ if (event.key === "ArrowLeft") refs.current[Math.max(index - 1, 0)]?.focus();
1620
+ if (event.key === "ArrowRight") refs.current[Math.min(index + 1, normalizedLength - 1)]?.focus();
1621
+ },
1622
+ onPaste: (event) => {
1623
+ event.preventDefault();
1624
+ const pasted = event.clipboardData.getData("text").split("").filter((char) => pattern.test(char)).join("").slice(0, normalizedLength).toUpperCase();
1625
+ if (pasted) commit(pasted, pasted.length - 1);
1626
+ },
1627
+ className: cn(innerClasses.cell?.className),
1628
+ style: { width: sizeValue(theme, JumpySize.largeXL), height: sizeValue(theme, JumpySize.largeXL), boxSizing: "border-box", appearance: "none", WebkitAppearance: "none", border: 0, outlineWidth: "var(--jumpy-cmp-pin-input-cell-outline-width)", outlineStyle: "solid", outlineColor: "var(--jumpy-cmp-pin-input-cell-border)", outlineOffset: `calc(var(--jumpy-cmp-pin-input-cell-outline-width) * -1)`, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: "var(--jumpy-cmp-pin-input-cell-surface)", color: "var(--jumpy-cmp-pin-input-cell-value)", textAlign: "center", overflow: "hidden", fontFamily: theme.fontFamily.familyBase, fontSize: textStyle.fontSize, lineHeight: textStyle.lineHeight, fontWeight: textStyle.fontWeight, ...innerClasses.cell?.style || {} }
1629
+ },
1630
+ index
1631
+ )) }),
1632
+ helperText ? /* @__PURE__ */ jsx("div", { id: helperId, "data-jumpy-slot": "pin-input-helper", className: cn(innerClasses.supportingText?.className), style: { paddingTop: spacingValue(theme, JumpySpacing.small3XS), color: supportingColor, fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.labelSmall).fontSize, ...innerClasses.supportingText?.style || {} }, children: helperText }) : null
1633
+ ] });
1634
+ }
1635
+ function JumpPageControl({ totalPages = 1, currentPage, defaultPage = 0, maxVisibleDots = 7, disabled = false, disabledPages = [], disableInteraction = false, onPageChange, pageAriaLabels = [], className = "", ...props }) {
1636
+ const theme = useJumpyTheme();
1637
+ const [internalPage, setInternalPage] = useState(defaultPage);
1638
+ const selectedPage = clampNumber(currentPage ?? internalPage, 0, Math.max(totalPages - 1, 0));
1639
+ const visibleCount = clampNumber(maxVisibleDots, 3, Math.max(totalPages, 3));
1640
+ const half = Math.floor(visibleCount / 2);
1641
+ const start = clampNumber(selectedPage - half, 0, Math.max(totalPages - visibleCount, 0));
1642
+ const pages = Array.from({ length: Math.min(totalPages, visibleCount) }, (_, index) => start + index);
1643
+ const showLeft = start > 0;
1644
+ const showRight = start + visibleCount < totalPages;
1645
+ function selectPage(page, pageDisabled) {
1646
+ if (disabled || disableInteraction || pageDisabled) return;
1647
+ setInternalPage(page);
1648
+ onPageChange?.(page);
1649
+ }
1650
+ function renderDot(page, truncation = false) {
1651
+ const selected = page === selectedPage;
1652
+ const pageDisabled = disabledPages.includes(page);
1653
+ const dotSize = truncation ? sizeValue(theme, JumpySize.small5XS) : sizeValue(theme, JumpySize.small4XS);
1654
+ return /* @__PURE__ */ jsx("button", { type: "button", disabled: disabled || pageDisabled || truncation || disableInteraction, "aria-label": truncation ? "More pages" : pageAriaLabels[page] || `Page ${page + 1}`, "aria-current": selected ? "page" : void 0, "data-jumpy-component": "page-control-dot", "data-selected": selected ? "true" : "false", "data-truncation": truncation ? "true" : "false", onClick: () => selectPage(page, pageDisabled), style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: truncation ? sizeValue(theme, JumpySize.small3XS) : sizeValue(theme, JumpySize.small), height: truncation ? sizeValue(theme, JumpySize.small3XS) : sizeValue(theme, JumpySize.small), border: `${borderWidthValue(theme, JumpyBorderWidth.medium)}px solid transparent`, borderRadius: radiusValue(theme, JumpyRadius.small), background: "transparent", cursor: disabled || pageDisabled || truncation || disableInteraction ? "default" : "pointer", opacity: disabled || pageDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid) }, children: /* @__PURE__ */ jsx("span", { style: { display: "block", width: dotSize, height: dotSize, boxSizing: "border-box", borderRadius: selected || truncation ? radiusValue(theme, JumpyRadius.full) : radiusValue(theme, JumpyRadius.small), border: truncation ? 0 : `${borderWidthValue(theme, JumpyBorderWidth.medium)}px solid ${selected ? theme.listItem.active.default.icon : theme.color.borderStrong}`, backgroundColor: selected ? theme.listItem.active.default.icon : truncation ? theme.color.borderStrong : "transparent" } }) }, truncation ? `truncate-${page}` : page);
1655
+ }
1656
+ return /* @__PURE__ */ jsxs("div", { ...props, "data-jumpy-component": "page-control", className: cn(className), style: { position: "relative", overflow: "clip", width: "100%", height: sizeValue(theme, JumpySize.small), display: "flex", justifyContent: "center", alignItems: "center", boxSizing: "border-box", ...props.style || {} }, children: [
1657
+ showLeft ? renderDot(start - 1, true) : null,
1658
+ pages.map((page) => renderDot(page)),
1659
+ showRight ? renderDot(start + visibleCount, true) : null
1660
+ ] });
1661
+ }
1662
+ function textFromNode(node) {
1663
+ if (typeof node === "string" || typeof node === "number") return String(node);
1664
+ if (Array.isArray(node)) return node.map(textFromNode).filter(Boolean).join(" ");
1665
+ if (isValidElement(node)) return textFromNode(node.props.children);
1666
+ return "";
1667
+ }
1668
+ function useJumpSnackbar() {
1669
+ return useContext(JumpySnackbarContext);
1670
+ }
1671
+ function JumpSnackbarProvider({ children, position = "bottom-center", maxSnackbars = 3, offset: offset2 = {}, className = "", ...props }) {
1672
+ const theme = useJumpyTheme();
1673
+ const [snackbars, setSnackbars] = useState([]);
1674
+ const top = position.startsWith("top");
1675
+ const align = position.endsWith("start") ? "flex-start" : position.endsWith("end") ? "flex-end" : "center";
1676
+ const horizontalOffset = offset2.left ?? offset2.right ?? spacingValue(theme, JumpySpacing.large);
1677
+ const verticalOffset = top ? offset2.top ?? spacingValue(theme, JumpySpacing.smallXS) : offset2.bottom ?? spacingValue(theme, JumpySpacing.smallXS);
1678
+ function removeSnackbar(id) {
1679
+ setSnackbars((items) => items.filter((item) => item.id !== id));
1680
+ }
1681
+ function addSnackbar(contentOrData, options = {}) {
1682
+ const id = options.id || `jumpy-snackbar-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1683
+ const isData = contentOrData && typeof contentOrData === "object" && !isValidElement(contentOrData) && ("title" in contentOrData || "description" in contentOrData || "status" in contentOrData);
1684
+ const duration = options.persistent || contentOrData?.persistent ? null : options.duration ?? contentOrData?.duration ?? 4e3;
1685
+ const snackbar = { id, content: contentOrData, isData, createdAt: Date.now() };
1686
+ setSnackbars((items) => [snackbar, ...items.filter((item) => item.id !== id)].slice(0, maxSnackbars));
1687
+ if (duration) window.setTimeout(() => removeSnackbar(id), duration);
1688
+ return id;
1689
+ }
1690
+ const contextValue = { addSnackbar, removeSnackbar, clearAll: () => setSnackbars([]), snackbars };
1691
+ return /* @__PURE__ */ jsxs(JumpySnackbarContext.Provider, { value: contextValue, children: [
1692
+ children,
1693
+ /* @__PURE__ */ jsx("output", { "aria-live": "assertive", "aria-atomic": "true", style: { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, children: snackbars[0] ? textFromNode(snackbars[0].content?.title || snackbars[0].content?.description || snackbars[0].content) : "" }),
1694
+ /* @__PURE__ */ jsx("div", { ...props, "data-jumpy-component": "snackbar-provider", className: cn(className), style: { position: "fixed", zIndex: 1100, left: position.endsWith("start") ? horizontalOffset : position.endsWith("end") ? "auto" : "50%", right: position.endsWith("end") ? horizontalOffset : "auto", top: top ? verticalOffset : "auto", bottom: top ? "auto" : verticalOffset, transform: position.endsWith("center") ? "translateX(-50%)" : void 0, display: "flex", flexDirection: "column", alignItems: align, gap: spacingValue(theme, JumpySpacing.smallXS), width: "min(487px, calc(100vw - 32px))", maxWidth: "50vw", pointerEvents: "none", ...props.style || {} }, children: snackbars.map((snackbar) => snackbar.isData ? /* @__PURE__ */ jsx(JumpBanner, { floating: true, status: snackbar.content.status || "info", title: snackbar.content.title, actions: snackbar.content.slots?.actions, style: { width: "100%", pointerEvents: "auto", boxShadow: theme.shadow.positiveLarge }, children: snackbar.content.description }, snackbar.id) : /* @__PURE__ */ jsx("div", { "data-jumpy-component": "snackbar", style: { width: "100%", pointerEvents: "auto", borderRadius: radiusValue(theme, JumpyRadius.small), boxShadow: theme.shadow.positiveLarge }, children: snackbar.content }, snackbar.id)) })
1695
+ ] });
1696
+ }
1697
+ function JumpTable({ children, showHorizontalBorders = false, disabled = false, stickyHeader = false, density = "regular", containerProps, innerClasses = {}, className = "", ...props }) {
1698
+ const { className: containerClassName, style: containerStyle, ...restContainerProps } = containerProps || {};
1699
+ const contextValue = { showHorizontalBorders, disabled };
1700
+ return /* @__PURE__ */ jsx(JumpyTableContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "table-wrapper", className: innerClasses.wrapper?.className, style: innerClasses.wrapper?.style, children: /* @__PURE__ */ jsx("div", { ...restContainerProps, "data-jumpy-slot": "table-container", "data-sticky-header": stickyHeader ? "true" : "false", className: cn(containerClassName), style: containerStyle, children: /* @__PURE__ */ jsx("table", { ...props, "data-jumpy-component": "table", "data-horizontal-borders": showHorizontalBorders ? "true" : "false", "data-density": density === "condensed" ? "condensed" : void 0, "data-disabled": disabled ? "true" : void 0, role: props.role || "grid", className: cn(className), children }) }) }) });
1701
+ }
1702
+ function JumpTableHead({ as, children, className = "", ...props }) {
1703
+ const Component = as || "thead";
1704
+ return /* @__PURE__ */ jsx(Component, { ...props, "data-jumpy-component": "table-head", role: props.role || "rowgroup", className: cn(className), children });
1705
+ }
1706
+ function JumpTableBody({ as, children, className = "", ...props }) {
1707
+ const Component = as || "tbody";
1708
+ return /* @__PURE__ */ jsx(Component, { ...props, "data-jumpy-component": "table-body", role: props.role || "rowgroup", className: cn(className), children });
1709
+ }
1710
+ function JumpTableFooter({ as, children, className = "", ...props }) {
1711
+ const Component = as || "tfoot";
1712
+ return /* @__PURE__ */ jsx(Component, { ...props, "data-jumpy-component": "table-footer", role: props.role || "rowgroup", className: cn(className), children });
1713
+ }
1714
+ function JumpTableRow({ as, children, selected = false, disabled = false, disableHover = false, verticalAlign = "middle", dragging = false, className = "", onClick, ...props }) {
1715
+ const Component = as || "tr";
1716
+ const tableContext = useContext(JumpyTableContext);
1717
+ const isDisabled = disabled || tableContext?.disabled;
1718
+ const isInteractive = !disableHover && !isDisabled && Boolean(onClick);
1719
+ return /* @__PURE__ */ jsx(
1720
+ Component,
1721
+ {
1722
+ ...props,
1723
+ "data-jumpy-component": "table-row",
1724
+ "data-selectable": isInteractive ? "true" : void 0,
1725
+ "data-disable-hover": disableHover ? "true" : void 0,
1726
+ "data-disabled": isDisabled ? "true" : void 0,
1727
+ "data-state": selected ? "selected" : void 0,
1728
+ "data-vertical-align": verticalAlign === "top" ? "top" : void 0,
1729
+ "data-dragging": dragging ? "true" : void 0,
1730
+ role: props.role || "row",
1731
+ "aria-selected": selected || void 0,
1732
+ "aria-disabled": isDisabled || void 0,
1733
+ tabIndex: isInteractive ? props.tabIndex ?? 0 : props.tabIndex,
1734
+ className: cn(className),
1735
+ onClick: isDisabled ? void 0 : onClick,
1736
+ children
1737
+ }
1738
+ );
1739
+ }
1740
+ function getTableCellTypography(theme, emphasized = false) {
1741
+ return textStyleValue(theme, emphasized ? JumpyTextStyle.bodyMediumEmphasized : JumpyTextStyle.bodyMedium);
1742
+ }
1743
+ function tableCellTextStyle(theme, emphasized = false, style) {
1744
+ const textStyle = getTableCellTypography(theme, emphasized);
1745
+ return {
1746
+ fontFamily: theme.fontFamily.familyBase,
1747
+ fontSize: textStyle.fontSize,
1748
+ lineHeight: textStyle.lineHeight,
1749
+ fontWeight: textStyle.fontWeight,
1750
+ letterSpacing: textStyle.letterSpacing,
1751
+ ...style
1752
+ };
1753
+ }
1754
+ function JumpTableCell({ as, children, selected = false, disabled = false, align, variant = "default", padding = "default", showHorizontalBorder, pinned, pinnedEdge = false, leading, trailing, description, slots, innerClasses = {}, className = "", onClick, ...props }) {
1755
+ const theme = useJumpyTheme();
1756
+ const tableContext = useContext(JumpyTableContext);
1757
+ const Component = as || "td";
1758
+ const isControl = variant === "control" || padding === "checkbox";
1759
+ const resolvedAlign = align || (isControl ? "center" : "start");
1760
+ const cellLeading = slots?.leading ?? leading;
1761
+ const cellTrailing = slots?.trailing ?? trailing;
1762
+ const cellDescription = slots?.description ?? description;
1763
+ const isDisabled = disabled || tableContext?.disabled;
1764
+ const resolvedHorizontalBorder = showHorizontalBorder ?? tableContext?.showHorizontalBorders ?? false;
1765
+ const isInteractive = !isDisabled && Boolean(onClick);
1766
+ return /* @__PURE__ */ jsxs(
1767
+ Component,
1768
+ {
1769
+ ...props,
1770
+ "data-jumpy-component": "table-cell",
1771
+ "data-pinned": pinned || void 0,
1772
+ "data-pinned-edge": pinnedEdge ? "true" : void 0,
1773
+ "data-horizontal-border": resolvedHorizontalBorder ? "true" : "false",
1774
+ "data-state": selected ? "selected" : "default",
1775
+ "data-disabled": isDisabled ? "true" : void 0,
1776
+ "data-align": resolvedAlign,
1777
+ "data-padding": padding,
1778
+ "data-variant": isControl ? "control" : void 0,
1779
+ "data-interactive": isInteractive ? "true" : void 0,
1780
+ role: props.role || "gridcell",
1781
+ "aria-disabled": isDisabled || void 0,
1782
+ "aria-selected": selected || void 0,
1783
+ tabIndex: props.tabIndex ?? (isInteractive ? 0 : void 0),
1784
+ className: cn(className),
1785
+ onClick: isDisabled ? void 0 : onClick,
1786
+ children: [
1787
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-jumpy-slot": "table-cell-surface" }),
1788
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "table-cell-inner", "data-align": resolvedAlign, className: innerClasses.inner?.className, style: innerClasses.inner?.style, children: [
1789
+ cellLeading ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-leading", className: innerClasses.leading?.className, style: innerClasses.leading?.style, children: cellLeading }) : null,
1790
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "table-cell-content", className: innerClasses.content?.className, style: innerClasses.content?.style, children: [
1791
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-text", className: innerClasses.text?.className, style: tableCellTextStyle(theme, false, innerClasses.text?.style), children }),
1792
+ cellDescription ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-description", className: innerClasses.description?.className, style: tableCellTextStyle(theme, false, innerClasses.description?.style), children: cellDescription }) : null
1793
+ ] }),
1794
+ cellTrailing ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-trailing", className: innerClasses.trailing?.className, style: innerClasses.trailing?.style, children: cellTrailing }) : null
1795
+ ] })
1796
+ ]
1797
+ }
1798
+ );
1799
+ }
1800
+ function getSortMapping(currentOrder, ascValue, descValue, defaultValue) {
1801
+ if (currentOrder === "ascending" || currentOrder === "asc") return ascValue;
1802
+ if (currentOrder === "descending" || currentOrder === "desc") return descValue;
1803
+ return defaultValue;
1804
+ }
1805
+ function getNextSortOrder(order) {
1806
+ if (order === "ascending" || order === "asc") return "desc";
1807
+ if (order === "descending" || order === "desc") return "default";
1808
+ return "asc";
1809
+ }
1810
+ function getSortOrder(sortDirection) {
1811
+ return getSortMapping(sortDirection, "asc", "desc", "default");
1812
+ }
1813
+ function getSortActionLabel(currentOrder, columnName) {
1814
+ if (columnName) {
1815
+ return getSortMapping(currentOrder, `Sort ${columnName} descending`, `Clear sort on ${columnName}`, `Sort ${columnName} ascending`);
1816
+ }
1817
+ return getSortMapping(currentOrder, "Sort descending", "Clear sort", "Sort ascending");
1818
+ }
1819
+ function participatesInSelection(row) {
1820
+ return row.getCanSelect() || (row.subRows?.some(participatesInSelection) ?? false);
1821
+ }
1822
+ function getSelectionChildren(row) {
1823
+ const children = row.subRows?.filter(participatesInSelection);
1824
+ return children?.length ? children : void 0;
1825
+ }
1826
+ function isRowFullySelected(row) {
1827
+ const children = getSelectionChildren(row);
1828
+ if (children) return children.every(isRowFullySelected);
1829
+ return row.getCanSelect() && row.getIsSelected();
1830
+ }
1831
+ function applySubtreeSelection(row, selected) {
1832
+ if (row.getCanSelect() && row.getIsSelected() !== selected) {
1833
+ row.toggleSelected(selected, { selectChildren: false });
1834
+ }
1835
+ row.subRows?.forEach((subRow) => applySubtreeSelection(subRow, selected));
1836
+ }
1837
+ function getRowCheckboxProps(row, disabled) {
1838
+ const checked = isRowFullySelected(row);
1839
+ return {
1840
+ checked,
1841
+ indeterminate: !checked && row.getIsSomeSelected(),
1842
+ disabled: disabled || !row.getCanSelect(),
1843
+ "aria-label": `Select row ${row.id}`,
1844
+ onChange: () => applySubtreeSelection(row, !isRowFullySelected(row))
1845
+ };
1846
+ }
1847
+ function getHeaderCheckboxProps(table, disabled) {
1848
+ const topRows = table.getRowModel?.().rows.filter((row) => (row.depth ?? 0) === 0 && participatesInSelection(row));
1849
+ const isTree = topRows?.some((row) => getSelectionChildren(row)) ?? false;
1850
+ const checked = isTree && topRows ? topRows.every(isRowFullySelected) : table.getIsAllRowsSelected();
1851
+ return {
1852
+ checked,
1853
+ indeterminate: !checked && table.getIsSomeRowsSelected(),
1854
+ disabled,
1855
+ "aria-label": "Select all rows",
1856
+ onChange: table.getToggleAllRowsSelectedHandler()
1857
+ };
1858
+ }
1859
+ function sortSymbol(order) {
1860
+ if (order === "ascending" || order === "asc") return "\u2191";
1861
+ if (order === "descending" || order === "desc") return "\u2193";
1862
+ return "\u2195";
1863
+ }
1864
+ function ariaSortOrder(order) {
1865
+ if (order === "ascending" || order === "asc") return "ascending";
1866
+ if (order === "descending" || order === "desc") return "descending";
1867
+ if (order === "default" || order === "none" || order === false) return "none";
1868
+ return void 0;
1869
+ }
1870
+ function JumpTableHeaderCell({ as, children, selected = false, disabled = false, align, variant = "default", padding = "default", showHorizontalBorder, pinned, pinnedEdge = false, sortable = false, sortOrder = "default", onSortChange, ariaLabels, leading, trailing, slots, innerClasses = {}, className = "", ...props }) {
1871
+ const theme = useJumpyTheme();
1872
+ const tableContext = useContext(JumpyTableContext);
1873
+ const Component = as || "th";
1874
+ const isControl = variant === "control" || padding === "checkbox";
1875
+ const resolvedAlign = align || (isControl ? "center" : "start");
1876
+ const cellLeading = slots?.leading ?? leading;
1877
+ const cellTrailing = slots?.trailing ?? trailing;
1878
+ const isDisabled = disabled || tableContext?.disabled;
1879
+ const resolvedHorizontalBorder = showHorizontalBorder ?? tableContext?.showHorizontalBorders ?? false;
1880
+ const participatesInSort = sortable || sortOrder !== "default" && sortOrder !== "none";
1881
+ const ariaSort = participatesInSort ? ariaSortOrder(sortOrder) : void 0;
1882
+ return /* @__PURE__ */ jsxs(
1883
+ Component,
1884
+ {
1885
+ ...props,
1886
+ "data-jumpy-component": "table-header-cell",
1887
+ "data-pinned": pinned || void 0,
1888
+ "data-pinned-edge": pinnedEdge ? "true" : void 0,
1889
+ "data-horizontal-border": resolvedHorizontalBorder ? "true" : "false",
1890
+ "data-sortable": sortable ? "true" : "false",
1891
+ "data-sort-order": sortOrder,
1892
+ "data-disabled": isDisabled ? "true" : void 0,
1893
+ "data-state": selected ? "selected" : "default",
1894
+ "data-align": resolvedAlign,
1895
+ "data-padding": padding,
1896
+ "data-variant": isControl ? "control" : void 0,
1897
+ role: props.role || "columnheader",
1898
+ "aria-sort": ariaSort,
1899
+ className: cn(className),
1900
+ children: [
1901
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-jumpy-slot": "table-cell-surface" }),
1902
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "table-cell-inner", "data-align": resolvedAlign, className: innerClasses.inner?.className, style: innerClasses.inner?.style, children: [
1903
+ cellLeading ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-leading", className: innerClasses.leading?.className, style: innerClasses.leading?.style, children: cellLeading }) : null,
1904
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-content", className: innerClasses.label?.className, style: innerClasses.label?.style, children: /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "table-cell-text", className: innerClasses.labelText?.className, style: tableCellTextStyle(theme, true, innerClasses.labelText?.style), children }) }),
1905
+ cellTrailing || sortable ? /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "table-cell-trailing", className: innerClasses.trailing?.className, style: innerClasses.trailing?.style, children: [
1906
+ sortable ? /* @__PURE__ */ jsx(JumpIconButton, { variant: "secondary", disabled: isDisabled, "aria-label": typeof ariaLabels?.sort === "function" ? ariaLabels.sort(sortOrder) : ariaLabels?.sort || getSortActionLabel(sortOrder, typeof children === "string" ? children : void 0), onClick: () => onSortChange?.(getNextSortOrder(sortOrder)), children: sortSymbol(sortOrder) }) : null,
1907
+ cellTrailing
1908
+ ] }) : null
1909
+ ] })
1910
+ ]
1911
+ }
1912
+ );
1913
+ }
1914
+ function ChevronIcon({ expanded }) {
1915
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: expanded ? "M7.4 15.4 12 10.8l4.6 4.6 1.4-1.4-6-6-6 6 1.4 1.4Z" : "M7.4 8.6 12 13.2l4.6-4.6 1.4 1.4-6 6-6-6 1.4-1.4Z" }) });
1916
+ }
1917
+ function JumpExpandable({ as, variant = "filled", disabled = false, expanded, defaultExpanded = false, onChange, slots, innerClasses = {}, className = "", children, ...props }) {
1918
+ const Component = as || "div";
1919
+ const context = useContext(JumpyExpandableContext);
1920
+ const panelId = useId();
1921
+ const isDisabled = disabled || context?.disabled;
1922
+ const isControlled = expanded !== void 0 || context?.expanded !== void 0;
1923
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultExpanded);
1924
+ const isExpanded = isControlled ? Boolean(expanded ?? context?.expanded) : uncontrolledExpanded;
1925
+ const label = slots?.label;
1926
+ const description = slots?.description;
1927
+ const leading = slots?.leading;
1928
+ const trailing = slots?.trailing;
1929
+ function handleToggle() {
1930
+ if (isDisabled) return;
1931
+ const nextExpanded = !isExpanded;
1932
+ context?.onExpandedChange?.(nextExpanded);
1933
+ if (!isControlled) setUncontrolledExpanded(nextExpanded);
1934
+ onChange?.(nextExpanded);
1935
+ }
1936
+ const header = /* @__PURE__ */ jsxs(
1937
+ "button",
1938
+ {
1939
+ ref: context?.buttonRef,
1940
+ type: "button",
1941
+ "data-jumpy-slot": "expandable-header",
1942
+ className: innerClasses.header?.className,
1943
+ style: innerClasses.header?.style,
1944
+ onClick: handleToggle,
1945
+ disabled: isDisabled,
1946
+ "aria-controls": panelId,
1947
+ "aria-expanded": isExpanded,
1948
+ children: [
1949
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "expandable-slots-container", children: [
1950
+ leading,
1951
+ label || description ? /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "expandable-header-content", className: innerClasses.headerContent?.className, style: innerClasses.headerContent?.style, children: [
1952
+ label ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "expandable-label", className: innerClasses.label?.className, style: innerClasses.label?.style, children: label }) : null,
1953
+ description ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "expandable-description", className: innerClasses.description?.className, style: innerClasses.description?.style, children: description }) : null
1954
+ ] }) : null
1955
+ ] }),
1956
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "expandable-slots-container", children: [
1957
+ trailing,
1958
+ !isDisabled ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "expandable-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, children: /* @__PURE__ */ jsx(ChevronIcon, { expanded: isExpanded }) }) : null
1959
+ ] })
1960
+ ]
1961
+ }
1962
+ );
1963
+ return /* @__PURE__ */ jsxs(Component, { ...props, "data-jumpy-component": "expandable", "data-variant": variant, className: cn(className), children: [
1964
+ context?.headingLevel ? /* @__PURE__ */ jsx(context.headingLevel, { "data-jumpy-slot": "expandable-header-wrapper", children: header }) : header,
1965
+ /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "expandable-content-wrapper", "data-expanded": isExpanded ? "true" : "false", children: /* @__PURE__ */ jsx("div", { id: panelId, "data-jumpy-slot": "expandable-panel", className: innerClasses.panel?.className, style: innerClasses.panel?.style, children }) })
1966
+ ] });
1967
+ }
1968
+ function headingTag(level) {
1969
+ if (!level) return void 0;
1970
+ return `h${Math.min(6, Math.max(1, Number(level)))}`;
1971
+ }
1972
+ function focusExpandableButton(buttonRefs, index) {
1973
+ const nextButton = buttonRefs.current[index];
1974
+ nextButton?.focus();
1975
+ }
1976
+ function JumpExpandableGroup({ as, multiple = false, disabled = false, headingLevel, onChange, innerClasses = {}, className = "", children, ...props }) {
1977
+ const Component = as || "div";
1978
+ const buttonRefs = useRef([]);
1979
+ const childArray = Children.toArray(children);
1980
+ const [expandedIndices, setExpandedIndices] = useState(() => {
1981
+ const initial = /* @__PURE__ */ new Set();
1982
+ childArray.forEach((child, index) => {
1983
+ if (isValidElement(child) && (child.props.expanded || child.props.defaultExpanded)) initial.add(index);
1984
+ });
1985
+ return initial;
1986
+ });
1987
+ function updateExpanded(index, nextExpanded) {
1988
+ setExpandedIndices((current) => {
1989
+ const next = new Set(current);
1990
+ if (nextExpanded) {
1991
+ if (!multiple) next.clear();
1992
+ next.add(index);
1993
+ } else {
1994
+ next.delete(index);
1995
+ }
1996
+ onChange?.(Array.from(next).sort((a, b) => a - b));
1997
+ return next;
1998
+ });
1999
+ }
2000
+ function handleKeyDown(event) {
2001
+ const currentIndex = buttonRefs.current.findIndex((button) => button === event.target);
2002
+ if (currentIndex === -1) {
2003
+ props.onKeyDown?.(event);
2004
+ return;
2005
+ }
2006
+ if (event.key === KeyboardKeys.ARROW_DOWN) {
2007
+ event.preventDefault();
2008
+ focusExpandableButton(buttonRefs, (currentIndex + 1) % buttonRefs.current.length);
2009
+ } else if (event.key === KeyboardKeys.ARROW_UP) {
2010
+ event.preventDefault();
2011
+ focusExpandableButton(buttonRefs, (currentIndex - 1 + buttonRefs.current.length) % buttonRefs.current.length);
2012
+ } else if (event.key === KeyboardKeys.HOME) {
2013
+ event.preventDefault();
2014
+ focusExpandableButton(buttonRefs, 0);
2015
+ } else if (event.key === KeyboardKeys.END) {
2016
+ event.preventDefault();
2017
+ focusExpandableButton(buttonRefs, buttonRefs.current.length - 1);
2018
+ }
2019
+ props.onKeyDown?.(event);
2020
+ }
2021
+ return /* @__PURE__ */ jsx(Component, { ...props, "data-jumpy-component": "expandable-group", "aria-disabled": disabled || void 0, className: cn(className), onKeyDown: handleKeyDown, children: childArray.map((child, index) => {
2022
+ const contextValue = {
2023
+ headingLevel: headingTag(headingLevel),
2024
+ disabled,
2025
+ expanded: expandedIndices.has(index),
2026
+ onExpandedChange: (nextExpanded) => updateExpanded(index, nextExpanded),
2027
+ buttonRef: (element) => {
2028
+ buttonRefs.current[index] = element;
2029
+ }
2030
+ };
2031
+ return /* @__PURE__ */ jsxs(JumpyExpandableContext.Provider, { value: contextValue, children: [
2032
+ child,
2033
+ index < childArray.length - 1 ? /* @__PURE__ */ jsx(JumpDivider, { direction: "horizontal", "data-jumpy-slot": "expandable-group-divider", className: innerClasses.divider?.className, style: innerClasses.divider?.style }) : null
2034
+ ] }, child.key || index);
2035
+ }) });
2036
+ }
2037
+ var JUMPY_POPUP_Z_INDEX = 1200;
2038
+ function JumpyPortal({ children }) {
2039
+ const theme = useJumpyTheme();
2040
+ const scope = useContext(JumpyThemeScopeContext);
2041
+ return /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx("div", { "data-jumpy-theme": "true", "data-jumpy-scope": scope || void 0, "data-jumpy-portal": "true", style: { ...!scope ? themeToCSSVariables(theme) : {}, fontFamily: theme.fontFamily.familyBase }, children }) });
2042
+ }
2043
+ function JumpyModalLayer({ children, onVisibleChange, style, onPointerDown, ...props }) {
2044
+ const theme = useJumpyTheme();
2045
+ const { refs, context } = useFloating({ open: true, onOpenChange: onVisibleChange });
2046
+ return /* @__PURE__ */ jsx(JumpyPortal, { children: /* @__PURE__ */ jsx(FloatingOverlay, { ...props, lockScroll: true, onPointerDown: (event) => {
2047
+ onPointerDown?.(event);
2048
+ if (event.target === event.currentTarget) event.preventDefault();
2049
+ }, style, children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, outsideElementsInert: true, restoreFocus: true, children: cloneElement(children, {
2050
+ ref: refs.setFloating,
2051
+ tabIndex: -1,
2052
+ onKeyDown(event) {
2053
+ children.props.onKeyDown?.(event);
2054
+ const nestedPopup = !event.currentTarget.contains(event.target) || event.target.closest('[aria-haspopup][aria-expanded="true"]');
2055
+ if (event.key === KeyboardKeys.ESCAPE && !event.defaultPrevented && !nestedPopup) {
2056
+ event.preventDefault();
2057
+ event.stopPropagation();
2058
+ onVisibleChange?.(false);
2059
+ }
2060
+ }
2061
+ }) }) }) });
2062
+ }
2063
+ function JumpSheet({ visible = false, onVisibleChange, position = "right", title, subtitle, footer, children, className = "", ...props }) {
2064
+ const theme = useJumpyTheme();
2065
+ const vertical = position === "left" || position === "right";
2066
+ if (!visible) return null;
2067
+ return /* @__PURE__ */ jsx(JumpyModalLayer, { onVisibleChange, "data-jumpy-component": "sheet-overlay", role: "presentation", onClick: (event) => {
2068
+ if (event.target === event.currentTarget) onVisibleChange?.(false);
2069
+ }, style: { position: "fixed", inset: 0, zIndex: 50, backgroundColor: theme.color.surfaceOverlay }, children: /* @__PURE__ */ jsxs("aside", { ...props, "data-jumpy-component": "sheet", className: cn(className), role: "dialog", "aria-modal": "true", "aria-label": title || props["aria-label"], tabIndex: -1, onClick: (event) => {
2070
+ event.stopPropagation();
2071
+ props.onClick?.(event);
2072
+ }, style: { position: "fixed", top: position === "bottom" ? "auto" : 0, bottom: position === "top" ? "auto" : 0, left: position === "right" ? "auto" : 0, right: position === "left" ? "auto" : 0, width: vertical ? "min(420px, 100vw)" : "100vw", height: vertical ? "100dvh" : "min(420px, 100dvh)", backgroundColor: theme.color.surfaceCard, color: theme.color.textPrimary, boxShadow: theme.shadow.positiveMedium, display: "flex", flexDirection: "column", ...props.style || {} }, children: [
2073
+ /* @__PURE__ */ jsxs("div", { style: { padding: spacingValue(theme, JumpySpacing.large), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` }, children: [
2074
+ title ? /* @__PURE__ */ jsx(JumpTypography, { variant: "titleMediumEmphasized", children: title }) : null,
2075
+ subtitle ? /* @__PURE__ */ jsx(JumpTypography, { variant: "bodySmall", color: "secondary", children: subtitle }) : null
2076
+ ] }),
2077
+ /* @__PURE__ */ jsx("div", { style: { padding: spacingValue(theme, JumpySpacing.large), flex: 1, overflow: "auto" }, children }),
2078
+ footer ? /* @__PURE__ */ jsx("div", { style: { padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` }, children: footer }) : null
2079
+ ] }) });
2080
+ }
2081
+ function JumpyNavigationIcon({ type, size: size2 = 24 }) {
2082
+ const paths = {
2083
+ menu: "M4 6h16M4 12h16M4 18h16",
2084
+ close: "M6 6l12 12M18 6 6 18",
2085
+ back: "M15 18l-6-6 6-6",
2086
+ more: "M5 12h.01M12 12h.01M19 12h.01"
2087
+ };
2088
+ return /* @__PURE__ */ jsx("svg", { width: size2, height: size2, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: type === "more" ? 3 : 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", "data-jumpy-stroke-icon": "true", style: { display: "block" }, children: /* @__PURE__ */ jsx("path", { d: paths[type] }) });
2089
+ }
2090
+ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, titleAlign = "start", titleHidden = false, ariaLabels = {}, className = "", ...props }) {
2091
+ const theme = useJumpyTheme();
2092
+ const [scrolled, setScrolled] = useState(false);
2093
+ const titleStyle = textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized);
2094
+ const subtitleStyle = textStyleValue(theme, JumpyTextStyle.bodySmall);
2095
+ useEffect(() => {
2096
+ if (!sticky) return void 0;
2097
+ const update = () => setScrolled(window.scrollY > 0);
2098
+ update();
2099
+ window.addEventListener("scroll", update, { passive: true });
2100
+ return () => window.removeEventListener("scroll", update);
2101
+ }, [sticky]);
2102
+ const leadingType = leading === "menu" || leading === "back" ? leading : void 0;
2103
+ const leadingNode = leading === "menu" ? /* @__PURE__ */ jsx(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.menu || "Open navigation", "aria-haspopup": "dialog", "aria-expanded": menuOpen, onClick: onMenu, children: /* @__PURE__ */ jsx(JumpyNavigationIcon, { type: "menu" }) }) : leading === "back" ? /* @__PURE__ */ jsx(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.back || "Back", onClick: onBack, children: /* @__PURE__ */ jsx(JumpyNavigationIcon, { type: "back" }) }) : leading;
2104
+ return /* @__PURE__ */ jsxs(
2105
+ "header",
2106
+ {
2107
+ ...props,
2108
+ "data-jumpy-component": "top-bar",
2109
+ "data-sticky": sticky ? "true" : "false",
2110
+ "data-scrolled": scrolled ? "true" : "false",
2111
+ "data-title-align": titleAlign,
2112
+ "data-title-hidden": titleHidden ? "true" : "false",
2113
+ className: cn(className),
2114
+ style: {
2115
+ boxSizing: "border-box",
2116
+ position: sticky ? "sticky" : "relative",
2117
+ top: 0,
2118
+ zIndex: 40,
2119
+ alignItems: "center",
2120
+ gap: spacingValue(theme, JumpySpacing.medium),
2121
+ minHeight: `calc(${sizeValue(theme, JumpySize.large2XL)}px + env(safe-area-inset-top, 0px))`,
2122
+ padding: `calc(${spacingValue(theme, JumpySpacing.smallXS)}px + env(safe-area-inset-top, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) ${spacingValue(theme, JumpySpacing.smallXS)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`,
2123
+ color: "var(--jumpy-color-text-primary)",
2124
+ fontFamily: theme.fontFamily.familyBase,
2125
+ ...props.style || {}
2126
+ },
2127
+ children: [
2128
+ leadingNode ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "top-bar-leading", "data-leading": leadingType, style: { minWidth: 0, maxWidth: "100%", marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: leadingNode }) : null,
2129
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "top-bar-title", "aria-hidden": titleHidden || void 0, style: titleAlign === "center" ? {
2130
+ display: "flex",
2131
+ flexDirection: "column",
2132
+ alignItems: "center",
2133
+ textAlign: "center",
2134
+ minWidth: 0,
2135
+ opacity: titleHidden ? 0 : 1
2136
+ } : { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0, opacity: titleHidden ? 0 : 1 }, children: [
2137
+ title ? /* @__PURE__ */ jsx("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: titleStyle.fontSize, lineHeight: titleStyle.lineHeight, fontWeight: titleStyle.fontWeight }, children: title }) : null,
2138
+ subtitle ? /* @__PURE__ */ jsx("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--jumpy-color-text-secondary)", fontSize: subtitleStyle.fontSize, lineHeight: subtitleStyle.lineHeight }, children: subtitle }) : null
2139
+ ] }),
2140
+ trailing ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "top-bar-trailing", style: { display: "inline-flex", flex: "0 0 auto", marginLeft: "auto", alignItems: "center", gap: spacingValue(theme, JumpySpacing.smallXS) }, children: trailing }) : null
2141
+ ]
2142
+ }
2143
+ );
2144
+ }
2145
+ function JumpTabBar({ children, className = "", "aria-label": ariaLabel = "Main navigation", ...props }) {
2146
+ const theme = useJumpyTheme();
2147
+ return /* @__PURE__ */ jsx(
2148
+ "div",
2149
+ {
2150
+ "data-jumpy-component": "tab-bar",
2151
+ className: cn(className),
2152
+ style: {
2153
+ boxSizing: "border-box",
2154
+ position: "sticky",
2155
+ bottom: 0,
2156
+ zIndex: 40,
2157
+ padding: `0 calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-right, 0px)) calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-bottom, 0px)) calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-left, 0px))`,
2158
+ pointerEvents: "none"
2159
+ },
2160
+ children: /* @__PURE__ */ jsx(
2161
+ "nav",
2162
+ {
2163
+ ...props,
2164
+ "aria-label": ariaLabel,
2165
+ "data-jumpy-slot": "tab-bar-surface",
2166
+ style: {
2167
+ boxSizing: "border-box",
2168
+ display: "flex",
2169
+ gap: spacingValue(theme, JumpySpacing.small4XS),
2170
+ padding: spacingValue(theme, JumpySpacing.small3XS),
2171
+ borderRadius: radiusValue(theme, JumpyRadius.large2XL),
2172
+ border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`,
2173
+ boxShadow: theme.shadow.positiveMedium,
2174
+ pointerEvents: "auto",
2175
+ fontFamily: theme.fontFamily.familyBase,
2176
+ ...props.style || {}
2177
+ },
2178
+ children
2179
+ }
2180
+ )
2181
+ }
2182
+ );
2183
+ }
2184
+ function JumpTabBarItem({ as, icon, badge, selected = false, children, className = "", ...props }) {
2185
+ const theme = useJumpyTheme();
2186
+ const Component = as || (props.href ? "a" : "button");
2187
+ const labelStyle = textStyleValue(theme, JumpyTextStyle.labelSmall);
2188
+ const badgeNode = typeof badge === "number" ? badge > 0 ? /* @__PURE__ */ jsx(JumpBadge, { size: "small", status: "active", "aria-label": void 0, role: "presentation", "aria-live": "off", children: badge > 99 ? "99+" : badge }) : null : badge;
2189
+ return /* @__PURE__ */ jsxs(
2190
+ Component,
2191
+ {
2192
+ ...props,
2193
+ type: Component === "button" ? props.type || "button" : props.type,
2194
+ "aria-current": selected ? props["aria-current"] || "page" : props["aria-current"],
2195
+ "data-jumpy-component": "tab-bar-item",
2196
+ "data-selected": selected ? "true" : "false",
2197
+ className: cn(className),
2198
+ style: {
2199
+ boxSizing: "border-box",
2200
+ flex: "1 1 0",
2201
+ minWidth: 0,
2202
+ display: "flex",
2203
+ flexDirection: "column",
2204
+ alignItems: "center",
2205
+ justifyContent: "center",
2206
+ gap: spacingValue(theme, JumpySpacing.small4XS),
2207
+ minHeight: sizeValue(theme, JumpySize.large2XL),
2208
+ padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`,
2209
+ border: 0,
2210
+ borderRadius: radiusValue(theme, JumpyRadius.largeXL),
2211
+ backgroundColor: selected ? "var(--jumpy-interactive-active-regular-default-surface)" : "transparent",
2212
+ color: selected ? "var(--jumpy-interactive-active-regular-default-text)" : "var(--jumpy-color-text-secondary)",
2213
+ textDecoration: "none",
2214
+ cursor: "pointer",
2215
+ fontFamily: theme.fontFamily.familyBase,
2216
+ fontSize: labelStyle.fontSize,
2217
+ lineHeight: labelStyle.lineHeight,
2218
+ fontWeight: labelStyle.fontWeight,
2219
+ ...props.style || {}
2220
+ },
2221
+ children: [
2222
+ icon ? /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "tab-bar-item-icon", "aria-hidden": "true", style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: sizeValue(theme, JumpySize.smallXS), height: sizeValue(theme, JumpySize.smallXS) }, children: [
2223
+ icon,
2224
+ badgeNode ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "tab-bar-item-badge", style: { position: "absolute", top: -spacingValue(theme, JumpySpacing.smallXS), left: "50%", marginLeft: spacingValue(theme, JumpySpacing.small3XS), display: "inline-flex" }, children: badgeNode }) : null
2225
+ ] }) : null,
2226
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "tab-bar-item-label", style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children })
2227
+ ]
2228
+ }
2229
+ );
2230
+ }
2231
+ var jumpyLocations = { current: null, previous: null };
2232
+ function jumpyNormalizeUrl(url) {
2233
+ const resolved = new URL(url, window.location.href);
2234
+ const path = resolved.pathname.length > 1 ? resolved.pathname.replace(/\/+$/, "") : resolved.pathname;
2235
+ return `${resolved.origin}${path}${resolved.search === "?" ? "" : resolved.search}`;
2236
+ }
2237
+ function jumpyPreviousEntryIs(href) {
2238
+ const target = jumpyNormalizeUrl(href);
2239
+ const navigation = window.navigation;
2240
+ if (navigation && typeof navigation.entries === "function" && navigation.currentEntry) {
2241
+ if (!navigation.canGoBack) return false;
2242
+ const previous = navigation.entries()[navigation.currentEntry.index - 1];
2243
+ return Boolean(previous?.url) && jumpyNormalizeUrl(previous.url) === target;
2244
+ }
2245
+ return Boolean(jumpyLocations.previous) && jumpyNormalizeUrl(jumpyLocations.previous) === target && window.history.length > 1;
2246
+ }
2247
+ function JumpyAppShellBack({ back, variant, ariaLabels }) {
2248
+ const theme = useJumpyTheme();
2249
+ const Component = back.as || (back.href ? "a" : "button");
2250
+ const textStyle = textStyleValue(theme, variant === "inline" ? JumpyTextStyle.bodySmallEmphasized : JumpyTextStyle.bodyMedium);
2251
+ const accessibleName = ariaLabels.backTo ? ariaLabels.backTo(back.label) : `Back to ${back.label}`;
2252
+ function handleClick(event) {
2253
+ back.onClick?.(event);
2254
+ if (event.defaultPrevented || !back.href || back.history === false) return;
2255
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
2256
+ if (!jumpyPreviousEntryIs(back.href)) return;
2257
+ event.preventDefault();
2258
+ window.history.back();
2259
+ }
2260
+ return /* @__PURE__ */ jsxs(
2261
+ Component,
2262
+ {
2263
+ href: back.href,
2264
+ type: Component === "button" ? "button" : void 0,
2265
+ "aria-label": accessibleName,
2266
+ "data-jumpy-slot": "app-shell-back",
2267
+ "data-variant": variant,
2268
+ onClick: handleClick,
2269
+ style: {
2270
+ boxSizing: "border-box",
2271
+ alignItems: "center",
2272
+ gap: spacingValue(theme, JumpySpacing.small4XS),
2273
+ maxWidth: "100%",
2274
+ minWidth: 0,
2275
+ minHeight: variant === "inline" ? void 0 : sizeValue(theme, JumpySize.largeXL),
2276
+ margin: variant === "inline" ? `0 0 ${spacingValue(theme, JumpySpacing.small3XS)}px ${-spacingValue(theme, JumpySpacing.small3XS)}px` : 0,
2277
+ padding: variant === "inline" ? 0 : `0 ${spacingValue(theme, JumpySpacing.smallXS)}px 0 0`,
2278
+ border: 0,
2279
+ borderRadius: radiusValue(theme, JumpyRadius.small),
2280
+ background: "transparent",
2281
+ color: "var(--jumpy-interactive-active-regular-default-text)",
2282
+ textDecoration: "none",
2283
+ cursor: "pointer",
2284
+ fontFamily: theme.fontFamily.familyBase,
2285
+ fontSize: textStyle.fontSize,
2286
+ lineHeight: textStyle.lineHeight,
2287
+ fontWeight: textStyle.fontWeight
2288
+ },
2289
+ children: [
2290
+ /* @__PURE__ */ jsx(JumpyNavigationIcon, { type: "back", size: variant === "inline" ? 18 : 24 }),
2291
+ /* @__PURE__ */ jsx("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: back.label })
2292
+ ]
2293
+ }
2294
+ );
2295
+ }
2296
+ function JumpyAppShellActions({ actions, variant, ariaLabels }) {
2297
+ const theme = useJumpyTheme();
2298
+ if (!actions.length) return null;
2299
+ const gap = spacingValue(theme, JumpySpacing.smallXS);
2300
+ if (variant === "full") {
2301
+ return /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "full", style: { alignItems: "center", gap, flexWrap: "wrap", justifyContent: "flex-end" }, children: actions.map((action, index) => /* @__PURE__ */ jsx(JumpButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant || "secondary", leadingIcon: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index)) });
2302
+ }
2303
+ const visible = actions.length > 2 ? actions.slice(0, 1) : actions;
2304
+ const overflow = actions.length > 2 ? actions.slice(1) : [];
2305
+ return /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "compact", style: { alignItems: "center", gap }, children: [
2306
+ visible.map((action, index) => /* @__PURE__ */ jsx(JumpIconButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant === "primary" ? "primary" : "borderless", "aria-label": action.label, disabled: action.disabled, onClick: action.onClick, children: action.icon }, action.id ?? index)),
2307
+ overflow.length ? /* @__PURE__ */ jsx(
2308
+ JumpMenu,
2309
+ {
2310
+ placement: "bottom-end",
2311
+ triggerProps: { role: "button", "aria-label": ariaLabels.moreActions || "More actions", "data-jumpy-slot": "app-shell-actions-more" },
2312
+ trigger: /* @__PURE__ */ jsx(JumpIconButton, { as: "span", variant: "borderless", tabIndex: -1, "aria-hidden": "true", children: /* @__PURE__ */ jsx(JumpyNavigationIcon, { type: "more" }) }),
2313
+ children: overflow.map((action, index) => /* @__PURE__ */ jsx(JumpMenuItem, { as: action.href ? "a" : void 0, href: action.href, leading: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index))
2314
+ }
2315
+ ) : null
2316
+ ] });
2317
+ }
2318
+ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTabBar = false, breakpoint = 1024, sidebarWidth = 256, navigationOpen, defaultNavigationOpen = false, onNavigationOpenChange, ariaLabels = {}, children, className = "", ...props }) {
2319
+ const theme = useJumpyTheme();
2320
+ const shellId = useId();
2321
+ const [internalOpen, setInternalOpen] = useState(defaultNavigationOpen);
2322
+ const isOpen = Boolean(navigation) && (navigationOpen ?? internalOpen);
2323
+ const swipeStart = useRef(null);
2324
+ const rootRef = useRef(null);
2325
+ const [drawerMounted, setDrawerMounted] = useState(isOpen);
2326
+ const showDrawer = isOpen || drawerMounted;
2327
+ const closing = drawerMounted && !isOpen;
2328
+ const wasShown = useRef(showDrawer);
2329
+ const { desktop: topBarDesktop = false, leading, largeTitle = false, actions = [], back, ...topBarProps } = topBar || {};
2330
+ const showLargeTitle = largeTitle && Boolean(topBarProps.title);
2331
+ const hasBack = Boolean(back?.label);
2332
+ const topBarOnDesktop = topBarDesktop || !showLargeTitle && (actions.length > 0 || hasBack);
2333
+ const largeTitleRef = useRef(null);
2334
+ const [largeTitleCovered, setLargeTitleCovered] = useState(false);
2335
+ const resolvedLeading = hasBack ? /* @__PURE__ */ jsx(JumpyAppShellBack, { back, variant: "bar", ariaLabels }) : leading ?? (navigation ? "menu" : void 0);
2336
+ useEffect(() => {
2337
+ const href = window.location.href;
2338
+ if (jumpyLocations.current === href) return;
2339
+ jumpyLocations.previous = jumpyLocations.current;
2340
+ jumpyLocations.current = href;
2341
+ });
2342
+ const shell = `[data-jumpy-app-shell="${shellId}"]`;
2343
+ const width = typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
2344
+ function setOpen(next) {
2345
+ if (navigationOpen === void 0) setInternalOpen(next);
2346
+ onNavigationOpenChange?.(next);
2347
+ }
2348
+ useEffect(() => {
2349
+ if (!isOpen) return void 0;
2350
+ const desktop = window.matchMedia(`(min-width: ${breakpoint}px)`);
2351
+ const closeOnDesktop = () => {
2352
+ if (desktop.matches) setOpen(false);
2353
+ };
2354
+ closeOnDesktop();
2355
+ desktop.addEventListener("change", closeOnDesktop);
2356
+ return () => desktop.removeEventListener("change", closeOnDesktop);
2357
+ });
2358
+ useEffect(() => {
2359
+ if (isOpen) {
2360
+ setDrawerMounted(true);
2361
+ return void 0;
2362
+ }
2363
+ if (!drawerMounted) return void 0;
2364
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2365
+ setDrawerMounted(false);
2366
+ return void 0;
2367
+ }
2368
+ const timer = window.setTimeout(() => setDrawerMounted(false), animationDurationValue(theme, JumpyAnimationDuration.small) * 1e3 + 100);
2369
+ return () => window.clearTimeout(timer);
2370
+ }, [isOpen, drawerMounted]);
2371
+ useEffect(() => {
2372
+ const closed = wasShown.current && !showDrawer;
2373
+ wasShown.current = showDrawer;
2374
+ if (!closed) return void 0;
2375
+ const frame = window.requestAnimationFrame(() => {
2376
+ if (document.activeElement && document.activeElement !== document.body) return;
2377
+ rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"] > [data-leading="menu"] button')?.focus();
2378
+ });
2379
+ return () => window.cancelAnimationFrame(frame);
2380
+ }, [showDrawer]);
2381
+ useEffect(() => {
2382
+ const element = largeTitleRef.current;
2383
+ if (!showLargeTitle || !element || typeof IntersectionObserver === "undefined") return void 0;
2384
+ const bar = rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"]');
2385
+ const barHeight = bar && bar.getBoundingClientRect().height > 0 ? bar.getBoundingClientRect().height : 0;
2386
+ const observer = new IntersectionObserver(([entry]) => setLargeTitleCovered(!entry.isIntersecting), { rootMargin: `-${Math.round(barHeight)}px 0px 0px 0px` });
2387
+ observer.observe(element);
2388
+ return () => observer.disconnect();
2389
+ }, [showLargeTitle, topBarProps.title]);
2390
+ function closeOnNavigate(event) {
2391
+ const item = event.target.closest?.('a[href], [data-jumpy-component="side-menu-item"]');
2392
+ if (!item || item.hasAttribute("aria-expanded") || item.getAttribute("aria-disabled") === "true" || item.disabled) return;
2393
+ setOpen(false);
2394
+ }
2395
+ const shellStyles = `
2396
+ ${shell} { grid-template-columns: minmax(0, 1fr); }
2397
+ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2398
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: inline-flex; }
2399
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: none; }
2400
+ ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: none; }
2401
+ @media (min-width: ${breakpoint}px) {
2402
+ ${shell} { grid-template-columns: ${width} minmax(0, 1fr); }
2403
+ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: flex; }
2404
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: none; }
2405
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: inline-flex; }
2406
+ ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: flex; width: fit-content; }
2407
+ ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='tab-bar'] { display: none; }
2408
+ ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'][data-mobile-only='true'] { display: none; }
2409
+ ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'] > [data-leading='menu'] { display: none; }
2410
+ }`;
2411
+ const topBarNode = /* @__PURE__ */ jsx(
2412
+ JumpTopBar,
2413
+ {
2414
+ ...topBarProps,
2415
+ "data-mobile-only": topBarOnDesktop ? "false" : "true",
2416
+ leading: resolvedLeading,
2417
+ menuOpen: isOpen,
2418
+ onMenu: () => setOpen(true),
2419
+ title: topBarProps.title ?? brand,
2420
+ subtitle: showLargeTitle ? void 0 : topBarProps.subtitle,
2421
+ titleAlign: showLargeTitle || hasBack ? "center" : topBarProps.titleAlign,
2422
+ titleHidden: showLargeTitle ? !largeTitleCovered : topBarProps.titleHidden,
2423
+ "data-large-title": showLargeTitle ? "true" : void 0,
2424
+ trailing: actions.length || topBarProps.trailing ? /* @__PURE__ */ jsxs(Fragment, { children: [
2425
+ /* @__PURE__ */ jsx(JumpyAppShellActions, { actions, variant: "compact", ariaLabels }),
2426
+ showLargeTitle ? null : /* @__PURE__ */ jsx(JumpyAppShellActions, { actions, variant: "full", ariaLabels }),
2427
+ topBarProps.trailing
2428
+ ] }) : void 0,
2429
+ ariaLabels: { menu: ariaLabels.openNavigation, back: ariaLabels.back, ...topBarProps.ariaLabels || {} }
2430
+ }
2431
+ );
2432
+ return /* @__PURE__ */ jsxs(
2433
+ "div",
2434
+ {
2435
+ ...props,
2436
+ ref: rootRef,
2437
+ "data-jumpy-component": "app-shell",
2438
+ "data-jumpy-app-shell": shellId,
2439
+ "data-navigation-open": isOpen ? "true" : "false",
2440
+ className: cn(className),
2441
+ style: {
2442
+ display: "grid",
2443
+ minHeight: "100dvh",
2444
+ backgroundColor: "var(--jumpy-color-surface-canvas)",
2445
+ color: "var(--jumpy-color-text-primary)",
2446
+ fontFamily: theme.fontFamily.familyBase,
2447
+ ...props.style || {}
2448
+ },
2449
+ children: [
2450
+ /* @__PURE__ */ jsx("style", { children: shellStyles }),
2451
+ /* @__PURE__ */ jsxs(
2452
+ "aside",
2453
+ {
2454
+ "data-jumpy-slot": "app-shell-sidebar",
2455
+ "aria-label": ariaLabels.navigation || "Navigation",
2456
+ style: {
2457
+ boxSizing: "border-box",
2458
+ position: "sticky",
2459
+ top: 0,
2460
+ height: "100dvh",
2461
+ flexDirection: "column",
2462
+ overflow: "hidden",
2463
+ borderRight: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`,
2464
+ backgroundColor: "var(--jumpy-color-surface-card)"
2465
+ },
2466
+ children: [
2467
+ brand ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-brand", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large2XL), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: brand }) : null,
2468
+ navigation ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }) : null,
2469
+ sidebarFooter ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-sidebar-footer", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: sidebarFooter }) : null
2470
+ ]
2471
+ }
2472
+ ),
2473
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "app-shell-body", style: { display: "flex", flexDirection: "column", minWidth: 0, minHeight: "100dvh" }, children: [
2474
+ topBar !== null ? topBarNode : null,
2475
+ /* @__PURE__ */ jsxs("main", { "data-jumpy-slot": "app-shell-content", style: { flex: 1, minWidth: 0 }, children: [
2476
+ showLargeTitle ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-large-title", style: { padding: `${spacingValue(theme, JumpySpacing.smallXS)}px calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-right, 0px)) 0 calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-left, 0px))` }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.large), flexWrap: "wrap" }, children: [
2477
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
2478
+ hasBack ? /* @__PURE__ */ jsx(JumpyAppShellBack, { back, variant: "inline", ariaLabels }) : null,
2479
+ /* @__PURE__ */ jsx("h1", { ref: largeTitleRef, style: { margin: 0, ...textStyleValue(theme, JumpyTextStyle.displaySmallEmphasized), overflowWrap: "anywhere" }, children: topBarProps.title }),
2480
+ topBarProps.subtitle ? /* @__PURE__ */ jsx("p", { style: { margin: `${spacingValue(theme, JumpySpacing.small4XS)}px 0 0`, color: "var(--jumpy-color-text-secondary)", ...textStyleValue(theme, JumpyTextStyle.bodyMedium) }, children: topBarProps.subtitle }) : null
2481
+ ] }),
2482
+ /* @__PURE__ */ jsx(JumpyAppShellActions, { actions, variant: "full", ariaLabels })
2483
+ ] }) }) : null,
2484
+ children
2485
+ ] }),
2486
+ tabBar && !hideTabBar ? tabBar : null
2487
+ ] }),
2488
+ showDrawer ? /* @__PURE__ */ jsx(JumpyModalLayer, { onVisibleChange: setOpen, "data-jumpy-component": "app-shell-drawer-overlay", "data-state": closing ? "closing" : "open", style: { position: "fixed", inset: 0, zIndex: 60, backgroundColor: "var(--jumpy-color-surface-overlay)" }, children: /* @__PURE__ */ jsxs(
2489
+ "div",
2490
+ {
2491
+ role: "dialog",
2492
+ "aria-modal": "true",
2493
+ "aria-label": ariaLabels.navigation || "Navigation",
2494
+ "data-jumpy-component": "app-shell-drawer",
2495
+ "data-state": closing ? "closing" : "open",
2496
+ onAnimationEnd: (event) => {
2497
+ if (closing && event.target === event.currentTarget) setDrawerMounted(false);
2498
+ },
2499
+ onClick: closeOnNavigate,
2500
+ onTouchStart: (event) => {
2501
+ const touch = event.touches[0];
2502
+ swipeStart.current = touch ? { x: touch.clientX, y: touch.clientY } : null;
2503
+ },
2504
+ onTouchEnd: (event) => {
2505
+ const start = swipeStart.current;
2506
+ const touch = event.changedTouches[0];
2507
+ swipeStart.current = null;
2508
+ if (!start || !touch) return;
2509
+ const dx = touch.clientX - start.x;
2510
+ const dy = touch.clientY - start.y;
2511
+ if (dx < -spacingValue(theme, JumpySpacing.large4XL) && Math.abs(dx) > Math.abs(dy) * 1.5) setOpen(false);
2512
+ },
2513
+ style: {
2514
+ boxSizing: "border-box",
2515
+ position: "fixed",
2516
+ inset: 0,
2517
+ display: "flex",
2518
+ flexDirection: "column",
2519
+ width: "100vw",
2520
+ height: "100dvh",
2521
+ overflow: "hidden",
2522
+ outline: "none",
2523
+ backgroundColor: "var(--jumpy-color-surface-card)",
2524
+ color: "var(--jumpy-color-text-primary)",
2525
+ fontFamily: theme.fontFamily.familyBase
2526
+ },
2527
+ children: [
2528
+ /* @__PURE__ */ jsx(
2529
+ JumpTopBar,
2530
+ {
2531
+ "data-jumpy-slot": "app-shell-drawer-header",
2532
+ sticky: false,
2533
+ style: { flex: "0 0 auto" },
2534
+ leading: /* @__PURE__ */ jsx(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.closeNavigation || "Close navigation", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx(JumpyNavigationIcon, { type: "close" }) }),
2535
+ title: brand
2536
+ }
2537
+ ),
2538
+ /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-drawer-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }),
2539
+ sidebarFooter ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "app-shell-drawer-footer", style: { flex: "0 0 auto", padding: `${spacingValue(theme, JumpySpacing.large)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-bottom, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`, borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`, backgroundColor: "var(--jumpy-color-surface-card)" }, children: sidebarFooter }) : null
2540
+ ]
2541
+ }
2542
+ ) }) : null
2543
+ ]
2544
+ }
2545
+ );
2546
+ }
2547
+ function JumpSectionHeader({ as, variant = "regular", description, className = "", children, ...props }) {
2548
+ const theme = useJumpyTheme();
2549
+ const Component = as || "li";
2550
+ return /* @__PURE__ */ jsxs(Component, { ...props, "data-jumpy-component": "section-header", className: cn(className), style: { display: "grid", gap: variant === "prominent" ? spacingValue(theme, JumpySpacing.small3XS) : 0, padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: theme.color.textSecondary, ...props.style || {} }, children: [
2551
+ /* @__PURE__ */ jsx(JumpTypography, { variant: variant === "prominent" ? "bodyMediumEmphasized" : "labelSmallEmphasized", color: "secondary", children }),
2552
+ variant === "prominent" && description ? /* @__PURE__ */ jsx(JumpTypography, { variant: "bodySmall", color: "secondary", children: description }) : null
2553
+ ] });
2554
+ }
2555
+ function JumpSectionFooter({ as, className = "", children, ...props }) {
2556
+ const theme = useJumpyTheme();
2557
+ const Component = as || "li";
2558
+ return /* @__PURE__ */ jsx(Component, { ...props, "data-jumpy-component": "section-footer", className: cn(className), style: { padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: theme.color.textSecondary, ...props.style || {} }, children: /* @__PURE__ */ jsx(JumpTypography, { variant: "bodySmall", color: "secondary", children }) });
2559
+ }
2560
+ function JumpTag({ size: size2 = "small", status = "regular", disabled = false, className = "", children, ...props }) {
2561
+ const theme = useJumpyTheme();
2562
+ const labelStyle = textStyleValue(theme, size2 === "medium" ? JumpyTextStyle.labelMediumEmphasized : JumpyTextStyle.labelSmallEmphasized);
2563
+ return /* @__PURE__ */ jsx(
2564
+ "span",
2565
+ {
2566
+ ...props,
2567
+ "data-jumpy-component": "tag",
2568
+ "data-size": size2,
2569
+ "data-status": status,
2570
+ "data-disabled": disabled ? "true" : "false",
2571
+ className: cn(className),
2572
+ style: {
2573
+ display: "inline-flex",
2574
+ alignItems: "center",
2575
+ width: "fit-content",
2576
+ minHeight: size2 === "medium" ? sizeValue(theme, JumpySize.small) : sizeValue(theme, JumpySize.small2XS),
2577
+ padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`,
2578
+ borderRadius: radiusValue(theme, JumpyRadius.full),
2579
+ borderWidth: borderWidthValue(theme, JumpyBorderWidth.small),
2580
+ borderStyle: "solid",
2581
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
2582
+ fontFamily: theme.fontFamily.familyBase,
2583
+ fontSize: labelStyle.fontSize,
2584
+ lineHeight: labelStyle.lineHeight,
2585
+ fontWeight: labelStyle.fontWeight,
2586
+ letterSpacing: labelStyle.letterSpacing,
2587
+ ...getTagStatusStyles(theme, status),
2588
+ ...props.style || {}
2589
+ },
2590
+ children
2591
+ }
2592
+ );
2593
+ }
2594
+ function JumpChip({ selected = false, disabled = false, className = "", children, ...props }) {
2595
+ const theme = useJumpyTheme();
2596
+ const bodyMediumEmphasized = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
2597
+ return /* @__PURE__ */ jsx(
2598
+ "button",
2599
+ {
2600
+ ...props,
2601
+ type: props.type || "button",
2602
+ "data-jumpy-component": "chip",
2603
+ "data-selected": selected ? "true" : "false",
2604
+ disabled,
2605
+ className: cn(className),
2606
+ style: {
2607
+ display: "inline-flex",
2608
+ alignItems: "center",
2609
+ justifyContent: "center",
2610
+ minHeight: sizeValue(theme, JumpySize.large),
2611
+ padding: `${spacingValue(theme, JumpySpacing.small2XS)}px ${spacingValue(theme, JumpySpacing.large)}px`,
2612
+ borderRadius: radiusValue(theme, JumpyRadius.full),
2613
+ borderWidth: borderWidthValue(theme, JumpyBorderWidth.small),
2614
+ borderStyle: "solid",
2615
+ borderColor: selected ? theme.color.surfacePrimary : theme.color.borderStrong,
2616
+ backgroundColor: selected ? theme.color.infoSurface : theme.color.surfaceField,
2617
+ color: selected ? theme.color.surfacePrimary : theme.color.textPrimary,
2618
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
2619
+ fontFamily: theme.fontFamily.familyBase,
2620
+ fontSize: bodyMediumEmphasized.fontSize,
2621
+ lineHeight: bodyMediumEmphasized.lineHeight,
2622
+ fontWeight: bodyMediumEmphasized.fontWeight,
2623
+ letterSpacing: bodyMediumEmphasized.letterSpacing,
2624
+ cursor: disabled ? "not-allowed" : "pointer",
2625
+ ...props.style || {}
2626
+ },
2627
+ children
2628
+ }
2629
+ );
2630
+ }
2631
+ function JumpCheckbox({ label, children, checked, defaultChecked = false, indeterminate = false, onCheckedChange, onIndeterminateChange, labelPlacement = "end", innerClasses = {}, className = "", disabled = false, readOnly = false, style, ...props }) {
2632
+ const theme = useJumpyTheme();
2633
+ const labelLarge = textStyleValue(theme, JumpyTextStyle.labelLarge);
2634
+ const inputRef = useRef(null);
2635
+ const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
2636
+ const isControlled = checked !== void 0;
2637
+ const isChecked = Boolean(isControlled ? checked : uncontrolledChecked);
2638
+ const isReadonly = readOnly && !disabled;
2639
+ const activeDefault = theme.selectors.active.default;
2640
+ const activeDisabled = theme.selectors.active.disabled;
2641
+ const activeReadOnly = theme.selectors.active.readOnly;
2642
+ const inactiveDefault = theme.selectors.inactive.default;
2643
+ const inactiveDisabled = theme.selectors.inactive.disabled;
2644
+ const inactiveReadOnly = theme.selectors.inactive.readOnly;
2645
+ const labelContent = children ?? label;
2646
+ useEffect(() => {
2647
+ if (inputRef.current) inputRef.current.indeterminate = indeterminate;
2648
+ }, [indeterminate]);
2649
+ const stateTokens = isChecked || indeterminate ? disabled ? activeDisabled : isReadonly ? activeReadOnly : activeDefault : disabled ? inactiveDisabled : isReadonly ? inactiveReadOnly : inactiveDefault;
2650
+ function handleChange(event) {
2651
+ if (isReadonly) {
2652
+ if (inputRef.current) inputRef.current.indeterminate = indeterminate;
2653
+ return;
2654
+ }
2655
+ if (indeterminate) onIndeterminateChange?.(false);
2656
+ if (!isControlled) setUncontrolledChecked(event.target.checked);
2657
+ onCheckedChange?.(event.target.checked);
2658
+ props.onChange?.(event);
2659
+ }
2660
+ return /* @__PURE__ */ jsxs(
2661
+ "label",
2662
+ {
2663
+ "data-jumpy-component": "checkbox",
2664
+ "data-checked": isChecked || indeterminate ? "true" : "false",
2665
+ "data-disabled": disabled ? "true" : void 0,
2666
+ "data-readonly": isReadonly ? "true" : void 0,
2667
+ "data-label-placement": labelPlacement,
2668
+ className: cn(className),
2669
+ style: {
2670
+ "--jumpy-cmp-checkbox-container-size": `${sizeValue(theme, JumpySize.large)}px`,
2671
+ "--jumpy-cmp-checkbox-container-radius": `${radiusValue(theme, JumpyRadius.medium)}px`,
2672
+ "--jumpy-cmp-checkbox-input-size": `${sizeValue(theme, JumpySize.smallXS)}px`,
2673
+ "--jumpy-cmp-checkbox-input-radius": `${radiusValue(theme, JumpyRadius.small2XS)}px`,
2674
+ "--jumpy-cmp-checkbox-label-gap-size": `${spacingValue(theme, JumpySpacing.smallXS)}px`,
2675
+ "--jumpy-cmp-checkbox-label-color": stateTokens.text,
2676
+ "--jumpy-cmp-checkbox-container-color": stateTokens.surface || activeDefault.surface,
2677
+ "--jumpy-cmp-checkbox-container-bg": "transparent",
2678
+ "--jumpy-cmp-checkbox-icon-fill": stateTokens.icon || activeDefault.icon || theme.color.textOnPrimary,
2679
+ "--jumpy-cmp-checkbox-input-border-color": stateTokens.border || inactiveDefault.border,
2680
+ "--jumpy-cmp-checkbox-hover-ring-color": "var(--jumpy-selectors-inactive-hover-hover-ring, var(--jumpy-color-feedback-info-surface))",
2681
+ "--jumpy-cmp-checkbox-hover-border-color": "var(--jumpy-selectors-inactive-hover-border, var(--jumpy-color-focus-border))",
2682
+ "--jumpy-cmp-checkbox-hover-surface-color": "var(--jumpy-selectors-active-hover-surface, var(--jumpy-color-surface-primary))",
2683
+ "--jumpy-cmp-checkbox-focus-border-color": "var(--jumpy-selectors-inactive-focus-border, var(--jumpy-color-focus-border))",
2684
+ "--jumpy-cmp-checkbox-focus-ring-color": "var(--jumpy-selectors-inactive-focus-focus-ring, var(--jumpy-color-focus-ring))",
2685
+ "--jumpy-cmp-checkbox-active-focus-ring-color": "var(--jumpy-selectors-active-focus-focus-ring, var(--jumpy-color-focus-ring))",
2686
+ fontFamily: theme.fontFamily.familyBase,
2687
+ fontSize: labelLarge.fontSize,
2688
+ lineHeight: labelLarge.lineHeight,
2689
+ fontWeight: labelLarge.fontWeight,
2690
+ letterSpacing: labelLarge.letterSpacing,
2691
+ ...style || {}
2692
+ },
2693
+ children: [
2694
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "checkbox-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: [
2695
+ /* @__PURE__ */ jsx(
2696
+ "input",
2697
+ {
2698
+ ...props,
2699
+ ref: (node) => {
2700
+ inputRef.current = node;
2701
+ if (typeof props.ref === "function") props.ref(node);
2702
+ else if (props.ref) props.ref.current = node;
2703
+ },
2704
+ type: "checkbox",
2705
+ "data-jumpy-slot": "checkbox-input",
2706
+ className: innerClasses.input?.className,
2707
+ checked: isChecked,
2708
+ disabled,
2709
+ readOnly: isReadonly,
2710
+ "aria-readonly": isReadonly || void 0,
2711
+ onChange: handleChange
2712
+ }
2713
+ ),
2714
+ isChecked || indeterminate ? /* @__PURE__ */ jsx("svg", { "data-jumpy-slot": "checkbox-icon", className: innerClasses.icons?.className, style: innerClasses.icons?.style, viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: indeterminate ? /* @__PURE__ */ jsx("path", { d: "M6 11h12v2H6z" }) : /* @__PURE__ */ jsx("path", { d: "M9.4 16.6 4.8 12l1.4-1.4 3.2 3.2 8.4-8.4L19.2 6.8z" }) }) : null
2715
+ ] }),
2716
+ labelContent ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "checkbox-label", children: labelContent }) : null
2717
+ ]
2718
+ }
2719
+ );
2720
+ }
2721
+ function JumpSwitch({ label, children, checked, defaultChecked = false, onCheckedChange, labelPlacement = "end", innerClasses = {}, className = "", disabled = false, readOnly = false, style, ...props }) {
2722
+ const theme = useJumpyTheme();
2723
+ const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
2724
+ const [uncontrolledChecked, setUncontrolledChecked] = useState(Boolean(defaultChecked));
2725
+ const isControlled = checked !== void 0;
2726
+ const isChecked = Boolean(isControlled ? checked : uncontrolledChecked);
2727
+ const isReadonly = readOnly && !disabled;
2728
+ const selectorState = disabled ? "disabled" : isReadonly ? "readOnly" : "default";
2729
+ const selectorTokens = isChecked ? theme.selectors.active[selectorState] : theme.selectors.inactive[selectorState];
2730
+ const labelContent = children ?? label;
2731
+ return /* @__PURE__ */ jsxs(
2732
+ "label",
2733
+ {
2734
+ "data-jumpy-component": "switch",
2735
+ "data-checked": isChecked ? "true" : "false",
2736
+ "data-disabled": disabled ? "true" : void 0,
2737
+ "data-readonly": isReadonly ? "true" : void 0,
2738
+ "data-label-placement": labelPlacement,
2739
+ className: cn(className),
2740
+ style: {
2741
+ "--jumpy-cmp-switch-thumb-size": `${sizeValue(theme, JumpySize.small2XS)}px`,
2742
+ "--jumpy-cmp-switch-thumb-size-checked": `${sizeValue(theme, JumpySize.small)}px`,
2743
+ "--jumpy-cmp-switch-track-width": `${sizeValue(theme, JumpySize.large2XL)}px`,
2744
+ "--jumpy-cmp-switch-track-bg": selectorTokens.surface,
2745
+ "--jumpy-cmp-switch-track-border": selectorTokens.border,
2746
+ "--jumpy-cmp-switch-thumb-bg": selectorTokens.handle || selectorTokens.icon || theme.color.textOnPrimary,
2747
+ "--jumpy-cmp-switch-icon-fill": selectorTokens.surface,
2748
+ "--jumpy-cmp-switch-label-color": selectorTokens.text,
2749
+ "--jumpy-cmp-switch-hover-track-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-surface)`,
2750
+ "--jumpy-cmp-switch-hover-track-border": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-border)`,
2751
+ "--jumpy-cmp-switch-hover-thumb-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-handle)`,
2752
+ "--jumpy-cmp-switch-focus-ring": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-focus-ring)`,
2753
+ "--jumpy-cmp-switch-focus-track-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-surface)`,
2754
+ "--jumpy-cmp-switch-focus-track-border": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-border)`,
2755
+ "--jumpy-cmp-switch-focus-thumb-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-handle)`,
2756
+ fontFamily: theme.fontFamily.familyBase,
2757
+ fontSize: bodyMedium.fontSize,
2758
+ lineHeight: bodyMedium.lineHeight,
2759
+ fontWeight: bodyMedium.fontWeight,
2760
+ ...style || {}
2761
+ },
2762
+ children: [
2763
+ /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "switch-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: [
2764
+ /* @__PURE__ */ jsx(
2765
+ "input",
2766
+ {
2767
+ ...props,
2768
+ type: "checkbox",
2769
+ role: "switch",
2770
+ checked: isChecked,
2771
+ disabled,
2772
+ readOnly: isReadonly,
2773
+ "aria-checked": isChecked,
2774
+ "aria-readonly": isReadonly || void 0,
2775
+ "data-jumpy-slot": "switch-input",
2776
+ className: innerClasses.input?.className,
2777
+ onChange: (event) => {
2778
+ if (isReadonly) return;
2779
+ if (!isControlled) setUncontrolledChecked(event.target.checked);
2780
+ onCheckedChange?.(event.target.checked);
2781
+ props.onChange?.(event);
2782
+ }
2783
+ }
2784
+ ),
2785
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "switch-track", className: innerClasses.track?.className, style: innerClasses.track?.style, children: /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "switch-thumb", className: innerClasses.thumb?.className, style: innerClasses.thumb?.style, children: /* @__PURE__ */ jsx("svg", { "data-jumpy-slot": "switch-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M6 10.25 8.6 12.85 14 7.45", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) })
2786
+ ] }),
2787
+ labelContent ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "switch-label", children: labelContent }) : null
2788
+ ]
2789
+ }
2790
+ );
2791
+ }
2792
+ function JumpRadioGroup({ name, value, defaultValue, onValueChange, options, className = "", disabled = false, readOnly = false, direction = "vertical", innerClasses = {}, ...props }) {
2793
+ const theme = useJumpyTheme();
2794
+ const labelLarge = textStyleValue(theme, JumpyTextStyle.labelLarge);
2795
+ const isReadonly = readOnly && !disabled;
2796
+ const generatedName = useId();
2797
+ const validName = name || generatedName;
2798
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
2799
+ const isControlled = value !== void 0;
2800
+ const selectedValue = isControlled ? value : uncontrolledValue;
2801
+ return /* @__PURE__ */ jsx("div", { ...props, role: "radiogroup", "aria-readonly": isReadonly || void 0, "aria-orientation": direction === "horizontal" ? "horizontal" : "vertical", "data-jumpy-component": "radio-group", "data-direction": direction, className: cn(className), style: { display: "flex", flexDirection: direction === "horizontal" ? "row" : "column", gap: spacingValue(theme, JumpySpacing.medium), ...props.style || {} }, onChange: (event) => {
2802
+ if (isReadonly || event.defaultPrevented) return;
2803
+ const target = event.target;
2804
+ if (target?.type === "radio" && target.name === validName) {
2805
+ if (!isControlled) setUncontrolledValue(target.value);
2806
+ onValueChange?.(target.value);
2807
+ }
2808
+ props.onChange?.(event);
2809
+ }, children: options.map((option) => {
2810
+ const optionDisabled = disabled || option.disabled;
2811
+ const optionSelected = selectedValue === option.value;
2812
+ const selectorState = optionDisabled ? "disabled" : isReadonly ? "readOnly" : "default";
2813
+ const selectorTokens = optionSelected ? theme.selectors.active[selectorState] : theme.selectors.inactive[selectorState];
2814
+ return /* @__PURE__ */ jsxs(
2815
+ "label",
2816
+ {
2817
+ "data-jumpy-component": "radio",
2818
+ "data-checked": optionSelected ? "true" : "false",
2819
+ "data-disabled": optionDisabled ? "true" : void 0,
2820
+ "data-readonly": isReadonly ? "true" : void 0,
2821
+ style: {
2822
+ "--jumpy-cmp-radio-label-color": selectorTokens.text,
2823
+ "--jumpy-cmp-radio-input-border-color": selectorTokens.border,
2824
+ "--jumpy-cmp-radio-dot-color": optionSelected ? selectorTokens.surface : "transparent",
2825
+ "--jumpy-cmp-radio-container-bg": "transparent",
2826
+ "--jumpy-cmp-radio-hover-ring-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-hover-ring)`,
2827
+ "--jumpy-cmp-radio-hover-border-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-border)`,
2828
+ "--jumpy-cmp-radio-hover-dot-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-surface)`,
2829
+ "--jumpy-cmp-radio-focus-ring-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-focus-focus-ring)`,
2830
+ "--jumpy-cmp-radio-focus-border-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-focus-border)`,
2831
+ fontFamily: theme.fontFamily.familyBase,
2832
+ fontSize: labelLarge.fontSize,
2833
+ lineHeight: labelLarge.lineHeight,
2834
+ fontWeight: labelLarge.fontWeight,
2835
+ letterSpacing: labelLarge.letterSpacing
2836
+ },
2837
+ children: [
2838
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "radio-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: /* @__PURE__ */ jsx("input", { type: "radio", name: validName, value: option.value, checked: optionSelected, disabled: optionDisabled, "aria-readonly": isReadonly || void 0, readOnly: isReadonly || void 0, "data-jumpy-slot": "radio-input", className: innerClasses.input?.className, style: innerClasses.input?.style, onChange: () => {
2839
+ } }) }),
2840
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "radio-label", className: innerClasses.label?.className, style: innerClasses.label?.style, children: option.label })
2841
+ ]
2842
+ },
2843
+ option.value
2844
+ );
2845
+ }) });
2846
+ }
2847
+ function toDateValue(value) {
2848
+ if (value === null || value === void 0 || value === "") return null;
2849
+ const date = value instanceof Date ? value : new Date(value);
2850
+ return Number.isNaN(date.getTime()) ? null : date;
2851
+ }
2852
+ function sameDay(a, b) {
2853
+ if (!a || !b) return false;
2854
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2855
+ }
2856
+ function formatDateValue(value, format = "MM/dd/yyyy") {
2857
+ const date = toDateValue(value);
2858
+ if (!date) return "";
2859
+ const parts = {
2860
+ dd: String(date.getDate()).padStart(2, "0"),
2861
+ MM: String(date.getMonth() + 1).padStart(2, "0"),
2862
+ yyyy: String(date.getFullYear())
2863
+ };
2864
+ return format.replace(/yyyy|MM|dd/g, (token) => parts[token]);
2865
+ }
2866
+ function formatTimeValue(value, format = "HH:mm") {
2867
+ const date = toDateValue(value);
2868
+ if (!date) return "";
2869
+ const hours24 = date.getHours();
2870
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2871
+ const period = hours24 >= 12 ? "PM" : "AM";
2872
+ const hours12 = hours24 % 12 || 12;
2873
+ return format.includes("aa") ? format.replace("hh", String(hours12).padStart(2, "0")).replace("mm", minutes).replace("aa", period) : format.replace("HH", String(hours24).padStart(2, "0")).replace("mm", minutes);
2874
+ }
2875
+ function parseDateValue(text, format = "MM/dd/yyyy") {
2876
+ const value = text.trim();
2877
+ if (!value) return null;
2878
+ const activeFormat = Array.isArray(format) ? format[0] : format;
2879
+ const escaped = activeFormat.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2880
+ const pattern = escaped.replace("dd", "(?<dd>\\d{1,2})").replace("MM", "(?<MM>\\d{1,2})").replace("yyyy", "(?<yyyy>\\d{4})");
2881
+ const match = value.match(new RegExp(`^${pattern}$`));
2882
+ if (!match?.groups) return null;
2883
+ const day = Number(match.groups.dd);
2884
+ const month = Number(match.groups.MM) - 1;
2885
+ const year = Number(match.groups.yyyy);
2886
+ const date = new Date(year, month, day);
2887
+ return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day ? date : null;
2888
+ }
2889
+ function parseTimeValue(text, format = "HH:mm") {
2890
+ const value = text.trim();
2891
+ if (!value) return null;
2892
+ const usesPeriod = format.includes("aa");
2893
+ const match = usesPeriod ? value.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i) : value.match(/^(\d{1,2}):(\d{2})$/);
2894
+ if (!match) return null;
2895
+ let hour = Number(match[1]);
2896
+ const minute = Number(match[2]);
2897
+ if (usesPeriod) {
2898
+ if (hour < 1 || hour > 12) return null;
2899
+ const period = match[3].toUpperCase();
2900
+ hour = period === "PM" ? hour % 12 + 12 : hour % 12;
2901
+ }
2902
+ if (hour > 23 || minute > 59) return null;
2903
+ const date = /* @__PURE__ */ new Date();
2904
+ date.setHours(hour, minute, 0, 0);
2905
+ return date;
2906
+ }
2907
+ function coerceDateOutput(date, sample) {
2908
+ return typeof sample === "number" ? date?.getTime() ?? null : date;
2909
+ }
2910
+ function PickerTrailingIcon({ type, open }) {
2911
+ if (type === "time") {
2912
+ return /* @__PURE__ */ jsxs("svg", { "data-jumpy-stroke-icon": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block", fill: "none" }, children: [
2913
+ /* @__PURE__ */ jsx("path", { d: "M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
2914
+ /* @__PURE__ */ jsx("path", { d: "M8 4.75V8l2.25 1.5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
2915
+ ] });
2916
+ }
2917
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block" }, children: [
2918
+ /* @__PURE__ */ jsx("path", { d: "M4.25 2.5v2M11.75 2.5v2M3.25 6.25h9.5M3.75 3.75h8.5c.69 0 1.25.56 1.25 1.25v7.25c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25V5c0-.69.56-1.25 1.25-1.25Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
2919
+ open ? /* @__PURE__ */ jsx("path", { d: "M6 10.5 7.25 11.75 10.25 8.75", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) : null
2920
+ ] });
2921
+ }
2922
+ function getMonthDays(viewDate) {
2923
+ const year = viewDate.getFullYear();
2924
+ const month = viewDate.getMonth();
2925
+ const firstDay = new Date(year, month, 1);
2926
+ const leading = (firstDay.getDay() + 6) % 7;
2927
+ const start = new Date(year, month, 1 - leading);
2928
+ return Array.from({ length: 42 }, (_, index) => new Date(start.getFullYear(), start.getMonth(), start.getDate() + index));
2929
+ }
2930
+ function dateIsUnavailable(date, { minDate, maxDate, disabledDates = [], isDateDisabled }) {
2931
+ const min = toDateValue(minDate);
2932
+ const max = toDateValue(maxDate);
2933
+ if (min && date < new Date(min.getFullYear(), min.getMonth(), min.getDate())) return true;
2934
+ if (max && date > new Date(max.getFullYear(), max.getMonth(), max.getDate())) return true;
2935
+ if (disabledDates.some((disabledDate) => sameDay(date, toDateValue(disabledDate)))) return true;
2936
+ return Boolean(isDateDisabled?.(date));
2937
+ }
2938
+ function JumpDatePicker({ label = "Date", hint, value, defaultValue = null, onChange, open, onOpenChange, format = "MM/dd/yyyy", minDate, maxDate, disabledDates = [], isDateDisabled, labels = {}, disabled = false, readOnly = false, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
2939
+ const theme = useJumpyTheme();
2940
+ const [internalValue, setInternalValue] = useState(defaultValue);
2941
+ const [internalOpen, setInternalOpen] = useState(false);
2942
+ const isControlled = value !== void 0;
2943
+ const selectedDate = toDateValue(isControlled ? value : internalValue);
2944
+ const selectedTime = selectedDate?.getTime() ?? null;
2945
+ const [viewDate, setViewDate] = useState(selectedDate || /* @__PURE__ */ new Date());
2946
+ const isOpen = open ?? internalOpen;
2947
+ const resolvedFormat = Array.isArray(format) ? format[0] : format;
2948
+ const inputValue = formatDateValue(selectedDate, resolvedFormat);
2949
+ const [draft, setDraft] = useState(inputValue);
2950
+ const typedValue = useRef(void 0);
2951
+ useEffect(() => {
2952
+ if (typedValue.current !== selectedTime) setDraft(inputValue);
2953
+ typedValue.current = void 0;
2954
+ if (selectedTime !== null) setViewDate(new Date(selectedTime));
2955
+ }, [selectedTime, inputValue, resolvedFormat]);
2956
+ const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
2957
+ const dayCells = getMonthDays(viewDate);
2958
+ const { refs, floatingStyles } = useFloating({
2959
+ open: isOpen,
2960
+ onOpenChange: setOpen,
2961
+ placement: "bottom-start",
2962
+ strategy: "fixed",
2963
+ whileElementsMounted: autoUpdate,
2964
+ middleware: [offset(spacingValue(theme, JumpySpacing.smallXS)), flip({ padding: spacingValue(theme, JumpySpacing.medium) })]
2965
+ });
2966
+ function setOpen(next) {
2967
+ setInternalOpen(next);
2968
+ onOpenChange?.(next);
2969
+ }
2970
+ function commit(date) {
2971
+ if (date && dateIsUnavailable(date, { minDate, maxDate, disabledDates, isDateDisabled })) return;
2972
+ const next = coerceDateOutput(date, value ?? defaultValue);
2973
+ typedValue.current = void 0;
2974
+ if (!isControlled) setInternalValue(next);
2975
+ setDraft(isControlled ? inputValue : formatDateValue(date, resolvedFormat));
2976
+ onChange?.(next);
2977
+ setOpen(false);
2978
+ }
2979
+ const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx(PickerTrailingIcon, { type: "date", open: isOpen });
2980
+ return /* @__PURE__ */ jsxs("div", { ref: refs.setReference, "data-jumpy-component": "date-picker", style: { position: "relative", width: "100%", maxWidth: "100%" }, children: [
2981
+ /* @__PURE__ */ jsx(JumpField, { label, hint, status, error, hideClearIcon, onClear, className, trailing, children: /* @__PURE__ */ jsx(
2982
+ JumpInput,
2983
+ {
2984
+ ...props,
2985
+ role: "combobox",
2986
+ "aria-expanded": isOpen,
2987
+ "aria-haspopup": "dialog",
2988
+ readOnly,
2989
+ disabled,
2990
+ "data-has-value": selectedTime !== null,
2991
+ value: draft,
2992
+ placeholder: props.placeholder || (Array.isArray(format) ? format[0] : format),
2993
+ autoComplete: "off",
2994
+ inputMode: "numeric",
2995
+ onChange: (event) => {
2996
+ const text = event.target.value;
2997
+ setDraft(text);
2998
+ const parsedDate = parseDateValue(text, format);
2999
+ typedValue.current = void 0;
3000
+ if (!text.trim() || parsedDate && !dateIsUnavailable(parsedDate, { minDate, maxDate, disabledDates, isDateDisabled })) {
3001
+ const next = coerceDateOutput(parsedDate, value ?? defaultValue);
3002
+ const nextTime = parsedDate?.getTime() ?? null;
3003
+ if (nextTime !== selectedTime) typedValue.current = nextTime;
3004
+ if (!isControlled) setInternalValue(next);
3005
+ onChange?.(next);
3006
+ }
3007
+ },
3008
+ onBlur: (event) => {
3009
+ typedValue.current = void 0;
3010
+ setDraft(inputValue);
3011
+ props.onBlur?.(event);
3012
+ },
3013
+ onClick: (event) => {
3014
+ if (!readOnly) setOpen(!isOpen);
3015
+ props.onClick?.(event);
3016
+ },
3017
+ onKeyDown: (event) => {
3018
+ if ((event.key === "ArrowDown" || event.key === "Enter") && !readOnly) {
3019
+ event.preventDefault();
3020
+ setOpen(true);
3021
+ }
3022
+ if (event.key === "Escape") setOpen(false);
3023
+ props.onKeyDown?.(event);
3024
+ }
3025
+ }
3026
+ ) }),
3027
+ isOpen && !disabled && !readOnly ? /* @__PURE__ */ jsx(JumpyPortal, { children: /* @__PURE__ */ jsxs("div", { ref: refs.setFloating, role: "dialog", "aria-label": label, style: { ...floatingStyles, zIndex: JUMPY_POPUP_Z_INDEX, width: sizeValue(theme, JumpySize.large8XL) * 2.5, maxWidth: "calc(100vw - 32px)", overflow: "visible", borderRadius: radiusValue(theme, JumpyRadius.smallXS), backgroundColor: "var(--jumpy-card-inactive-default-surface, var(--jumpy-color-surface-card))", color: "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", boxShadow: theme.shadow.positiveMedium, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0` }, children: [
3028
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.medium), padding: spacingValue(theme, JumpySpacing.small3XS) }, children: [
3029
+ /* @__PURE__ */ jsx(JumpIconButton, { variant: "borderless", "aria-label": "Previous", onClick: () => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1)), children: "\u2039" }),
3030
+ /* @__PURE__ */ jsx("strong", { style: { fontFamily: theme.fontFamily.familyBase, color: "inherit" }, children: viewDate.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }),
3031
+ /* @__PURE__ */ jsx(JumpIconButton, { variant: "borderless", "aria-label": "Next", onClick: () => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1)), children: "\u203A" })
3032
+ ] }),
3033
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: spacingValue(theme, JumpySpacing.small3XS), padding: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px` }, children: [
3034
+ weekdays.map((weekday) => /* @__PURE__ */ jsx("span", { style: { color: theme.color.textSecondary, textAlign: "center", fontSize: textStyleValue(theme, JumpyTextStyle.labelSmall).fontSize, fontWeight: theme.fontFamily.labelSmall.fontWeight }, children: weekday }, weekday)),
3035
+ dayCells.map((date) => {
3036
+ const outside = date.getMonth() !== viewDate.getMonth();
3037
+ const selected = sameDay(date, selectedDate);
3038
+ const unavailable = dateIsUnavailable(date, { minDate, maxDate, disabledDates, isDateDisabled });
3039
+ return /* @__PURE__ */ jsx("button", { type: "button", disabled: unavailable, onClick: () => commit(date), style: { minHeight: sizeValue(theme, JumpySize.large), border: 0, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: selected ? "var(--jumpy-interactive-active-regular-default-surface, var(--jumpy-color-surface-primary))" : "transparent", color: selected ? "var(--jumpy-interactive-active-regular-default-text, var(--jumpy-color-text-on-primary))" : outside ? "var(--jumpy-card-inactive-default-subtext, var(--jumpy-color-text-secondary))" : "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", opacity: unavailable ? opacityValue(theme, JumpyOpacity.medium) : outside ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid), cursor: unavailable ? "not-allowed" : "pointer", fontFamily: theme.fontFamily.familyBase }, children: date.getDate() }, date.toISOString());
3040
+ })
3041
+ ] }),
3042
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: spacingValue(theme, JumpySpacing.smallXS), padding: spacingValue(theme, JumpySpacing.large) }, children: [
3043
+ /* @__PURE__ */ jsx(JumpButton, { variant: "tertiary", onClick: () => setOpen(false), children: labels.cancelButton || "Cancel" }),
3044
+ /* @__PURE__ */ jsx(JumpButton, { variant: "borderless", onClick: () => commit(/* @__PURE__ */ new Date()), children: "Today" })
3045
+ ] })
3046
+ ] }) }) : null
3047
+ ] });
3048
+ }
3049
+ function JumpTimePicker({ label = "Time", hint, value, defaultValue = null, onChange, open, onOpenChange, format, twentyFourHours = true, labels = {}, disabled = false, readOnly = false, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
3050
+ const theme = useJumpyTheme();
3051
+ const resolvedFormat = format || (twentyFourHours ? "HH:mm" : "hh:mm aa");
3052
+ const [internalValue, setInternalValue] = useState(defaultValue);
3053
+ const [internalOpen, setInternalOpen] = useState(false);
3054
+ const isControlled = value !== void 0;
3055
+ const selectedDate = toDateValue(isControlled ? value : internalValue);
3056
+ const selectedTime = selectedDate?.getTime() ?? null;
3057
+ const inputValue = formatTimeValue(selectedDate, resolvedFormat);
3058
+ const [draft, setDraft] = useState(inputValue);
3059
+ const typedValue = useRef(void 0);
3060
+ useEffect(() => {
3061
+ if (typedValue.current !== selectedTime) setDraft(inputValue);
3062
+ typedValue.current = void 0;
3063
+ }, [selectedTime, inputValue, resolvedFormat]);
3064
+ const isOpen = open ?? internalOpen;
3065
+ const hours = Array.from({ length: twentyFourHours ? 24 : 12 }, (_, index) => String(twentyFourHours ? index : index + 1).padStart(2, "0"));
3066
+ const minutes = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"));
3067
+ const currentHour = selectedDate ? selectedDate.getHours() : 0;
3068
+ const currentMinute = selectedDate ? selectedDate.getMinutes() : 0;
3069
+ const displayHour = twentyFourHours ? currentHour : currentHour % 12 || 12;
3070
+ const currentPeriod = currentHour >= 12 ? "PM" : "AM";
3071
+ const { refs, floatingStyles } = useFloating({
3072
+ open: isOpen,
3073
+ onOpenChange: setOpen,
3074
+ placement: "bottom-start",
3075
+ strategy: "fixed",
3076
+ whileElementsMounted: autoUpdate,
3077
+ middleware: [offset(spacingValue(theme, JumpySpacing.smallXS)), flip({ padding: spacingValue(theme, JumpySpacing.medium) })]
3078
+ });
3079
+ function setOpen(next) {
3080
+ setInternalOpen(next);
3081
+ onOpenChange?.(next);
3082
+ }
3083
+ function commit(part, rawValue) {
3084
+ const next = selectedDate ? new Date(selectedDate) : /* @__PURE__ */ new Date();
3085
+ if (part === "hour") {
3086
+ const hour = Number(rawValue);
3087
+ next.setHours(twentyFourHours ? hour : currentPeriod === "PM" ? hour % 12 + 12 : hour % 12);
3088
+ }
3089
+ if (part === "minute") next.setMinutes(Number(rawValue));
3090
+ if (part === "period") {
3091
+ const base = next.getHours() % 12;
3092
+ next.setHours(rawValue === "PM" ? base + 12 : base);
3093
+ }
3094
+ next.setSeconds(0, 0);
3095
+ const output = coerceDateOutput(next, value ?? defaultValue);
3096
+ typedValue.current = void 0;
3097
+ if (!isControlled) setInternalValue(output);
3098
+ setDraft(isControlled ? inputValue : formatTimeValue(next, resolvedFormat));
3099
+ onChange?.(output);
3100
+ }
3101
+ const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx(PickerTrailingIcon, { type: "time", open: isOpen });
3102
+ return /* @__PURE__ */ jsxs("div", { ref: refs.setReference, "data-jumpy-component": "time-picker", style: { position: "relative", width: "100%", maxWidth: "100%" }, children: [
3103
+ /* @__PURE__ */ jsx(JumpField, { label, hint, status, error, hideClearIcon, onClear, className, trailing, children: /* @__PURE__ */ jsx(
3104
+ JumpInput,
3105
+ {
3106
+ ...props,
3107
+ role: "combobox",
3108
+ "aria-expanded": isOpen,
3109
+ "aria-haspopup": "dialog",
3110
+ readOnly,
3111
+ disabled,
3112
+ "data-has-value": selectedTime !== null,
3113
+ value: draft,
3114
+ placeholder: props.placeholder || resolvedFormat,
3115
+ autoComplete: "off",
3116
+ inputMode: resolvedFormat.includes("aa") ? "text" : "numeric",
3117
+ onChange: (event) => {
3118
+ const text = event.target.value;
3119
+ setDraft(text);
3120
+ const parsedDate = parseTimeValue(text, resolvedFormat);
3121
+ typedValue.current = void 0;
3122
+ if (!text.trim() || parsedDate) {
3123
+ const output = coerceDateOutput(parsedDate, value ?? defaultValue);
3124
+ const nextTime = parsedDate?.getTime() ?? null;
3125
+ if (nextTime !== selectedTime) typedValue.current = nextTime;
3126
+ if (!isControlled) setInternalValue(output);
3127
+ onChange?.(output);
3128
+ }
3129
+ },
3130
+ onBlur: (event) => {
3131
+ typedValue.current = void 0;
3132
+ setDraft(inputValue);
3133
+ props.onBlur?.(event);
3134
+ },
3135
+ onClick: (event) => {
3136
+ if (!readOnly) setOpen(!isOpen);
3137
+ props.onClick?.(event);
3138
+ },
3139
+ onKeyDown: (event) => {
3140
+ if ((event.key === "ArrowDown" || event.key === "Enter") && !readOnly) {
3141
+ event.preventDefault();
3142
+ setOpen(true);
3143
+ }
3144
+ if (event.key === "Escape") setOpen(false);
3145
+ props.onKeyDown?.(event);
3146
+ }
3147
+ }
3148
+ ) }),
3149
+ isOpen && !disabled && !readOnly ? /* @__PURE__ */ jsx(JumpyPortal, { children: /* @__PURE__ */ jsxs("div", { ref: refs.setFloating, role: "dialog", "aria-label": label, style: { ...floatingStyles, zIndex: JUMPY_POPUP_Z_INDEX, overflow: "visible", borderRadius: radiusValue(theme, JumpyRadius.smallXS), backgroundColor: `var(--jumpy-non-interactive-background-alternative, var(--jumpy-color-non-interactive-background-alternative, ${theme.color.surfaceCard}))`, color: "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", boxShadow: theme.shadow.positiveMedium, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, minWidth: 260 }, children: [
3150
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", position: "relative", justifyContent: "center", alignItems: "center", gap: spacingValue(theme, JumpySpacing.small), padding: `${spacingValue(theme, JumpySpacing.zero)}px ${spacingValue(theme, JumpySpacing.zero)}px`, overflow: "hidden" }, children: [
3151
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", left: 0, right: 0, top: "50%", height: sizeValue(theme, JumpySize.medium), transform: "translateY(-50%)", backgroundColor: "var(--jumpy-interactive-active-regular-default-surface)", pointerEvents: "none" } }),
3152
+ /* @__PURE__ */ jsx(TimeColumn, { label: "Hour", values: hours, selected: String(displayHour).padStart(2, "0"), onSelect: (next) => commit("hour", next), theme }),
3153
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { zIndex: 1, display: "flex", alignItems: "center", justifyContent: "center", transform: "translateY(-0.1em)", width: sizeValue(theme, JumpySize.smallXS), height: sizeValue(theme, JumpySize.large), padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, color: "var(--jumpy-interactive-active-regular-default-text)", fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized).fontSize, fontWeight: textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized).fontWeight }, children: ":" }),
3154
+ /* @__PURE__ */ jsx(TimeColumn, { label: "Minute", values: minutes, selected: String(currentMinute).padStart(2, "0"), onSelect: (next) => commit("minute", next), theme }),
3155
+ !twentyFourHours ? /* @__PURE__ */ jsx(TimeColumn, { label: "Period", values: ["AM", "PM"], selected: currentPeriod, onSelect: (next) => commit("period", next), theme }) : null
3156
+ ] }),
3157
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.smallXS), padding: spacingValue(theme, JumpySpacing.large) }, children: [
3158
+ /* @__PURE__ */ jsx(JumpButton, { variant: "tertiary", onClick: () => setOpen(false), children: labels.cancelButton || "Cancel" }),
3159
+ /* @__PURE__ */ jsx(JumpButton, { onClick: () => setOpen(false), children: labels.applyButton || "Apply" })
3160
+ ] })
3161
+ ] }) }) : null
3162
+ ] });
3163
+ }
3164
+ function TimeColumn({ label, values, selected, onSelect, theme }) {
3165
+ const selectedIndex = Math.max(0, values.indexOf(selected));
3166
+ const wraps = values.length >= 5;
3167
+ const visibleValues = Array.from({ length: 5 }, (_, offsetIndex) => {
3168
+ const index = selectedIndex + offsetIndex - 2;
3169
+ if (wraps) return values[(index + values.length) % values.length];
3170
+ return index >= 0 && index < values.length ? values[index] : null;
3171
+ });
3172
+ const rowHeight = sizeValue(theme, JumpySize.large) + spacingValue(theme, JumpySpacing.small3XS) * 2;
3173
+ const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3174
+ const bodyLargeEmphasized = textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized);
3175
+ return /* @__PURE__ */ jsx("div", { "aria-label": label, style: { position: "relative", zIndex: 1, display: "flex", flexDirection: "column", overflow: "hidden", padding: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px`, margin: `0 ${-spacingValue(theme, JumpySpacing.small3XS)}px`, height: rowHeight * 5 }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column" }, children: visibleValues.map((value, offsetIndex) => {
3176
+ if (value === null) return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { flex: `0 0 ${rowHeight}px` } }, `${label}-empty-${offsetIndex}`);
3177
+ const active = value === selected;
3178
+ return /* @__PURE__ */ jsx("button", { type: "button", "data-jumpy-component": "time-picker-wheel-button", onClick: () => onSelect(value), "data-selected": active ? "true" : "false", style: { border: 0, width: sizeValue(theme, JumpySize.large), height: rowHeight, flex: `0 0 ${rowHeight}px`, backgroundColor: "transparent", color: active ? "var(--jumpy-interactive-active-regular-default-text, var(--jumpy-color-text-on-primary))" : "var(--jumpy-interactive-inactive-regular-default-text, var(--jumpy-color-text-primary))", padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, cursor: "pointer", fontFamily: theme.fontFamily.familyBase, fontSize: active ? bodyLargeEmphasized.fontSize : bodyMedium.fontSize, lineHeight: active ? bodyLargeEmphasized.lineHeight : bodyMedium.lineHeight, fontWeight: active ? bodyLargeEmphasized.fontWeight : bodyMedium.fontWeight }, children: value }, `${label}-${value}`);
3179
+ }) }) });
3180
+ }
3181
+ function flattenDropdownItems(items) {
3182
+ return items.flatMap((item) => [item, ...flattenDropdownItems(item.children || [])]);
3183
+ }
3184
+ function JumpDropdown({ label, hint, value, defaultValue = [], onChange, open, onOpenChange, items = [], placeholder, popupMinWidth = 0, disabled = false, readOnly = false, multiple = false, searchable = false, renderTrigger, renderItem, inputRef, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
3185
+ const theme = useJumpyTheme();
3186
+ const fieldContext = useContext(JumpyFieldContext);
3187
+ const generatedId = useId();
3188
+ const triggerId = props.id || `jumpy-dropdown-${generatedId}`;
3189
+ const [internalValue, setInternalValue] = useState(defaultValue);
3190
+ const [internalOpen, setInternalOpen] = useState(false);
3191
+ const [searchValue, setSearchValue] = useState("");
3192
+ const [focusedIndex, setFocusedIndex] = useState(-1);
3193
+ const dropdownRef = useRef(null);
3194
+ const triggerRef = useRef(null);
3195
+ const selectedValues = value ?? internalValue;
3196
+ const isOpen = open ?? internalOpen;
3197
+ const flattenedItems = flattenDropdownItems(items);
3198
+ const visibleItems = searchValue ? flattenedItems.filter((item) => item.label.toLowerCase().includes(searchValue.toLowerCase())) : flattenedItems;
3199
+ const enabledVisibleItems = visibleItems.filter((item) => !item.disabled);
3200
+ const selectedItems = flattenedItems.filter((item) => selectedValues.includes(item.value));
3201
+ const displayText = selectedItems.map((item) => item.label).join(", ");
3202
+ const dropdownOffset = spacingValue(theme, JumpySpacing.small3XS);
3203
+ const dropdownPadding = spacingValue(theme, JumpySpacing.medium);
3204
+ const dropdownMinHeight = sizeValue(theme, JumpySize.large2XL);
3205
+ const dropdownMaxHeight = sizeValue(theme, JumpySize.large8XL) * 2;
3206
+ const { refs, floatingStyles } = useFloating({
3207
+ open: isOpen,
3208
+ onOpenChange: setOpen,
3209
+ placement: "bottom-start",
3210
+ strategy: "fixed",
3211
+ whileElementsMounted: autoUpdate,
3212
+ middleware: [
3213
+ offset(dropdownOffset),
3214
+ flip({ padding: dropdownPadding }),
3215
+ shift({ padding: dropdownPadding }),
3216
+ size({
3217
+ padding: dropdownPadding,
3218
+ apply({ rects, availableWidth, availableHeight, elements }) {
3219
+ Object.assign(elements.floating.style, {
3220
+ width: `${Math.max(0, Math.min(Math.max(rects.reference.width, popupMinWidth), availableWidth))}px`,
3221
+ maxHeight: `${Math.max(dropdownMinHeight, Math.min(dropdownMaxHeight, availableHeight))}px`
3222
+ });
3223
+ }
3224
+ })
3225
+ ]
3226
+ });
3227
+ const triggerStyle = fieldContext && !label && !hint ? {
3228
+ paddingRight: sizeValue(theme, JumpySize.small2XS) + spacingValue(theme, JumpySpacing.medium),
3229
+ ...props.style || {}
3230
+ } : props.style;
3231
+ const triggerInputStyle = {
3232
+ ...triggerStyle || {},
3233
+ caretColor: "transparent"
3234
+ };
3235
+ function setOpen(next) {
3236
+ setInternalOpen(next);
3237
+ onOpenChange?.(next);
3238
+ if (!next) {
3239
+ setSearchValue("");
3240
+ setFocusedIndex(-1);
3241
+ } else if (enabledVisibleItems.length > 0 && focusedIndex < 0) {
3242
+ setFocusedIndex(visibleItems.findIndex((item) => !item.disabled));
3243
+ }
3244
+ }
3245
+ function commit(nextValues) {
3246
+ setInternalValue(nextValues);
3247
+ onChange?.(nextValues);
3248
+ if (!multiple) {
3249
+ setOpen(false);
3250
+ triggerRef.current?.focus();
3251
+ }
3252
+ }
3253
+ useEffect(() => {
3254
+ if (!isOpen) return void 0;
3255
+ function dismissOutside(event) {
3256
+ if (!dropdownRef.current?.contains(event.target) && !refs.floating.current?.contains(event.target)) setOpen(false);
3257
+ }
3258
+ document.addEventListener("pointerdown", dismissOutside);
3259
+ document.addEventListener("focusin", dismissOutside);
3260
+ return () => {
3261
+ document.removeEventListener("pointerdown", dismissOutside);
3262
+ document.removeEventListener("focusin", dismissOutside);
3263
+ };
3264
+ });
3265
+ function selectItem(item) {
3266
+ if (!item || item.disabled) return;
3267
+ if (multiple) {
3268
+ commit(selectedValues.includes(item.value) ? selectedValues.filter((v) => v !== item.value) : [...selectedValues, item.value]);
3269
+ return;
3270
+ }
3271
+ commit([item.value]);
3272
+ }
3273
+ function moveFocus(direction) {
3274
+ if (enabledVisibleItems.length === 0) return;
3275
+ const currentEnabledIndex = enabledVisibleItems.findIndex((item) => item.value === visibleItems[focusedIndex]?.value);
3276
+ const nextEnabledIndex = direction === "start" ? 0 : direction === "end" ? enabledVisibleItems.length - 1 : (currentEnabledIndex + direction + enabledVisibleItems.length) % enabledVisibleItems.length;
3277
+ setFocusedIndex(visibleItems.findIndex((item) => item.value === enabledVisibleItems[nextEnabledIndex].value));
3278
+ }
3279
+ const triggerInput = /* @__PURE__ */ jsx(
3280
+ JumpInput,
3281
+ {
3282
+ ...props,
3283
+ ref: (node) => {
3284
+ triggerRef.current = node;
3285
+ refs.setReference(node?.closest('[data-jumpy-component="field-control"]') || node);
3286
+ if (typeof inputRef === "function") inputRef(node);
3287
+ else if (inputRef) inputRef.current = node;
3288
+ },
3289
+ id: triggerId,
3290
+ style: triggerInputStyle,
3291
+ "data-jumpy-dropdown-trigger": "true",
3292
+ onChange: () => {
3293
+ },
3294
+ "data-has-value": Boolean(displayText),
3295
+ role: "combobox",
3296
+ "aria-expanded": isOpen,
3297
+ "aria-haspopup": "listbox",
3298
+ "aria-controls": isOpen ? `${triggerId}-listbox` : void 0,
3299
+ "aria-activedescendant": isOpen && focusedIndex >= 0 ? `${triggerId}-option-${focusedIndex}` : void 0,
3300
+ readOnly,
3301
+ disabled,
3302
+ value: displayText,
3303
+ placeholder: displayText ? void 0 : placeholder,
3304
+ autoComplete: "off",
3305
+ spellCheck: false,
3306
+ inputMode: "none",
3307
+ onClick: (event) => {
3308
+ if (!readOnly) setOpen(!isOpen);
3309
+ props.onClick?.(event);
3310
+ },
3311
+ onBlur: (event) => {
3312
+ props.onBlur?.(event);
3313
+ },
3314
+ onKeyDown: (event) => {
3315
+ if (event.key !== "Tab" && event.key !== "Escape") event.preventDefault();
3316
+ if (readOnly) {
3317
+ props.onKeyDown?.(event);
3318
+ return;
3319
+ }
3320
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
3321
+ setOpen(true);
3322
+ }
3323
+ if (event.key === "ArrowDown") moveFocus(1);
3324
+ if (event.key === "ArrowUp") moveFocus(-1);
3325
+ if (event.key === "Home") moveFocus("start");
3326
+ if (event.key === "End") moveFocus("end");
3327
+ if ((event.key === "Enter" || event.key === " ") && isOpen) selectItem(visibleItems[focusedIndex]);
3328
+ props.onKeyDown?.(event);
3329
+ },
3330
+ onPaste: (event) => event.preventDefault()
3331
+ }
3332
+ );
3333
+ const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block", fontSize: sizeValue(theme, JumpySize.small) }, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: isOpen ? "m16.59 15.705 1.41-1.41-6-6-6 6 1.41 1.41 4.59-4.58z" : "M7.41 8.295 6 9.705l6 6 6-6-1.41-1.41-4.59 4.58z", clipRule: "evenodd", fill: "currentColor" }) });
3334
+ const trigger = renderTrigger ? renderTrigger(selectedValues, isOpen) : fieldContext && !label && !hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
3335
+ triggerInput,
3336
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { pointerEvents: "none", position: "absolute", right: spacingValue(theme, JumpySpacing.medium), top: "50%", transform: "translateY(-50%)", color: theme.color.textSecondary, display: "inline-flex", alignItems: "center", justifyContent: "center", lineHeight: 1 }, children: trailing })
3337
+ ] }) : /* @__PURE__ */ jsx(JumpField, { label, hint, status, error, hideClearIcon, onClear: () => {
3338
+ commit([]);
3339
+ setSearchValue("");
3340
+ onClear?.();
3341
+ }, className, trailing, children: triggerInput });
3342
+ const listbox = isOpen && !disabled ? /* @__PURE__ */ jsx(JumpyPortal, { children: /* @__PURE__ */ jsxs(
3343
+ "div",
3344
+ {
3345
+ ref: refs.setFloating,
3346
+ id: `${triggerId}-listbox`,
3347
+ role: "listbox",
3348
+ "aria-multiselectable": multiple || void 0,
3349
+ "data-jumpy-component": "dropdown-popover",
3350
+ style: {
3351
+ ...floatingStyles,
3352
+ zIndex: JUMPY_POPUP_Z_INDEX,
3353
+ overflowX: "hidden",
3354
+ overflowY: "auto",
3355
+ borderRadius: radiusValue(theme, JumpyRadius.large),
3356
+ backgroundColor: `var(--jumpy-non-interactive-background-alternative, var(--jumpy-color-non-interactive-background-alternative, ${theme.color.surfaceCard}))`,
3357
+ boxShadow: theme.shadow.positiveMedium,
3358
+ padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`
3359
+ },
3360
+ children: [
3361
+ searchable ? /* @__PURE__ */ jsx("div", { style: { margin: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px` }, children: /* @__PURE__ */ jsx(JumpSearchField, { label: "Search", placeholder: "Search", value: searchValue, onChange: (event) => {
3362
+ setSearchValue(event.target.value);
3363
+ setFocusedIndex(-1);
3364
+ } }) }) : null,
3365
+ visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "dropdown-empty-state", style: { padding: `${spacingValue(theme, JumpySpacing.medium)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: "var(--jumpy-interactive-inactive-regular-default-text)", fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.bodyMedium).fontSize }, children: "No options available" }) : null,
3366
+ visibleItems.map((item, index) => {
3367
+ const selected = selectedValues.includes(item.value);
3368
+ const listItemProps = {
3369
+ id: `${triggerId}-option-${index}`,
3370
+ role: "option",
3371
+ "aria-selected": selected,
3372
+ disabled: item.disabled,
3373
+ onMouseDown: (event) => event.preventDefault(),
3374
+ onMouseEnter: () => setFocusedIndex(index),
3375
+ onClick: () => {
3376
+ selectItem(item);
3377
+ }
3378
+ };
3379
+ return renderItem ? renderItem(item, selected, listItemProps) : /* @__PURE__ */ jsx(JumpListItem, { selected, ...listItemProps, children: item.label }, String(item.value));
3380
+ })
3381
+ ]
3382
+ }
3383
+ ) }) : null;
3384
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, "data-jumpy-component": "dropdown", onKeyDown: (event) => {
3385
+ if (event.key === KeyboardKeys.ESCAPE && isOpen && !event.defaultPrevented) {
3386
+ event.preventDefault();
3387
+ event.stopPropagation();
3388
+ setOpen(false);
3389
+ triggerRef.current?.focus();
3390
+ }
3391
+ }, style: { position: "relative", width: "100%" }, children: [
3392
+ trigger,
3393
+ listbox
3394
+ ] });
3395
+ }
3396
+ function JumpSpinner({ size: size2 = "medium", mode = "indeterminate", value = 0, showTrack = true, inverse = false, className = "", ...props }) {
3397
+ const theme = useJumpyTheme();
3398
+ const sizeMap = {
3399
+ xSmall: { diameter: 20, strokeWidth: 2 },
3400
+ small: { diameter: 24, strokeWidth: 2 },
3401
+ medium: { diameter: 32, strokeWidth: 2 },
3402
+ large: { diameter: 48, strokeWidth: 4 },
3403
+ xLarge: { diameter: 56, strokeWidth: 4 },
3404
+ xxLarge: { diameter: 72, strokeWidth: 4 }
3405
+ };
3406
+ const { diameter, strokeWidth } = sizeMap[size2] || sizeMap.medium;
3407
+ const radius = (diameter - strokeWidth) / 2;
3408
+ const center = diameter / 2;
3409
+ const circumference = 2 * Math.PI * radius;
3410
+ const clampedValue = Math.min(Math.max(value, 0), 100);
3411
+ const dash = mode === "determinate" ? clampedValue / 100 * circumference : circumference * 0.72;
3412
+ const gap = circumference - dash;
3413
+ return /* @__PURE__ */ jsxs("svg", { ...props, "data-jumpy-component": "spinner", className: cn(className), width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, role: "progressbar", "aria-valuemin": mode === "determinate" ? 0 : void 0, "aria-valuemax": mode === "determinate" ? 100 : void 0, "aria-valuenow": mode === "determinate" ? clampedValue : void 0, "aria-label": props["aria-label"] || (mode === "determinate" ? `Loading progress: ${clampedValue}%` : "Loading in progress"), style: { color: inverse ? theme.color.textOnPrimary : theme.color.surfacePrimary, ...props.style || {} }, children: [
3414
+ showTrack ? /* @__PURE__ */ jsx("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: theme.color.borderSubtle, strokeWidth }) : null,
3415
+ /* @__PURE__ */ jsx("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "currentColor", strokeWidth, strokeLinecap: "round", strokeDasharray: `${dash} ${gap}`, transform: `rotate(-90 ${center} ${center})`, children: mode === "indeterminate" ? /* @__PURE__ */ jsx("animateTransform", { attributeName: "transform", type: "rotate", from: `0 ${center} ${center}`, to: `360 ${center} ${center}`, dur: "1s", repeatCount: "indefinite" }) : null })
3416
+ ] });
3417
+ }
3418
+ function JumpDialog({ as, visible = false, onVisibleChange, title, description, footer, children, className = "", disableBackdropClick = false, disableContentScroll = false, fullScreen = false, overlayProps, slots, innerClasses = {}, ...props }) {
3419
+ const theme = useJumpyTheme();
3420
+ const Component = as || "div";
3421
+ const labelId = useId();
3422
+ const descriptionId = useId();
3423
+ const dialogTitle = slots?.title ?? title;
3424
+ const dialogDescription = slots?.description ?? description;
3425
+ const dialogFooter = slots?.footer ?? footer;
3426
+ const dialogIcon = slots?.icon;
3427
+ const dialogIllustration = slots?.illustration;
3428
+ const titleStyle = textStyleValue(theme, JumpyTextStyle.titleLarge);
3429
+ const descriptionStyle = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3430
+ const { className: overlayClassName, style: overlayStyle, onClick: overlayOnClick, ...restOverlayProps } = overlayProps || {};
3431
+ if (!visible) return null;
3432
+ return /* @__PURE__ */ jsx(
3433
+ JumpyModalLayer,
3434
+ {
3435
+ ...restOverlayProps,
3436
+ onVisibleChange,
3437
+ "data-jumpy-component": "dialog-overlay",
3438
+ "data-disable-content-scroll": disableContentScroll ? "true" : "false",
3439
+ role: "presentation",
3440
+ className: cn(overlayClassName),
3441
+ onClick: (event) => {
3442
+ overlayOnClick?.(event);
3443
+ if (event.target === event.currentTarget && !event.defaultPrevented && !disableBackdropClick) onVisibleChange?.(false);
3444
+ },
3445
+ style: overlayStyle,
3446
+ children: /* @__PURE__ */ jsxs(
3447
+ Component,
3448
+ {
3449
+ ...props,
3450
+ "data-jumpy-component": "dialog",
3451
+ "data-full-screen": fullScreen ? "true" : "false",
3452
+ "data-no-footer": dialogFooter ? "false" : "true",
3453
+ role: "dialog",
3454
+ "aria-modal": "true",
3455
+ "aria-labelledby": dialogTitle ? labelId : void 0,
3456
+ "aria-describedby": dialogDescription ? descriptionId : void 0,
3457
+ "aria-label": !dialogTitle ? props["aria-label"] || title : props["aria-label"],
3458
+ className: cn(className),
3459
+ onClick: (event) => {
3460
+ event.stopPropagation();
3461
+ props.onClick?.(event);
3462
+ },
3463
+ style: {
3464
+ "--jumpy-dialog-width": props.style?.width || "472px",
3465
+ ...props.style || {}
3466
+ },
3467
+ children: [
3468
+ dialogIllustration ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "dialog-illustration", className: innerClasses.illustration?.className, style: innerClasses.illustration?.style, children: dialogIllustration }) : null,
3469
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "dialog-body", className: innerClasses.body?.className, style: innerClasses.body?.style, children: [
3470
+ dialogIcon ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "dialog-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, children: dialogIcon }) : null,
3471
+ dialogTitle ? /* @__PURE__ */ jsx(
3472
+ "div",
3473
+ {
3474
+ id: labelId,
3475
+ "data-jumpy-slot": "dialog-title",
3476
+ className: innerClasses.title?.className,
3477
+ style: {
3478
+ margin: 0,
3479
+ fontFamily: theme.fontFamily.familyBase,
3480
+ fontSize: titleStyle.fontSize,
3481
+ lineHeight: titleStyle.lineHeight,
3482
+ fontWeight: titleStyle.fontWeight,
3483
+ letterSpacing: titleStyle.letterSpacing,
3484
+ ...innerClasses.title?.style || {}
3485
+ },
3486
+ children: dialogTitle
3487
+ }
3488
+ ) : null,
3489
+ /* @__PURE__ */ jsxs("div", { "data-jumpy-slot": "dialog-scrollable-content", className: innerClasses.scrollableContent?.className, style: innerClasses.scrollableContent?.style, children: [
3490
+ dialogDescription ? /* @__PURE__ */ jsx(
3491
+ "div",
3492
+ {
3493
+ id: descriptionId,
3494
+ "data-jumpy-slot": "dialog-description",
3495
+ className: innerClasses.description?.className,
3496
+ style: {
3497
+ margin: 0,
3498
+ fontFamily: theme.fontFamily.familyBase,
3499
+ fontSize: descriptionStyle.fontSize,
3500
+ lineHeight: descriptionStyle.lineHeight,
3501
+ fontWeight: descriptionStyle.fontWeight,
3502
+ letterSpacing: descriptionStyle.letterSpacing,
3503
+ ...innerClasses.description?.style || {}
3504
+ },
3505
+ children: dialogDescription
3506
+ }
3507
+ ) : null,
3508
+ children
3509
+ ] })
3510
+ ] }),
3511
+ dialogFooter ? /* @__PURE__ */ jsx("div", { "data-jumpy-slot": "dialog-footer", className: innerClasses.footer?.className, style: innerClasses.footer?.style, children: dialogFooter }) : null
3512
+ ]
3513
+ }
3514
+ )
3515
+ }
3516
+ );
3517
+ }
3518
+ function JumpLoader({ label = "Jumpy UI wird geladen", fullScreen = false, className = "", decorativeBackground = false }) {
3519
+ const theme = useJumpyTheme();
3520
+ const content = /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center text-center", className), role: "status", "aria-live": "polite", children: [
3521
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", style: { height: theme.sizing.loaderSize, width: theme.sizing.loaderSize }, children: [
3522
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { borderRadius: theme.radius.large2XL, backgroundColor: theme.color.infoSurface } }),
3523
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-2", style: { borderRadius: theme.radius.largeXL, border: `1px solid ${theme.color.infoBorder}`, backgroundColor: theme.color.surfaceCard, boxShadow: theme.shadow.loader } }),
3524
+ /* @__PURE__ */ jsx("span", { className: "relative grid place-items-center", style: { color: theme.color.surfacePrimary }, children: /* @__PURE__ */ jsx(JumpSpinner, { size: "large", "aria-label": label }) })
3525
+ ] }),
3526
+ /* @__PURE__ */ jsx("p", { className: "mt-5 text-[11px] font-bold uppercase tracking-[0.18em]", style: { color: theme.color.surfacePrimary }, children: label }),
3527
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-1.5", "aria-hidden": "true", children: [
3528
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.2s]", style: { backgroundColor: theme.color.surfacePrimary } }),
3529
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.1s]", style: { backgroundColor: theme.color.successBorder } }),
3530
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full", style: { backgroundColor: theme.color.infoBorder } })
3531
+ ] }),
3532
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Laden" })
3533
+ ] });
3534
+ if (!fullScreen) return content;
3535
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-screen items-center justify-center overflow-hidden p-6", style: { backgroundColor: theme.color.surfaceCanvas, color: theme.color.textPrimary }, children: [
3536
+ decorativeBackground && /* @__PURE__ */ jsx(JumpBackground, { snow: false }),
3537
+ /* @__PURE__ */ jsx(JumpCard, { className: cn("px-10 py-9", decorativeBackground && "relative z-10"), children: content })
3538
+ ] });
3539
+ }
3540
+ function JumpButton({ as, size: size2, variant = "primary", status = "regular", className = "", isLoading = false, disabled, elevated = false, slots, leadingIcon, trailingIcon, children, ...props }) {
3541
+ const theme = useJumpyTheme();
3542
+ const variantStyles = getButtonVariantStyles(theme, variant, status);
3543
+ const height = sizeValue(theme, size2 ?? JumpySize.large);
3544
+ if (!Number.isFinite(height) || height < 0) throw new Error(`Invalid JumpButton size token: ${size2}`);
3545
+ const compact = height < sizeValue(theme, JumpySize.large);
3546
+ const textStyle = textStyleValue(theme, compact ? JumpyTextStyle.bodySmallEmphasized : JumpyTextStyle.bodyMediumEmphasized);
3547
+ const iconSize = sizeValue(theme, compact ? JumpySize.small3XS : JumpySize.small2XS);
3548
+ const [pressed, setPressed] = useState(false);
3549
+ const leading = slots?.leading ?? leadingIcon;
3550
+ const trailing = slots?.trailing ?? trailingIcon;
3551
+ const Component = as || "button";
3552
+ const isButton = Component === "button";
3553
+ const isDisabled = disabled || isLoading;
3554
+ const displayedLeading = isLoading ? /* @__PURE__ */ jsx(JumpSpinner, { size: "xSmall", showTrack: false, "aria-hidden": "true", style: { display: "block", width: iconSize, height: iconSize, color: "inherit" } }) : leading;
3555
+ const spacing = getButtonSpacing(theme, variant, Boolean(displayedLeading), Boolean(trailing));
3556
+ const hasBorder = variant === "secondary" || variant === "tertiary";
3557
+ if (size2 !== void 0) {
3558
+ const border = hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0;
3559
+ const contentHeight = Math.max(textStyle.fontSize * textStyle.lineHeight, displayedLeading || trailing ? iconSize : 0);
3560
+ spacing.paddingTop = spacing.paddingBottom = Math.max(0, (height - contentHeight - border * 2) / 2);
3561
+ if (compact) {
3562
+ spacing.gap = spacingValue(theme, JumpySpacing.small3XS);
3563
+ spacing.paddingLeft = spacing.paddingRight = spacingValue(theme, variant === "borderless" ? JumpySpacing.zero : JumpySpacing.medium);
3564
+ }
3565
+ }
3566
+ const content = /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.gap, whiteSpace: "nowrap" }, children: [
3567
+ displayedLeading ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "button-leading", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: displayedLeading }) : null,
3568
+ /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "button-label", children }),
3569
+ trailing ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "button-trailing", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: trailing }) : null
3570
+ ] });
3571
+ return /* @__PURE__ */ jsx(
3572
+ Component,
3573
+ {
3574
+ ...props,
3575
+ type: isButton ? props.type || "button" : props.type,
3576
+ "data-jumpy-component": "icon-button",
3577
+ "data-variant": variant,
3578
+ "data-status": status,
3579
+ "data-elevated": elevated ? "true" : "false",
3580
+ "data-loading": isLoading ? "true" : "false",
3581
+ "data-size": size2,
3582
+ "data-pressed": pressed ? "true" : "false",
3583
+ "aria-busy": isLoading || void 0,
3584
+ "aria-disabled": !isButton && isDisabled ? true : props["aria-disabled"],
3585
+ tabIndex: !isButton && isDisabled ? -1 : props.tabIndex,
3586
+ disabled: isButton ? isDisabled : void 0,
3587
+ onClick: (event) => {
3588
+ if (isDisabled) {
3589
+ event.preventDefault();
3590
+ return;
3591
+ }
3592
+ props.onClick?.(event);
3593
+ },
3594
+ onPointerDown: (event) => {
3595
+ setPressed(true);
3596
+ props.onPointerDown?.(event);
3597
+ },
3598
+ onPointerUp: (event) => {
3599
+ setPressed(false);
3600
+ props.onPointerUp?.(event);
3601
+ },
3602
+ onPointerLeave: (event) => {
3603
+ setPressed(false);
3604
+ props.onPointerLeave?.(event);
3605
+ },
3606
+ onBlur: (event) => {
3607
+ setPressed(false);
3608
+ props.onBlur?.(event);
3609
+ },
3610
+ className: cn("disabled:cursor-not-allowed", className),
3611
+ style: {
3612
+ display: "inline-flex",
3613
+ alignItems: "center",
3614
+ justifyContent: "center",
3615
+ minHeight: height,
3616
+ appearance: "none",
3617
+ WebkitAppearance: "none",
3618
+ boxSizing: "border-box",
3619
+ paddingLeft: spacing.paddingLeft,
3620
+ paddingRight: spacing.paddingRight,
3621
+ paddingTop: spacing.paddingTop,
3622
+ paddingBottom: spacing.paddingBottom,
3623
+ borderRadius: radiusValue(theme, JumpyRadius.small),
3624
+ letterSpacing: textStyle.letterSpacing,
3625
+ fontFamily: theme.fontFamily.familyBase,
3626
+ fontSize: textStyle.fontSize,
3627
+ lineHeight: textStyle.lineHeight,
3628
+ fontWeight: textStyle.fontWeight,
3629
+ // Primary regular disabled tokens already encode the subdued appearance.
3630
+ opacity: isDisabled && !(variant === "primary" && status === "regular") ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
3631
+ borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
3632
+ borderStyle: hasBorder ? "solid" : "none",
3633
+ ...variantStyles,
3634
+ ...elevated && !isDisabled ? { "--jumpy-action-default-shadow": shadowValue(theme, JumpyShadowStyle.positiveMedium) } : {},
3635
+ ...props.style || {}
3636
+ },
3637
+ children: /* @__PURE__ */ jsx("span", { style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", whiteSpace: "nowrap" }, children: content })
3638
+ }
3639
+ );
3640
+ }
3641
+ function JumpIconButton({ as, variant = "primary", status = "regular", alternateHover = false, elevated = false, className = "", disabled, children, "aria-label": ariaLabel, ...props }) {
3642
+ const theme = useJumpyTheme();
3643
+ const actionVariant = variant === "primary" ? "secondary" : variant === "secondary" ? "tertiary" : variant;
3644
+ const variantStyles = getButtonVariantStyles(theme, actionVariant, status);
3645
+ const actionTokenPrefix = `--jumpy-actions-${actionVariant}-${status}`;
3646
+ const [pressed, setPressed] = useState(false);
3647
+ const Component = as || "button";
3648
+ const isButton = Component === "button";
3649
+ const supportsDisabled = typeof Component === "string" && supportsDisabledAttribute(Component);
3650
+ const hasBorder = variant === "primary";
3651
+ return /* @__PURE__ */ jsx(
3652
+ Component,
3653
+ {
3654
+ ...props,
3655
+ type: isButton ? props.type || "button" : props.type,
3656
+ "aria-label": ariaLabel,
3657
+ "data-jumpy-component": "button",
3658
+ "data-variant": variant,
3659
+ "data-status": status,
3660
+ "data-elevated": elevated ? "true" : "false",
3661
+ "data-pressed": pressed ? "true" : "false",
3662
+ "aria-disabled": !supportsDisabled && disabled ? true : props["aria-disabled"],
3663
+ tabIndex: !supportsDisabled && disabled ? -1 : props.tabIndex,
3664
+ disabled: supportsDisabled ? disabled : void 0,
3665
+ onPointerDown: (event) => {
3666
+ setPressed(true);
3667
+ props.onPointerDown?.(event);
3668
+ },
3669
+ onPointerUp: (event) => {
3670
+ setPressed(false);
3671
+ props.onPointerUp?.(event);
3672
+ },
3673
+ onPointerLeave: (event) => {
3674
+ setPressed(false);
3675
+ props.onPointerLeave?.(event);
3676
+ },
3677
+ onBlur: (event) => {
3678
+ setPressed(false);
3679
+ props.onBlur?.(event);
3680
+ },
3681
+ className: cn("disabled:cursor-not-allowed", className),
3682
+ style: {
3683
+ display: "inline-grid",
3684
+ placeItems: "center",
3685
+ width: sizeValue(theme, JumpySize.large),
3686
+ height: sizeValue(theme, JumpySize.large),
3687
+ minWidth: sizeValue(theme, JumpySize.large),
3688
+ minHeight: sizeValue(theme, JumpySize.large),
3689
+ appearance: "none",
3690
+ WebkitAppearance: "none",
3691
+ boxSizing: "border-box",
3692
+ padding: spacingValue(theme, JumpySpacing.small2XS),
3693
+ borderRadius: radiusValue(theme, JumpyRadius.small),
3694
+ borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
3695
+ borderStyle: hasBorder ? "solid" : "none",
3696
+ opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
3697
+ ...variantStyles,
3698
+ "--jumpy-action-default-icon": `var(${actionTokenPrefix}-default-action, ${variantStyles["--jumpy-action-default-icon"]})`,
3699
+ "--jumpy-action-hover-icon": `var(${actionTokenPrefix}-hover-action, ${variantStyles["--jumpy-action-hover-icon"]})`,
3700
+ "--jumpy-action-focus-icon": `var(${actionTokenPrefix}-focus-action, ${variantStyles["--jumpy-action-focus-icon"]})`,
3701
+ "--jumpy-action-pressed-icon": `var(${actionTokenPrefix}-pressed-action, ${variantStyles["--jumpy-action-pressed-icon"]})`,
3702
+ "--jumpy-action-disabled-icon": `var(${actionTokenPrefix}-disabled-action, ${variantStyles["--jumpy-action-disabled-icon"]})`,
3703
+ ...elevated && !disabled ? { "--jumpy-action-default-shadow": shadowValue(theme, JumpyShadowStyle.positiveMedium) } : {},
3704
+ ...alternateHover ? {
3705
+ "--jumpy-action-hover-surface": `var(--jumpy-interactive-inactive-${status}-hover-surface-alternate, ${variantStyles["--jumpy-action-hover-surface"]})`,
3706
+ "--jumpy-action-pressed-surface": `var(--jumpy-interactive-inactive-${status}-pressed-surface-alternate, ${variantStyles["--jumpy-action-pressed-surface"]})`
3707
+ } : {},
3708
+ ...props.style || {}
3709
+ },
3710
+ children: /* @__PURE__ */ jsx("span", { style: { display: "grid", placeItems: "center", width: sizeValue(theme, JumpySize.small), height: sizeValue(theme, JumpySize.small), lineHeight: 0, fontSize: sizeValue(theme, JumpySize.small) }, "aria-hidden": "true", children })
3711
+ }
3712
+ );
3713
+ }
3714
+ function JumpLinkButton({ href, variant = "borderless", status = "regular", className = "", leadingIcon, trailingIcon, children, ...props }) {
3715
+ const theme = useJumpyTheme();
3716
+ const variantStyles = getButtonVariantStyles(theme, variant, status);
3717
+ const bodyMediumEmphasized = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
3718
+ const iconSize = sizeValue(theme, JumpySize.small2XS);
3719
+ const [pressed, setPressed] = useState(false);
3720
+ const spacing = getButtonSpacing(theme, variant, Boolean(leadingIcon), Boolean(trailingIcon));
3721
+ const hasBorder = variant === "secondary" || variant === "tertiary";
3722
+ return /* @__PURE__ */ jsx(
3723
+ "a",
3724
+ {
3725
+ href,
3726
+ ...props,
3727
+ "data-jumpy-component": "link-button",
3728
+ "data-variant": variant,
3729
+ "data-status": status,
3730
+ "data-pressed": pressed ? "true" : "false",
3731
+ onPointerDown: (event) => {
3732
+ setPressed(true);
3733
+ props.onPointerDown?.(event);
3734
+ },
3735
+ onPointerUp: (event) => {
3736
+ setPressed(false);
3737
+ props.onPointerUp?.(event);
3738
+ },
3739
+ onPointerLeave: (event) => {
3740
+ setPressed(false);
3741
+ props.onPointerLeave?.(event);
3742
+ },
3743
+ onBlur: (event) => {
3744
+ setPressed(false);
3745
+ props.onBlur?.(event);
3746
+ },
3747
+ className: cn(className),
3748
+ style: {
3749
+ display: "inline-flex",
3750
+ alignItems: "center",
3751
+ justifyContent: "center",
3752
+ minHeight: sizeValue(theme, JumpySize.large),
3753
+ appearance: "none",
3754
+ WebkitAppearance: "none",
3755
+ boxSizing: "border-box",
3756
+ paddingLeft: spacing.paddingLeft,
3757
+ paddingRight: spacing.paddingRight,
3758
+ paddingTop: spacing.paddingTop,
3759
+ paddingBottom: spacing.paddingBottom,
3760
+ borderRadius: radiusValue(theme, JumpyRadius.small),
3761
+ letterSpacing: bodyMediumEmphasized.letterSpacing,
3762
+ fontFamily: theme.fontFamily.familyBase,
3763
+ fontSize: bodyMediumEmphasized.fontSize,
3764
+ lineHeight: bodyMediumEmphasized.lineHeight,
3765
+ fontWeight: bodyMediumEmphasized.fontWeight,
3766
+ borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
3767
+ borderStyle: hasBorder ? "solid" : "none",
3768
+ ...variantStyles,
3769
+ ...props.style || {}
3770
+ },
3771
+ children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.gap, whiteSpace: "nowrap" }, children: [
3772
+ leadingIcon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center", style: { width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: leadingIcon }) : null,
3773
+ /* @__PURE__ */ jsx("span", { children }),
3774
+ trailingIcon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center", style: { width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: trailingIcon }) : null
3775
+ ] })
3776
+ }
3777
+ );
3778
+ }
3779
+ var JumpInput = forwardRef(function JumpInput2({ className = "", ...props }, ref) {
3780
+ const theme = useJumpyTheme();
3781
+ const fieldContext = useContext(JumpyFieldContext);
3782
+ const inputOffset = fieldContext?.hasLabel ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero);
3783
+ const inputMinHeight = fieldContext ? void 0 : sizeValue(theme, JumpySize.largeXL) - spacingValue(theme, JumpySpacing.small3XS) * 2;
3784
+ const inputElement = /* @__PURE__ */ jsx(
3785
+ "input",
3786
+ {
3787
+ ...props,
3788
+ ref,
3789
+ "data-jumpy-component": "input",
3790
+ className: cn("w-full text-sm disabled:cursor-not-allowed", className),
3791
+ style: {
3792
+ ...getFieldInputStyles(theme, props.disabled),
3793
+ minHeight: inputMinHeight,
3794
+ marginTop: inputOffset,
3795
+ ...props.style || {}
3796
+ }
3797
+ }
3798
+ );
3799
+ if (fieldContext) {
3800
+ return inputElement;
3801
+ }
3802
+ return renderStandaloneField(theme, props.disabled, "", null, inputElement, props.status || "regular", props.readOnly);
3803
+ });
3804
+ function JumpTextarea({ className = "", ...props }) {
3805
+ const theme = useJumpyTheme();
3806
+ const fieldContext = useContext(JumpyFieldContext);
3807
+ const inputOffset = fieldContext?.hasLabel ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero);
3808
+ const textareaElement = /* @__PURE__ */ jsx(
3809
+ "textarea",
3810
+ {
3811
+ ...props,
3812
+ "data-jumpy-component": "textarea",
3813
+ className: cn("w-full text-sm disabled:cursor-not-allowed", className),
3814
+ style: {
3815
+ ...getFieldInputStyles(theme, props.disabled),
3816
+ minHeight: props.rows ? Number(props.rows) * 24 : sizeValue(theme, JumpySize.large2XL),
3817
+ marginTop: inputOffset,
3818
+ resize: "none",
3819
+ ...props.style || {}
3820
+ }
3821
+ }
3822
+ );
3823
+ if (fieldContext) {
3824
+ return textareaElement;
3825
+ }
3826
+ return renderStandaloneField(theme, props.disabled, "", { alignItems: "flex-start", height: "auto", maxHeight: "none", paddingRight: spacingValue(theme, JumpySpacing.small3XS) }, textareaElement, props.status || "regular", props.readOnly);
3827
+ }
3828
+ function JumpBaseInput({ label, hint, children, className = "", error = false, hideClearIcon = false, showCharCount = false, onClear, ...props }) {
3829
+ const theme = useJumpyTheme();
3830
+ const fieldId = useId();
3831
+ const labelId = useId();
3832
+ const helperId = useId();
3833
+ const counterId = useId();
3834
+ const child = Children.only(children);
3835
+ const childValue = isValidElement(child) ? child.props.value ?? child.props.defaultValue ?? "" : "";
3836
+ const childHasValue = isValidElement(child) && (child.props["data-has-value"] === true || child.props["data-has-value"] === "true");
3837
+ const [focused, setFocused] = useState(false);
3838
+ const [hasValue, setHasValue] = useState(childHasValue || Boolean(String(childValue).length));
3839
+ const [inputValue, setInputValue] = useState(String(childValue ?? ""));
3840
+ const bodySmall = textStyleValue(theme, JumpyTextStyle.bodySmall);
3841
+ const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3842
+ const labelSmall = textStyleValue(theme, JumpyTextStyle.labelSmall);
3843
+ useEffect(() => {
3844
+ setHasValue(childHasValue || Boolean(String(childValue).length));
3845
+ setInputValue(String(childValue ?? ""));
3846
+ }, [childHasValue, childValue]);
3847
+ if (!isValidElement(child)) {
3848
+ return /* @__PURE__ */ jsx("div", { className: cn("block w-full", className), children });
3849
+ }
3850
+ const originalOnFocus = child.props.onFocus;
3851
+ const originalOnBlur = child.props.onBlur;
3852
+ const originalOnChange = child.props.onChange;
3853
+ const disabled = Boolean(child.props.disabled);
3854
+ const isDropdownTrigger = child.props["data-jumpy-dropdown-trigger"] === true || child.props["data-jumpy-dropdown-trigger"] === "true";
3855
+ const readOnly = Boolean(child.props.readOnly) && !isDropdownTrigger;
3856
+ const floating = focused && !readOnly || hasValue || Boolean(props.slots?.content);
3857
+ const isTextarea = child.type === JumpTextarea || child.props["data-jumpy-token-editor"] && child.props.multiline;
3858
+ const isDropdown = isDropdownTrigger || child.props["data-jumpy-component"] === "dropdown";
3859
+ const status = error ? "alarm" : props.status || "regular";
3860
+ const leading = props.slots?.leading ?? props.leading;
3861
+ const trailing = props.slots?.trailing ?? props.trailing;
3862
+ const content = props.slots?.content;
3863
+ const helperText = props.slots?.helperText ?? hint;
3864
+ const hasHelperText = Boolean(helperText);
3865
+ const maxLength = child.props.maxLength;
3866
+ const hasCounter = showCharCount && maxLength !== void 0 && maxLength !== null;
3867
+ const showClearButton = hasValue && !hideClearIcon && !disabled && !readOnly;
3868
+ const showErrorIcon = Boolean(error) && !disabled && !readOnly;
3869
+ const showTrailing = showClearButton || trailing || showErrorIcon;
3870
+ const describedBy = [child.props["aria-describedby"], hasHelperText ? helperId : void 0, hasCounter ? counterId : void 0].filter(Boolean).join(" ") || void 0;
3871
+ const labelScale = labelSmall.fontSize / bodyMedium.fontSize;
3872
+ const fieldInnerHeight = sizeValue(theme, JumpySize.largeXL) - spacingValue(theme, JumpySpacing.small3XS) * 2;
3873
+ const labelRestingY = fieldInnerHeight / 2;
3874
+ const labelFloatingY = (labelSmall.fontSize * labelSmall.lineHeight - labelSmall.fontSize) / 2;
3875
+ const labelTiming = focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease;
3876
+ const enhancedChild = cloneElement(child, {
3877
+ id: child.props.id || fieldId,
3878
+ "aria-labelledby": child.props["aria-labelledby"] || (!child.props["aria-label"] && label ? labelId : void 0),
3879
+ "aria-invalid": error ? true : child.props["aria-invalid"],
3880
+ "aria-describedby": describedBy,
3881
+ placeholder: !label || isDropdown || floating ? child.props.placeholder : " ",
3882
+ style: content ? {
3883
+ ...child.props.style || {},
3884
+ position: "absolute",
3885
+ inset: 0,
3886
+ zIndex: 2,
3887
+ width: "100%",
3888
+ height: "100%",
3889
+ margin: 0,
3890
+ opacity: 0,
3891
+ cursor: disabled || readOnly ? "default" : "pointer"
3892
+ } : child.props.style,
3893
+ onFocus: (event) => {
3894
+ setFocused(true);
3895
+ originalOnFocus?.(event);
3896
+ },
3897
+ onBlur: (event) => {
3898
+ setFocused(false);
3899
+ setHasValue(childHasValue || Boolean(String(event.target?.value ?? "").length));
3900
+ originalOnBlur?.(event);
3901
+ },
3902
+ onChange: (event) => {
3903
+ setHasValue(childHasValue || Boolean(String(event.target?.value ?? "").length));
3904
+ setInputValue(String(event.target?.value ?? ""));
3905
+ originalOnChange?.(event);
3906
+ }
3907
+ });
3908
+ const Container = label ? "label" : "div";
3909
+ const containerProps = label ? { htmlFor: child.props.id || fieldId } : {
3910
+ onClick: () => {
3911
+ if (!disabled) document.getElementById(child.props.id || fieldId)?.focus();
3912
+ }
3913
+ };
3914
+ return /* @__PURE__ */ jsxs("div", { "data-jumpy-component": "base-input-root", className: cn("inline-flex w-full max-w-full flex-col", className), style: { color: "var(--jumpy-cmp-base-input-helper-color)" }, children: [
3915
+ /* @__PURE__ */ jsx(
3916
+ Container,
3917
+ {
3918
+ ...containerProps,
3919
+ "data-jumpy-component": "base-input",
3920
+ "data-disabled": disabled ? "true" : "false",
3921
+ "data-readonly": readOnly ? "true" : "false",
3922
+ "data-status": status,
3923
+ style: { color: "var(--jumpy-cmp-base-input-helper-color)" },
3924
+ children: /* @__PURE__ */ jsxs(
3925
+ "div",
3926
+ {
3927
+ "data-jumpy-component": "field-control",
3928
+ "data-disabled": disabled ? "true" : "false",
3929
+ "data-readonly": readOnly ? "true" : "false",
3930
+ "data-status": status,
3931
+ "data-testid": props["data-testid"],
3932
+ onMouseDown: (event) => {
3933
+ const control = document.getElementById(child.props.id || fieldId);
3934
+ const interactive = event.target.closest("input, textarea, select, button, a, [tabindex]");
3935
+ if (disabled || !control || event.button !== 0 || interactive && event.currentTarget.contains(interactive)) return;
3936
+ event.preventDefault();
3937
+ control.focus();
3938
+ },
3939
+ style: {
3940
+ ...getFieldShellStyles(theme, disabled, status, focused, readOnly),
3941
+ alignItems: isTextarea ? "flex-start" : "center",
3942
+ position: "relative",
3943
+ height: isTextarea ? "auto" : sizeValue(theme, JumpySize.largeXL),
3944
+ minHeight: isTextarea ? sizeValue(theme, JumpySize.large2XL) : sizeValue(theme, JumpySize.largeXL)
3945
+ },
3946
+ children: [
3947
+ leading ? /* @__PURE__ */ jsx(
3948
+ "span",
3949
+ {
3950
+ "aria-hidden": "true",
3951
+ className: "inline-flex shrink-0 items-center justify-center",
3952
+ style: {
3953
+ width: sizeValue(theme, JumpySize.small),
3954
+ height: sizeValue(theme, JumpySize.small),
3955
+ color: "var(--jumpy-cmp-base-input-icon-fill)",
3956
+ transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
3957
+ },
3958
+ children: leading
3959
+ }
3960
+ ) : null,
3961
+ /* @__PURE__ */ jsxs(
3962
+ "div",
3963
+ {
3964
+ style: {
3965
+ position: "relative",
3966
+ display: "flex",
3967
+ alignItems: isTextarea ? "initial" : "center",
3968
+ alignSelf: "stretch",
3969
+ flex: 1,
3970
+ minWidth: 0,
3971
+ overflow: "hidden"
3972
+ },
3973
+ children: [
3974
+ label && /* @__PURE__ */ jsx(
3975
+ "span",
3976
+ {
3977
+ id: labelId,
3978
+ "data-testid": props.labelTestId,
3979
+ style: {
3980
+ position: "absolute",
3981
+ left: spacingValue(theme, JumpySpacing.zero),
3982
+ top: spacingValue(theme, JumpySpacing.zero),
3983
+ transform: floating ? `translateY(${labelFloatingY}px) scale(${labelScale})` : `translateY(calc(${labelRestingY}px - 50%)) scale(1)`,
3984
+ transformOrigin: "left top",
3985
+ color: "var(--jumpy-cmp-base-input-label-color)",
3986
+ fontFamily: theme.fontFamily.familyBase,
3987
+ fontSize: bodyMedium.fontSize,
3988
+ lineHeight: floating ? labelSmall.lineHeight : bodyMedium.lineHeight,
3989
+ fontWeight: floating ? labelSmall.fontWeight : bodyMedium.fontWeight,
3990
+ letterSpacing: floating ? labelSmall.letterSpacing : bodyMedium.letterSpacing,
3991
+ pointerEvents: "none",
3992
+ transition: `transform ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${labelTiming}, color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${labelTiming}`,
3993
+ maxWidth: "100%",
3994
+ overflow: "hidden",
3995
+ textOverflow: "ellipsis",
3996
+ whiteSpace: "nowrap"
3997
+ },
3998
+ children: label
3999
+ }
4000
+ ),
4001
+ /* @__PURE__ */ jsx(JumpyFieldContext.Provider, { value: { fieldId, hasLabel: Boolean(label), floating }, children: enhancedChild }),
4002
+ content ? /* @__PURE__ */ jsx(
4003
+ "div",
4004
+ {
4005
+ "data-jumpy-slot": "content",
4006
+ style: {
4007
+ width: "100%",
4008
+ minWidth: 0,
4009
+ paddingTop: label ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero),
4010
+ color: "var(--jumpy-cmp-base-input-value-color)",
4011
+ fontFamily: theme.fontFamily.familyBase,
4012
+ fontSize: bodyMedium.fontSize,
4013
+ lineHeight: `${bodyMedium.fontSize * bodyMedium.lineHeight}px`,
4014
+ overflow: "hidden",
4015
+ textOverflow: "ellipsis",
4016
+ whiteSpace: "nowrap"
4017
+ },
4018
+ children: content
4019
+ }
4020
+ ) : null
4021
+ ]
4022
+ }
4023
+ ),
4024
+ showTrailing ? /* @__PURE__ */ jsxs(
4025
+ "span",
4026
+ {
4027
+ "data-jumpy-slot": "trailing-container",
4028
+ className: "inline-flex shrink-0 items-center justify-center",
4029
+ style: {
4030
+ display: "inline-flex",
4031
+ alignItems: isTextarea ? "flex-start" : "center",
4032
+ justifyContent: "center",
4033
+ alignSelf: isTextarea ? "flex-start" : void 0,
4034
+ minHeight: isTextarea ? sizeValue(theme, JumpySize.large) : void 0,
4035
+ flexShrink: 0,
4036
+ gap: spacingValue(theme, JumpySpacing.smallXS),
4037
+ color: "var(--jumpy-cmp-base-input-trailing-color)",
4038
+ transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
4039
+ },
4040
+ children: [
4041
+ showClearButton ? /* @__PURE__ */ jsx(
4042
+ JumpIconButton,
4043
+ {
4044
+ variant: "secondary",
4045
+ "aria-label": "Clear",
4046
+ "data-jumpy-slot": "clear-button",
4047
+ onClick: (event) => {
4048
+ event.preventDefault();
4049
+ event.stopPropagation();
4050
+ const input = document.getElementById(child.props.id || fieldId);
4051
+ if (input && child.props.value === void 0) input.value = "";
4052
+ setHasValue(false);
4053
+ setInputValue("");
4054
+ originalOnChange?.({ target: { value: "" }, currentTarget: { value: "" } });
4055
+ onClear?.();
4056
+ input?.focus();
4057
+ },
4058
+ style: {
4059
+ width: sizeValue(theme, JumpySize.large),
4060
+ height: sizeValue(theme, JumpySize.large),
4061
+ minWidth: sizeValue(theme, JumpySize.large),
4062
+ minHeight: sizeValue(theme, JumpySize.large),
4063
+ padding: spacingValue(theme, JumpySpacing.small2XS),
4064
+ borderRadius: radiusValue(theme, JumpyRadius.small)
4065
+ },
4066
+ children: /* @__PURE__ */ jsx(CircleClearIcon, {})
4067
+ }
4068
+ ) : null,
4069
+ showErrorIcon || trailing ? /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "trailing", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: spacingValue(theme, JumpySpacing.smallXS), paddingRight: spacingValue(theme, JumpySpacing.medium), color: "var(--jumpy-cmp-base-input-trailing-color)", fontSize: bodyMedium.fontSize, lineHeight: bodyMedium.lineHeight }, children: [
4070
+ showErrorIcon ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "trailing-error", style: { display: "inline-flex", alignItems: "center", color: "var(--jumpy-cmp-base-input-trailing-error-fill, var(--jumpy-cmp-base-input-icon-fill))", lineHeight: 1 }, children: /* @__PURE__ */ jsx(CircleErrorIcon, { style: { width: sizeValue(theme, JumpySize.small2XS), height: sizeValue(theme, JumpySize.small2XS) } }) }) : null,
4071
+ trailing ? /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "trailing-text", style: { display: "inline-flex", alignItems: "center", maxWidth: `calc(30 * ${spacingValue(theme, JumpySpacing.smallXS)}px)`, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", lineHeight: 1 }, children: trailing }) : null
4072
+ ] }) : null
4073
+ ]
4074
+ }
4075
+ ) : null
4076
+ ]
4077
+ }
4078
+ )
4079
+ }
4080
+ ),
4081
+ (hasHelperText || hasCounter) && /* @__PURE__ */ jsxs(
4082
+ "div",
4083
+ {
4084
+ className: "flex justify-end",
4085
+ style: {
4086
+ gap: spacingValue(theme, JumpySpacing.smallXS),
4087
+ paddingTop: spacingValue(theme, JumpySpacing.small3XS),
4088
+ paddingLeft: spacingValue(theme, JumpySpacing.large),
4089
+ paddingRight: spacingValue(theme, JumpySpacing.large),
4090
+ color: "var(--jumpy-cmp-base-input-helper-color)",
4091
+ fontSize: bodySmall.fontSize,
4092
+ lineHeight: `${bodySmall.fontSize * bodySmall.lineHeight}px`,
4093
+ transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
4094
+ },
4095
+ children: [
4096
+ hasHelperText ? /* @__PURE__ */ jsx("span", { id: helperId, style: { flex: 1 }, children: helperText }) : null,
4097
+ hasCounter ? /* @__PURE__ */ jsxs("span", { id: counterId, style: { flex: 0, whiteSpace: "nowrap" }, children: [
4098
+ inputValue.length,
4099
+ "/",
4100
+ maxLength
4101
+ ] }) : null
4102
+ ]
4103
+ }
4104
+ )
4105
+ ] });
4106
+ }
4107
+ function JumpField(props) {
4108
+ return /* @__PURE__ */ jsx(JumpBaseInput, { ...props });
4109
+ }
4110
+ function JumpChoice({ selected = false, title, description, className = "", ...props }) {
4111
+ const theme = useJumpyTheme();
4112
+ return /* @__PURE__ */ jsxs(
4113
+ "button",
4114
+ {
4115
+ type: "button",
4116
+ ...props,
4117
+ "data-jumpy-component": "choice",
4118
+ className: cn("w-full p-4 text-left transition active:scale-[0.99] disabled:cursor-not-allowed", className),
4119
+ style: {
4120
+ borderRadius: theme.sizing.choiceRadius,
4121
+ border: `1px solid ${selected ? theme.color.surfacePrimary : theme.color.borderSubtle}`,
4122
+ backgroundColor: selected ? theme.color.infoSurface : theme.color.surfaceCard,
4123
+ color: theme.color.textPrimary,
4124
+ opacity: props.disabled ? theme.opacity.disabled : 1
4125
+ },
4126
+ children: [
4127
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-bold", children: title }),
4128
+ description && /* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs leading-5", style: { color: theme.color.textSecondary }, children: description })
4129
+ ]
4130
+ }
4131
+ );
4132
+ }
4133
+ function JumpNotice({ tone = "info", title, children, className = "" }) {
4134
+ const theme = useJumpyTheme();
4135
+ const toneStyles = getNoticeToneStyles(theme, tone);
4136
+ return /* @__PURE__ */ jsxs("div", { className: cn("p-4", className), style: { borderRadius: theme.sizing.choiceRadius, border: `1px solid ${toneStyles.borderColor}`, backgroundColor: toneStyles.backgroundColor, color: toneStyles.color }, children: [
4137
+ title && /* @__PURE__ */ jsx("p", { className: "text-sm font-bold", children: title }),
4138
+ /* @__PURE__ */ jsx("div", { className: cn("text-xs leading-5", title && "mt-1"), children })
4139
+ ] });
4140
+ }
4141
+ function JumpTabs({ items, value, onChange, className = "" }) {
4142
+ const scrollRef = useRef(null);
4143
+ const indicatorRef = useRef(null);
4144
+ const tabRefs = useRef(/* @__PURE__ */ new Map());
4145
+ const placeIndicatorRef = useRef(null);
4146
+ placeIndicatorRef.current = (animate) => {
4147
+ const selectedTab = tabRefs.current.get(value);
4148
+ const scrollContainer = scrollRef.current;
4149
+ const indicator = indicatorRef.current;
4150
+ if (!selectedTab || !scrollContainer || !indicator) return;
4151
+ const tabBounds = selectedTab.getBoundingClientRect();
4152
+ const containerBounds = scrollContainer.getBoundingClientRect();
4153
+ const tabStyles = getComputedStyle(selectedTab);
4154
+ const content = selectedTab.firstElementChild;
4155
+ const contentStyles = content ? getComputedStyle(content) : null;
4156
+ const insetLeft = Number.parseFloat(tabStyles.paddingLeft) + (contentStyles ? Number.parseFloat(contentStyles.paddingLeft) : 0);
4157
+ const insetRight = Number.parseFloat(tabStyles.paddingRight) + (contentStyles ? Number.parseFloat(contentStyles.paddingRight) : 0);
4158
+ const x = tabBounds.left - containerBounds.left + scrollContainer.scrollLeft + insetLeft;
4159
+ const width = tabBounds.width - insetLeft - insetRight;
4160
+ if (!animate) indicator.style.transition = "none";
4161
+ indicator.style.setProperty("--jumpy-cmp-tabs-indicator-x", `${x}px`);
4162
+ indicator.style.setProperty("--jumpy-cmp-tabs-indicator-width", `${width}px`);
4163
+ indicator.style.setProperty("--jumpy-cmp-tabs-indicator-opacity", "1");
4164
+ if (!animate) {
4165
+ indicator.getBoundingClientRect();
4166
+ indicator.style.transition = "";
4167
+ }
4168
+ };
4169
+ const indicatorPlacedRef = useRef(false);
4170
+ useLayoutEffect(() => {
4171
+ placeIndicatorRef.current(indicatorPlacedRef.current);
4172
+ indicatorPlacedRef.current = true;
4173
+ }, [items, value]);
4174
+ useEffect(() => {
4175
+ const scrollContainer = scrollRef.current;
4176
+ if (!scrollContainer || typeof ResizeObserver === "undefined") return void 0;
4177
+ const sizes = /* @__PURE__ */ new WeakMap();
4178
+ const observer = new ResizeObserver((entries) => {
4179
+ let resized = false;
4180
+ entries.forEach(({ target, contentRect }) => {
4181
+ const size2 = `${contentRect.width}x${contentRect.height}`;
4182
+ if (sizes.has(target) && sizes.get(target) !== size2) resized = true;
4183
+ sizes.set(target, size2);
4184
+ });
4185
+ if (resized) placeIndicatorRef.current(false);
4186
+ });
4187
+ observer.observe(scrollContainer);
4188
+ tabRefs.current.forEach((tab) => observer.observe(tab));
4189
+ return () => observer.disconnect();
4190
+ }, [items]);
4191
+ return /* @__PURE__ */ jsx("div", { className, "data-jumpy-component": "tabs", children: /* @__PURE__ */ jsxs("div", { ref: scrollRef, "data-jumpy-slot": "tabs-scroll-container", role: "tablist", "aria-orientation": "horizontal", tabIndex: -1, children: [
4192
+ items.map((item) => {
4193
+ const active = item.key === value;
4194
+ return /* @__PURE__ */ jsx(
4195
+ "button",
4196
+ {
4197
+ ref: (node) => {
4198
+ if (node) tabRefs.current.set(item.key, node);
4199
+ else tabRefs.current.delete(item.key);
4200
+ },
4201
+ type: "button",
4202
+ role: "tab",
4203
+ "aria-selected": active,
4204
+ onClick: (event) => {
4205
+ onChange(item.key);
4206
+ event.currentTarget.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
4207
+ },
4208
+ "data-jumpy-component": "tab",
4209
+ children: /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "tab-content", children: /* @__PURE__ */ jsx("span", { "data-jumpy-slot": "tab-label", "data-text": item.label, children: item.label }) })
4210
+ },
4211
+ item.key
4212
+ );
4213
+ }),
4214
+ /* @__PURE__ */ jsx(
4215
+ "div",
4216
+ {
4217
+ ref: indicatorRef,
4218
+ "aria-hidden": "true",
4219
+ "data-jumpy-slot": "tabs-indicator"
4220
+ }
4221
+ )
4222
+ ] }) });
4223
+ }
4224
+ function JumpModal({ children }) {
4225
+ return /* @__PURE__ */ jsx(JumpDialog, { visible: true, children });
4226
+ }
4227
+
4228
+ export {
4229
+ JumpyFieldContext,
4230
+ KeyboardKeys,
4231
+ JumpBackground,
4232
+ JumpBadge,
4233
+ BrandMark,
4234
+ JumpCard,
4235
+ JumpGrid,
4236
+ JumpGridItem,
4237
+ JumpList,
4238
+ JumpListItem,
4239
+ JumpSideMenu,
4240
+ JumpSideMenuItem,
4241
+ JumpTypography,
4242
+ JumpHyperlink,
4243
+ JumpDivider,
4244
+ JumpAvatar,
4245
+ JumpBanner,
4246
+ JumpSkeletonLoader,
4247
+ JumpSearchField,
4248
+ JumpTooltip,
4249
+ JumpPopover,
4250
+ JumpMenu,
4251
+ JumpMenuItem,
4252
+ JumpToggleButton,
4253
+ JumpToggleButtonGroup,
4254
+ JumpPinInput,
4255
+ JumpPageControl,
4256
+ useJumpSnackbar,
4257
+ JumpSnackbarProvider,
4258
+ JumpTable,
4259
+ JumpTableHead,
4260
+ JumpTableBody,
4261
+ JumpTableFooter,
4262
+ JumpTableRow,
4263
+ JumpTableCell,
4264
+ getSortMapping,
4265
+ getNextSortOrder,
4266
+ getSortOrder,
4267
+ getSortActionLabel,
4268
+ isRowFullySelected,
4269
+ applySubtreeSelection,
4270
+ getRowCheckboxProps,
4271
+ getHeaderCheckboxProps,
4272
+ JumpTableHeaderCell,
4273
+ JumpExpandable,
4274
+ JumpExpandableGroup,
4275
+ JumpSheet,
4276
+ JumpTopBar,
4277
+ JumpTabBar,
4278
+ JumpTabBarItem,
4279
+ JumpAppShell,
4280
+ JumpSectionHeader,
4281
+ JumpSectionFooter,
4282
+ JumpTag,
4283
+ JumpChip,
4284
+ JumpCheckbox,
4285
+ JumpSwitch,
4286
+ JumpRadioGroup,
4287
+ JumpDatePicker,
4288
+ JumpTimePicker,
4289
+ JumpDropdown,
4290
+ JumpSpinner,
4291
+ JumpDialog,
4292
+ JumpLoader,
4293
+ JumpButton,
4294
+ JumpIconButton,
4295
+ JumpLinkButton,
4296
+ JumpInput,
4297
+ JumpTextarea,
4298
+ JumpBaseInput,
4299
+ JumpField,
4300
+ JumpChoice,
4301
+ JumpNotice,
4302
+ JumpTabs,
4303
+ JumpModal
4304
+ };