ajo-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +126 -0
- package/dist/accordion.js +130 -0
- package/dist/avatar.js +54 -0
- package/dist/calendar.js +2 -0
- package/dist/carousel.js +239 -0
- package/dist/chart.js +790 -0
- package/dist/checkbox-group.js +70 -0
- package/dist/checkbox.js +77 -0
- package/dist/chunks/bar-CVafh6C1.js +99 -0
- package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
- package/dist/chunks/collection-DtRB63U4.js +111 -0
- package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
- package/dist/chunks/menu-B45IyHHC.js +704 -0
- package/dist/chunks/native-BJdhd9XJ.js +20 -0
- package/dist/chunks/popup-C8Bb3l_g.js +459 -0
- package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
- package/dist/chunks/position-D6_i_SRn.js +434 -0
- package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
- package/dist/collapsible.js +106 -0
- package/dist/command.js +263 -0
- package/dist/context-menu.js +112 -0
- package/dist/data-table.js +5 -0
- package/dist/dialog.js +207 -0
- package/dist/direction.js +22 -0
- package/dist/drawer.js +139 -0
- package/dist/field.js +26 -0
- package/dist/index.js +38 -0
- package/dist/input-date.js +994 -0
- package/dist/input-group.js +52 -0
- package/dist/input-otp.js +179 -0
- package/dist/menu.js +2 -0
- package/dist/menubar.js +236 -0
- package/dist/message-scroller.js +446 -0
- package/dist/navigation-menu.js +330 -0
- package/dist/popover.js +307 -0
- package/dist/progress.js +39 -0
- package/dist/radio-group.js +107 -0
- package/dist/resizable.js +172 -0
- package/dist/select.js +961 -0
- package/dist/sidebar.js +343 -0
- package/dist/slider.js +259 -0
- package/dist/switch.js +53 -0
- package/dist/tabs.js +182 -0
- package/dist/toast.js +492 -0
- package/dist/toggle-group.js +111 -0
- package/dist/toggle.js +52 -0
- package/dist/toolbar.js +127 -0
- package/dist/tooltip.js +196 -0
- package/dist/utils.js +104 -0
- package/dist/virtual-list.js +2 -0
- package/package.json +250 -0
- package/src/accordion.tsx +261 -0
- package/src/availability.ts +261 -0
- package/src/avatar.tsx +99 -0
- package/src/bar.ts +156 -0
- package/src/calendar.tsx +1441 -0
- package/src/carousel.tsx +424 -0
- package/src/chart.tsx +1194 -0
- package/src/checkbox-group.tsx +132 -0
- package/src/checkbox.tsx +130 -0
- package/src/collapsible.tsx +188 -0
- package/src/collection.ts +154 -0
- package/src/command.tsx +511 -0
- package/src/context-menu.tsx +233 -0
- package/src/data-table-contract.ts +143 -0
- package/src/data-table-model.ts +760 -0
- package/src/data-table.tsx +475 -0
- package/src/dialog.tsx +393 -0
- package/src/direction.tsx +45 -0
- package/src/drawer.tsx +251 -0
- package/src/field.tsx +61 -0
- package/src/index.ts +37 -0
- package/src/input-date.tsx +1539 -0
- package/src/input-group.tsx +142 -0
- package/src/input-otp.tsx +324 -0
- package/src/menu-cluster.ts +124 -0
- package/src/menu.tsx +1095 -0
- package/src/menubar.tsx +459 -0
- package/src/message-scroller.tsx +732 -0
- package/src/native.ts +26 -0
- package/src/navigation-menu.tsx +578 -0
- package/src/popover.tsx +519 -0
- package/src/popup-surface.tsx +31 -0
- package/src/popup.ts +569 -0
- package/src/position.ts +523 -0
- package/src/progress.tsx +70 -0
- package/src/radio-group.tsx +186 -0
- package/src/resizable.tsx +310 -0
- package/src/segments.ts +922 -0
- package/src/select.tsx +1501 -0
- package/src/sidebar.tsx +683 -0
- package/src/slider.tsx +424 -0
- package/src/switch.tsx +104 -0
- package/src/tabs.tsx +314 -0
- package/src/toast.tsx +923 -0
- package/src/toggle-group.tsx +249 -0
- package/src/toggle.tsx +91 -0
- package/src/toolbar.tsx +212 -0
- package/src/tooltip.tsx +359 -0
- package/src/utils.ts +204 -0
- package/src/virtual-list.tsx +205 -0
- package/src/virtual.ts +385 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { flag, strings } from "./utils.js";
|
|
2
|
+
import { Toggle } from "./toggle.js";
|
|
3
|
+
import { listen, roving, selection, statefulRootAttrs } from "ajo-cloves";
|
|
4
|
+
import { context } from "ajo/context";
|
|
5
|
+
import { Fragment, jsx } from "ajo/jsx-runtime";
|
|
6
|
+
//#region packages/ajo-ui/src/toggle-group.tsx
|
|
7
|
+
/** Composition context exposing ToggleGroup state and styling markers to descendant items. */
|
|
8
|
+
var ToggleGroupContext = context(null);
|
|
9
|
+
var selected = (type, value) => type === "multiple" ? strings(value) : value == null || value === "" ? [] : [String(value)];
|
|
10
|
+
var isButton = (value) => value instanceof HTMLButtonElement && value.dataset.slot === "toggle-group-item";
|
|
11
|
+
var focusableItems = (root) => Array.from(root.querySelectorAll("button[data-slot=\"toggle-group-item\"]")).filter((button) => !button.disabled);
|
|
12
|
+
var ToggleGroupRoot = function* ({ defaultValue, type: initialType }) {
|
|
13
|
+
let disabled = false;
|
|
14
|
+
let loop = true;
|
|
15
|
+
let onValueChange;
|
|
16
|
+
let orientation = "horizontal";
|
|
17
|
+
let type = initialType;
|
|
18
|
+
const sel = selection(this, {
|
|
19
|
+
multiple: () => type === "multiple",
|
|
20
|
+
fallback: selected(initialType, defaultValue),
|
|
21
|
+
onChange: (next, event) => {
|
|
22
|
+
if (type === "multiple") onValueChange?.(next, event);
|
|
23
|
+
else onValueChange?.(next[0] ?? "", event);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const pressed = (value) => sel.has(value);
|
|
27
|
+
const change = (value, _nextPressed, event) => {
|
|
28
|
+
if (disabled) return;
|
|
29
|
+
sel.toggle(value, event);
|
|
30
|
+
};
|
|
31
|
+
const nav = roving(this, {
|
|
32
|
+
items: () => focusableItems(this),
|
|
33
|
+
orientation: () => orientation,
|
|
34
|
+
loop: () => loop,
|
|
35
|
+
onMove: (target) => target.focus()
|
|
36
|
+
});
|
|
37
|
+
listen(this, "keydown", (event) => {
|
|
38
|
+
if (!isButton(event.target)) return;
|
|
39
|
+
if (this.closest("[role=\"toolbar\"], [popover], dialog")?.matches("[role=\"toolbar\"]")) return;
|
|
40
|
+
nav.handle(event);
|
|
41
|
+
});
|
|
42
|
+
for (const args of this) {
|
|
43
|
+
type = args.type;
|
|
44
|
+
orientation = args.orientation;
|
|
45
|
+
disabled = Boolean(args.disabled);
|
|
46
|
+
loop = args.loop !== false;
|
|
47
|
+
onValueChange = args.onValueChange;
|
|
48
|
+
sel.sync(args.value != null ? selected(type, args.value) : void 0);
|
|
49
|
+
ToggleGroupContext({
|
|
50
|
+
disabled,
|
|
51
|
+
orientation,
|
|
52
|
+
pressed,
|
|
53
|
+
size: args.size,
|
|
54
|
+
spacing: args.spacing,
|
|
55
|
+
toggle: change,
|
|
56
|
+
type,
|
|
57
|
+
variant: args.variant
|
|
58
|
+
});
|
|
59
|
+
yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Unstyled toggle group with selection state and roving keyboard focus. */
|
|
63
|
+
var ToggleGroup = ({ children, defaultValue, disabled, loop = true, onValueChange, orientation = "horizontal", role = "group", size, spacing = 2, type = "single", value, variant, ...attrs }) => {
|
|
64
|
+
const disabledFlag = Boolean(disabled);
|
|
65
|
+
return /* @__PURE__ */ jsx(ToggleGroupRoot, {
|
|
66
|
+
...statefulRootAttrs(attrs),
|
|
67
|
+
defaultValue,
|
|
68
|
+
disabled: disabledFlag,
|
|
69
|
+
loop,
|
|
70
|
+
onValueChange,
|
|
71
|
+
orientation,
|
|
72
|
+
size,
|
|
73
|
+
spacing,
|
|
74
|
+
type,
|
|
75
|
+
value,
|
|
76
|
+
variant,
|
|
77
|
+
"attr:aria-disabled": flag(disabledFlag),
|
|
78
|
+
"attr:aria-orientation": orientation,
|
|
79
|
+
"attr:data-disabled": flag(disabledFlag),
|
|
80
|
+
"attr:data-orientation": orientation,
|
|
81
|
+
"attr:data-size": size,
|
|
82
|
+
"attr:data-slot": "toggle-group",
|
|
83
|
+
"attr:data-spacing": spacing,
|
|
84
|
+
"attr:data-variant": variant,
|
|
85
|
+
"attr:role": role,
|
|
86
|
+
children
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/** Unstyled toggle group item wired to the nearest group. */
|
|
90
|
+
var ToggleGroupItem = ({ disabled, size, value, variant, ...attrs }) => {
|
|
91
|
+
const group = ToggleGroupContext();
|
|
92
|
+
const itemValue = String(value);
|
|
93
|
+
const itemSize = size ?? group?.size;
|
|
94
|
+
const itemVariant = variant ?? group?.variant;
|
|
95
|
+
const disabledFlag = Boolean(disabled ?? group?.disabled);
|
|
96
|
+
const pressed = group?.pressed(itemValue) ?? false;
|
|
97
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
98
|
+
...attrs,
|
|
99
|
+
"data-orientation": group?.orientation ?? "horizontal",
|
|
100
|
+
"data-size": itemSize,
|
|
101
|
+
"data-slot": "toggle-group-item",
|
|
102
|
+
"data-spacing": group?.spacing ?? 2,
|
|
103
|
+
"data-variant": itemVariant,
|
|
104
|
+
disabled: disabledFlag,
|
|
105
|
+
pressed,
|
|
106
|
+
value: itemValue,
|
|
107
|
+
onPressedChange: (next, event) => group?.toggle(itemValue, next, event)
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { ToggleGroup, ToggleGroupContext, ToggleGroupItem };
|
package/dist/toggle.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { callHandler, controlled, dom, statefulRootAttrs } from "ajo-cloves";
|
|
2
|
+
import { Fragment, jsx } from "ajo/jsx-runtime";
|
|
3
|
+
//#region packages/ajo-ui/src/toggle.tsx
|
|
4
|
+
var pressedAttribute = (pressed) => pressed ? "true" : "false";
|
|
5
|
+
var stateAttribute = (pressed) => pressed ? "on" : "off";
|
|
6
|
+
var ToggleRoot = function* ({ defaultPressed, pressed }) {
|
|
7
|
+
let disabled = false;
|
|
8
|
+
let onClick;
|
|
9
|
+
let onPressedChange;
|
|
10
|
+
const state = controlled(this, {
|
|
11
|
+
fallback: Boolean(pressed ?? defaultPressed),
|
|
12
|
+
onChange: (next, event) => onPressedChange?.(next, event)
|
|
13
|
+
});
|
|
14
|
+
for (const args of this) {
|
|
15
|
+
disabled = Boolean(args.disabled);
|
|
16
|
+
onClick = args.onClick;
|
|
17
|
+
onPressedChange = args.onPressedChange;
|
|
18
|
+
state.sync(args.pressed != null ? Boolean(args.pressed) : void 0);
|
|
19
|
+
if (dom(this)) {
|
|
20
|
+
this.dataset.state = stateAttribute(state.value);
|
|
21
|
+
this.setAttribute("aria-pressed", pressedAttribute(state.value));
|
|
22
|
+
this.onclick = (event) => {
|
|
23
|
+
callHandler(onClick, event);
|
|
24
|
+
if (event.defaultPrevented || disabled) return;
|
|
25
|
+
state.set(!state.value, event);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
ToggleRoot.is = "button";
|
|
32
|
+
/** Unstyled two-state button using aria-pressed. */
|
|
33
|
+
var Toggle = ({ children, defaultPressed, disabled, onPressedChange, pressed, type = "button", "set:onclick": onClick, ...attrs }) => {
|
|
34
|
+
const state = stateAttribute(Boolean(pressed ?? defaultPressed));
|
|
35
|
+
const disabledFlag = Boolean(disabled);
|
|
36
|
+
return /* @__PURE__ */ jsx(ToggleRoot, {
|
|
37
|
+
...statefulRootAttrs(attrs),
|
|
38
|
+
defaultPressed,
|
|
39
|
+
disabled: disabledFlag,
|
|
40
|
+
onClick,
|
|
41
|
+
onPressedChange,
|
|
42
|
+
pressed,
|
|
43
|
+
"attr:aria-pressed": pressedAttribute(state === "on"),
|
|
44
|
+
"attr:data-state": state,
|
|
45
|
+
"attr:data-slot": attrs["data-slot"] ?? "toggle",
|
|
46
|
+
"attr:disabled": disabledFlag || void 0,
|
|
47
|
+
"attr:type": type,
|
|
48
|
+
children
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Toggle };
|
package/dist/toolbar.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { DirectionContext } from "./direction.js";
|
|
2
|
+
import { dom, listen, roving, statefulRootAttrs } from "ajo-cloves";
|
|
3
|
+
import { context } from "ajo/context";
|
|
4
|
+
import { Fragment, jsx } from "ajo/jsx-runtime";
|
|
5
|
+
//#region packages/ajo-ui/src/toolbar.tsx
|
|
6
|
+
var ToolbarContext = context(null);
|
|
7
|
+
var CONTROL_SELECTOR = "button, [href], input, select, textarea, summary, [tabindex]";
|
|
8
|
+
var controls = (root) => {
|
|
9
|
+
const layer = root.closest("[popover], dialog");
|
|
10
|
+
return Array.from(root.querySelectorAll(CONTROL_SELECTOR)).filter((control) => !control.disabled && (control.checkVisibility?.() ?? control.offsetParent !== null) && control.closest("[popover], dialog") === layer);
|
|
11
|
+
};
|
|
12
|
+
var NON_TEXT_INPUTS = new Set([
|
|
13
|
+
"button",
|
|
14
|
+
"checkbox",
|
|
15
|
+
"color",
|
|
16
|
+
"file",
|
|
17
|
+
"hidden",
|
|
18
|
+
"image",
|
|
19
|
+
"radio",
|
|
20
|
+
"range",
|
|
21
|
+
"reset",
|
|
22
|
+
"submit"
|
|
23
|
+
]);
|
|
24
|
+
var textEntry = (element) => element instanceof HTMLTextAreaElement || element.isContentEditable || element instanceof HTMLInputElement && !NON_TEXT_INPUTS.has(element.type);
|
|
25
|
+
var caretEscapes = (target, event) => {
|
|
26
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return false;
|
|
27
|
+
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return false;
|
|
28
|
+
const caret = target.selectionStart;
|
|
29
|
+
if (caret == null || caret !== target.selectionEnd) return false;
|
|
30
|
+
const rtl = getComputedStyle(target).direction === "rtl";
|
|
31
|
+
return event.key === "ArrowLeft" !== rtl ? caret === 0 : caret === target.value.length;
|
|
32
|
+
};
|
|
33
|
+
var ToolbarRoot = function* () {
|
|
34
|
+
let active = null;
|
|
35
|
+
let dir = "ltr";
|
|
36
|
+
let loop = true;
|
|
37
|
+
let orientation = "horizontal";
|
|
38
|
+
let queued = false;
|
|
39
|
+
const apply = () => {
|
|
40
|
+
if (!dom(this)) return;
|
|
41
|
+
const row = controls(this);
|
|
42
|
+
if (!active || !row.includes(active)) active = row[0] ?? null;
|
|
43
|
+
for (const control of row) {
|
|
44
|
+
const value = control === active ? "0" : "-1";
|
|
45
|
+
if (control.getAttribute("tabindex") !== value) control.setAttribute("tabindex", value);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const sync = () => {
|
|
49
|
+
if (queued) return;
|
|
50
|
+
queued = true;
|
|
51
|
+
queueMicrotask(() => {
|
|
52
|
+
queued = false;
|
|
53
|
+
apply();
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const nav = roving(this, {
|
|
57
|
+
items: () => controls(this),
|
|
58
|
+
orientation: () => orientation,
|
|
59
|
+
dir: () => dir,
|
|
60
|
+
loop: () => loop,
|
|
61
|
+
onMove: (target) => target.focus()
|
|
62
|
+
});
|
|
63
|
+
listen(this, "focusin", (event) => {
|
|
64
|
+
const target = event.target;
|
|
65
|
+
if (!target || !controls(this).includes(target)) return;
|
|
66
|
+
active = target;
|
|
67
|
+
apply();
|
|
68
|
+
});
|
|
69
|
+
listen(this, "keydown", (event) => {
|
|
70
|
+
const target = event.target;
|
|
71
|
+
if (!target || event.defaultPrevented || event.isComposing || event.keyCode === 229) return;
|
|
72
|
+
if (!controls(this).includes(target)) return;
|
|
73
|
+
if (textEntry(target) && !caretEscapes(target, event)) return;
|
|
74
|
+
nav.handle(event);
|
|
75
|
+
});
|
|
76
|
+
if (dom(this)) {
|
|
77
|
+
const observer = new MutationObserver(sync);
|
|
78
|
+
observer.observe(this, {
|
|
79
|
+
attributeFilter: [
|
|
80
|
+
"disabled",
|
|
81
|
+
"hidden",
|
|
82
|
+
"tabindex"
|
|
83
|
+
],
|
|
84
|
+
attributes: true,
|
|
85
|
+
childList: true,
|
|
86
|
+
subtree: true
|
|
87
|
+
});
|
|
88
|
+
this.signal.addEventListener("abort", () => observer.disconnect());
|
|
89
|
+
}
|
|
90
|
+
for (const args of this) {
|
|
91
|
+
dir = args.dir;
|
|
92
|
+
loop = args.loop;
|
|
93
|
+
orientation = args.orientation;
|
|
94
|
+
ToolbarContext({ orientation });
|
|
95
|
+
sync();
|
|
96
|
+
yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/** Unstyled toolbar: arbitrary controls behind a single tab stop with dir-aware arrow-key roving (APG toolbar pattern). Button, ToggleGroup, Select, and Input compose inside; nested ToggleGroup items join the toolbar roving. */
|
|
100
|
+
var Toolbar = ({ children, "data-slot": slot = "toolbar", dir, loop = true, orientation = "horizontal", role = "toolbar", ...attrs }) => {
|
|
101
|
+
const resolvedDir = dir ?? DirectionContext();
|
|
102
|
+
return /* @__PURE__ */ jsx(ToolbarRoot, {
|
|
103
|
+
...statefulRootAttrs(attrs),
|
|
104
|
+
dir: resolvedDir,
|
|
105
|
+
loop,
|
|
106
|
+
orientation,
|
|
107
|
+
"attr:aria-orientation": orientation,
|
|
108
|
+
"attr:data-orientation": orientation,
|
|
109
|
+
"attr:data-slot": slot,
|
|
110
|
+
"attr:dir": resolvedDir,
|
|
111
|
+
"attr:role": role,
|
|
112
|
+
children
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
/** Unstyled separator between toolbar groups, oriented across the toolbar axis. */
|
|
116
|
+
var ToolbarSeparator = ({ role = "separator", ...attrs }) => {
|
|
117
|
+
const orientation = (ToolbarContext()?.orientation ?? "horizontal") === "horizontal" ? "vertical" : "horizontal";
|
|
118
|
+
return /* @__PURE__ */ jsx("div", {
|
|
119
|
+
...attrs,
|
|
120
|
+
"aria-orientation": orientation,
|
|
121
|
+
"data-orientation": orientation,
|
|
122
|
+
"data-slot": "toolbar-separator",
|
|
123
|
+
role
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
//#endregion
|
|
127
|
+
export { Toolbar, ToolbarSeparator };
|
package/dist/tooltip.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { popupStyle, triggerAttrs } from "./utils.js";
|
|
2
|
+
import { n as popup, t as contentAttrs } from "./chunks/popup-C8Bb3l_g.js";
|
|
3
|
+
import { t as PopupSurface } from "./chunks/popup-surface-BDCgtVU0.js";
|
|
4
|
+
import { callHandler, statefulRootAttrs } from "ajo-cloves";
|
|
5
|
+
import { context } from "ajo/context";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "ajo/jsx-runtime";
|
|
7
|
+
//#region packages/ajo-ui/src/tooltip.tsx
|
|
8
|
+
var ProviderContext = context(null);
|
|
9
|
+
var TooltipContext = context(null);
|
|
10
|
+
var TooltipProviderRoot = function* () {
|
|
11
|
+
let lastClosedAt = 0;
|
|
12
|
+
let skipDelayDuration = 300;
|
|
13
|
+
for (const args of this) {
|
|
14
|
+
skipDelayDuration = Math.max(0, Number(args.skipDelayDuration ?? 300));
|
|
15
|
+
ProviderContext({
|
|
16
|
+
delayDuration: Math.max(0, Number(args.delayDuration ?? 0)),
|
|
17
|
+
disableHoverableContent: Boolean(args.disableHoverableContent),
|
|
18
|
+
markClosed: () => lastClosedAt = Date.now(),
|
|
19
|
+
shouldSkipDelay: () => skipDelayDuration > 0 && Date.now() - lastClosedAt < skipDelayDuration
|
|
20
|
+
});
|
|
21
|
+
yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Unstyled shared defaults provider for descendant tooltips. */
|
|
25
|
+
var TooltipProvider = ({ children, class: classes, "data-slot": slot = "tooltip-provider", delayDuration, disableHoverableContent, skipDelayDuration, style, ...attrs }) => /* @__PURE__ */ jsx(TooltipProviderRoot, {
|
|
26
|
+
...statefulRootAttrs(attrs),
|
|
27
|
+
delayDuration,
|
|
28
|
+
disableHoverableContent,
|
|
29
|
+
skipDelayDuration,
|
|
30
|
+
"attr:class": classes,
|
|
31
|
+
"attr:data-slot": slot,
|
|
32
|
+
"attr:style": style,
|
|
33
|
+
children
|
|
34
|
+
});
|
|
35
|
+
var TooltipRoot = function* ({ defaultOpen, open }) {
|
|
36
|
+
let delayDuration = 0;
|
|
37
|
+
let disabled = false;
|
|
38
|
+
let disableHoverableContent = false;
|
|
39
|
+
let onOpenChange;
|
|
40
|
+
let provider = null;
|
|
41
|
+
let tip;
|
|
42
|
+
tip = popup(this, {
|
|
43
|
+
prefix: "tooltip",
|
|
44
|
+
profile: "tooltip",
|
|
45
|
+
initialOpen: Boolean(open ?? defaultOpen),
|
|
46
|
+
disabled: () => disabled,
|
|
47
|
+
hover: {
|
|
48
|
+
openDelay: () => delayDuration <= 0 || provider?.shouldSkipDelay() ? 0 : delayDuration,
|
|
49
|
+
closeDelay: () => disableHoverableContent ? 0 : 80
|
|
50
|
+
},
|
|
51
|
+
onOpenChange: (next, event) => onOpenChange?.(next, event),
|
|
52
|
+
onSync: (next) => {
|
|
53
|
+
if (!next) provider?.markClosed();
|
|
54
|
+
},
|
|
55
|
+
referenceHidden: "hide",
|
|
56
|
+
dismiss: {
|
|
57
|
+
prevent: true,
|
|
58
|
+
onDismiss: (event, view) => {
|
|
59
|
+
view.cancelHover();
|
|
60
|
+
view.setOpen(false, event);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const registerContentHover = (hovering, event) => {
|
|
65
|
+
if (hovering) {
|
|
66
|
+
if (!disableHoverableContent) tip.hold("content", event);
|
|
67
|
+
} else tip.release("content", event);
|
|
68
|
+
};
|
|
69
|
+
const registerTriggerHover = (hovering, event) => {
|
|
70
|
+
if (hovering) tip.hold("trigger", event);
|
|
71
|
+
else tip.release("trigger", event);
|
|
72
|
+
};
|
|
73
|
+
const registerFocus = (next, event) => {
|
|
74
|
+
if (next) tip.hold("focus", event);
|
|
75
|
+
else tip.release("focus", event);
|
|
76
|
+
};
|
|
77
|
+
for (const args of this) {
|
|
78
|
+
provider = ProviderContext();
|
|
79
|
+
delayDuration = Math.max(0, Number(args.delayDuration ?? provider?.delayDuration ?? 0));
|
|
80
|
+
disabled = Boolean(args.disabled);
|
|
81
|
+
disableHoverableContent = Boolean(args.disableHoverableContent ?? provider?.disableHoverableContent);
|
|
82
|
+
onOpenChange = args.onOpenChange;
|
|
83
|
+
const opened = tip.sync(args.open == null ? null : Boolean(args.open), {
|
|
84
|
+
placement: args.placement,
|
|
85
|
+
gap: args.gap
|
|
86
|
+
});
|
|
87
|
+
TooltipContext({
|
|
88
|
+
...tip,
|
|
89
|
+
disabled,
|
|
90
|
+
open: opened,
|
|
91
|
+
registerContentHover,
|
|
92
|
+
registerFocus,
|
|
93
|
+
registerTriggerHover
|
|
94
|
+
});
|
|
95
|
+
yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/** Unstyled root provider for one tooltip. */
|
|
99
|
+
var Tooltip = ({ children, class: classes, "data-slot": slot = "tooltip", defaultOpen, delayDuration, disabled, disableHoverableContent, gap, onOpenChange, open, placement, ...attrs }) => /* @__PURE__ */ jsx(TooltipRoot, {
|
|
100
|
+
...statefulRootAttrs(attrs),
|
|
101
|
+
defaultOpen,
|
|
102
|
+
delayDuration,
|
|
103
|
+
disabled,
|
|
104
|
+
disableHoverableContent,
|
|
105
|
+
gap,
|
|
106
|
+
onOpenChange,
|
|
107
|
+
open,
|
|
108
|
+
placement,
|
|
109
|
+
"attr:class": classes,
|
|
110
|
+
"attr:data-slot": slot,
|
|
111
|
+
children
|
|
112
|
+
});
|
|
113
|
+
/** Unstyled element that owns the tooltip description. */
|
|
114
|
+
var TooltipTrigger = ({ as = "button", children, class: classes, "data-slot": slot = "tooltip-trigger", disabled, id, ref, type = "button", "aria-describedby": describedBy, "set:onblur": onBlur, "set:onfocus": onFocus, "set:onkeydown": onKeydown, "set:onmouseleave": onMouseLeave, "set:onmouseenter": onMouseEnter, ...attrs }) => {
|
|
115
|
+
const tooltip = TooltipContext();
|
|
116
|
+
const disabledFlag = Boolean(disabled ?? tooltip?.disabled);
|
|
117
|
+
const descriptions = [...new Set(`${describedBy ?? ""} ${tooltip?.contentId ?? ""}`.trim().split(/\s+/).filter(Boolean))].join(" ") || void 0;
|
|
118
|
+
const common = {
|
|
119
|
+
...attrs,
|
|
120
|
+
...triggerAttrs({
|
|
121
|
+
describedby: descriptions,
|
|
122
|
+
id,
|
|
123
|
+
open: Boolean(tooltip?.open),
|
|
124
|
+
ref,
|
|
125
|
+
setTrigger: tooltip?.setTrigger,
|
|
126
|
+
triggerId: tooltip?.triggerId
|
|
127
|
+
}),
|
|
128
|
+
class: classes,
|
|
129
|
+
"data-slot": slot,
|
|
130
|
+
"set:onblur": (event) => {
|
|
131
|
+
callHandler(onBlur, event);
|
|
132
|
+
tooltip?.registerFocus(false, event);
|
|
133
|
+
},
|
|
134
|
+
"set:onfocus": (event) => {
|
|
135
|
+
callHandler(onFocus, event);
|
|
136
|
+
if (event.defaultPrevented || disabledFlag) return;
|
|
137
|
+
tooltip?.registerFocus(true, event);
|
|
138
|
+
},
|
|
139
|
+
"set:onkeydown": (event) => {
|
|
140
|
+
callHandler(onKeydown, event);
|
|
141
|
+
},
|
|
142
|
+
"set:onmouseleave": (event) => {
|
|
143
|
+
callHandler(onMouseLeave, event);
|
|
144
|
+
tooltip?.registerTriggerHover(false, event);
|
|
145
|
+
},
|
|
146
|
+
"set:onmouseenter": (event) => {
|
|
147
|
+
callHandler(onMouseEnter, event);
|
|
148
|
+
if (event.defaultPrevented || disabledFlag) return;
|
|
149
|
+
tooltip?.registerTriggerHover(true, event);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
if (as === "span") return /* @__PURE__ */ jsx("span", {
|
|
153
|
+
...common,
|
|
154
|
+
"aria-disabled": disabledFlag ? "true" : void 0,
|
|
155
|
+
children
|
|
156
|
+
});
|
|
157
|
+
return /* @__PURE__ */ jsx("button", {
|
|
158
|
+
...common,
|
|
159
|
+
disabled: disabledFlag,
|
|
160
|
+
type,
|
|
161
|
+
children
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
/** Unstyled non-interactive text bubble shown for a TooltipTrigger. */
|
|
165
|
+
var TooltipContent = ({ children, class: classes, "data-slot": slot = "tooltip-content", ref, style, "set:onmouseleave": onMouseLeave, "set:onmouseenter": onMouseEnter, ...attrs }) => {
|
|
166
|
+
const tooltip = TooltipContext();
|
|
167
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
168
|
+
...attrs,
|
|
169
|
+
...contentAttrs({
|
|
170
|
+
id: tooltip?.contentId,
|
|
171
|
+
open: Boolean(tooltip?.open),
|
|
172
|
+
ref,
|
|
173
|
+
setContent: tooltip?.setContent,
|
|
174
|
+
style: tooltip?.contentStyle(style) ?? popupStyle(style),
|
|
175
|
+
tabindex: void 0
|
|
176
|
+
}),
|
|
177
|
+
class: classes,
|
|
178
|
+
"data-arrow": "true",
|
|
179
|
+
"data-slot": slot,
|
|
180
|
+
role: "tooltip",
|
|
181
|
+
"set:onmouseleave": (event) => {
|
|
182
|
+
callHandler(onMouseLeave, event);
|
|
183
|
+
tooltip?.registerContentHover(false, event);
|
|
184
|
+
},
|
|
185
|
+
"set:onmouseenter": (event) => {
|
|
186
|
+
callHandler(onMouseEnter, event);
|
|
187
|
+
tooltip?.registerContentHover(true, event);
|
|
188
|
+
},
|
|
189
|
+
children: [/* @__PURE__ */ jsx(PopupSurface, {
|
|
190
|
+
arrow: true,
|
|
191
|
+
popup: tooltip
|
|
192
|
+
}), children]
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { callRef } from "ajo-cloves";
|
|
2
|
+
import { jsx } from "ajo/jsx-runtime";
|
|
3
|
+
//#region packages/ajo-ui/src/utils.ts
|
|
4
|
+
/** Builds state, popup relations, ids and a composed trigger ref. */
|
|
5
|
+
var triggerAttrs = (options) => {
|
|
6
|
+
const { controls, describedby, expanded, haspopup, id, open, ref, setTrigger, triggerId } = options;
|
|
7
|
+
const attrs = { "data-state": open ? "open" : "closed" };
|
|
8
|
+
if ("controls" in options) attrs["aria-controls"] = controls;
|
|
9
|
+
if ("describedby" in options) attrs["aria-describedby"] = describedby;
|
|
10
|
+
if ("expanded" in options) attrs["aria-expanded"] = expanded ? "true" : "false";
|
|
11
|
+
if ("haspopup" in options) attrs["aria-haspopup"] = haspopup;
|
|
12
|
+
if ("id" in options || "triggerId" in options) attrs.id = id ?? triggerId;
|
|
13
|
+
if ("ref" in options || "setTrigger" in options) attrs.ref = (element) => {
|
|
14
|
+
setTrigger?.(element);
|
|
15
|
+
callRef(ref, element);
|
|
16
|
+
};
|
|
17
|
+
return attrs;
|
|
18
|
+
};
|
|
19
|
+
/** Inline reset for a native popover plus caller-owned declarations. */
|
|
20
|
+
var popupStyle = (...parts) => [
|
|
21
|
+
"inset:auto",
|
|
22
|
+
"margin:0",
|
|
23
|
+
...parts.filter((part) => typeof part === "string" && part.length > 0)
|
|
24
|
+
].join(";");
|
|
25
|
+
/** Wraps a re-exported family part with a fixed data-slot and optional defaults. */
|
|
26
|
+
var withSlot = (Component, slot, defaults) => (attrs) => jsx(Component, {
|
|
27
|
+
...defaults,
|
|
28
|
+
...attrs,
|
|
29
|
+
"data-slot": slot
|
|
30
|
+
});
|
|
31
|
+
/** Marks a boolean state attr: 'true' when set, absent otherwise. */
|
|
32
|
+
var flag = (value) => value ? "true" : void 0;
|
|
33
|
+
/** Parses boolean-ish attr input (true, '', 'true'). */
|
|
34
|
+
var bool = (value) => value === true || value === "" || value === "true";
|
|
35
|
+
/** Maps a checked-state token to the native aria-checked vocabulary. */
|
|
36
|
+
var ariaChecked = (state) => state === "indeterminate" ? "mixed" : state === "checked" ? "true" : "false";
|
|
37
|
+
/** Mirrors a native input's live checked state onto it and one visual companion. */
|
|
38
|
+
var syncCheckedState = (input, companion) => {
|
|
39
|
+
const state = input.type === "checkbox" && input.indeterminate ? "indeterminate" : input.checked ? "checked" : "unchecked";
|
|
40
|
+
input.dataset.state = state;
|
|
41
|
+
input.setAttribute("aria-checked", ariaChecked(state));
|
|
42
|
+
if (companion) companion.dataset.state = state;
|
|
43
|
+
return state;
|
|
44
|
+
};
|
|
45
|
+
/** Flattens JSX children into their concatenated plain-text content. */
|
|
46
|
+
var text = (value) => {
|
|
47
|
+
if (value == null || value === false) return "";
|
|
48
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") return String(value);
|
|
49
|
+
if (Array.isArray(value)) return value.map(text).join("");
|
|
50
|
+
return "";
|
|
51
|
+
};
|
|
52
|
+
/** Copies a multi-value array while coercing each present value to a string. */
|
|
53
|
+
var strings = (value) => Array.isArray(value) ? value.map(String) : [];
|
|
54
|
+
/** Token match: every whitespace-separated search token appears in the haystack, case-insensitive; an empty search matches. */
|
|
55
|
+
var matchesTokens = (search, haystack) => {
|
|
56
|
+
const query = search.trim().toLowerCase();
|
|
57
|
+
if (!query) return true;
|
|
58
|
+
const target = haystack.trim().toLowerCase();
|
|
59
|
+
return query.split(/\s+/).every((part) => target.includes(part));
|
|
60
|
+
};
|
|
61
|
+
/** Formats the default English result count for filterable collections. */
|
|
62
|
+
var defaultResultsLabel = (count) => `${count} result${count === 1 ? "" : "s"}`;
|
|
63
|
+
var passAll = () => true;
|
|
64
|
+
/** Resolves undefined to the built-in filter, null to unfiltered, and preserves a custom filter. */
|
|
65
|
+
var resolveFilter = (filter, fallback) => filter === void 0 ? fallback : filter ?? passAll;
|
|
66
|
+
/** Joins conditional class names, returning undefined when empty. */
|
|
67
|
+
var clx = (...values) => values.filter(Boolean).join(" ") || void 0;
|
|
68
|
+
/** Coerces to a finite number, falling back otherwise. */
|
|
69
|
+
var toNumber = (value, fallback) => {
|
|
70
|
+
const next = Number(value);
|
|
71
|
+
return Number.isFinite(next) ? next : fallback;
|
|
72
|
+
};
|
|
73
|
+
/** Returns true when JSX children carry no visible content. */
|
|
74
|
+
var emptyChildren = (children) => children == null || children === false || Array.isArray(children) && children.every((child) => child == null || child === false);
|
|
75
|
+
var property = (key) => {
|
|
76
|
+
if (key === "cssFloat") return "float";
|
|
77
|
+
if (key.startsWith("--")) return key;
|
|
78
|
+
const css = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
79
|
+
return css.startsWith("ms-") ? `-${css}` : css;
|
|
80
|
+
};
|
|
81
|
+
var collectStyles = (input, result) => {
|
|
82
|
+
if (!input || input === true || typeof input === "number") return;
|
|
83
|
+
if (typeof input === "string") {
|
|
84
|
+
const value = input.trim().replace(/;+$/, "");
|
|
85
|
+
if (value) result.push(value);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(input)) {
|
|
89
|
+
for (const item of input) collectStyles(item, result);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const [key, value] of Object.entries(input)) {
|
|
93
|
+
if (value == null || value === false) continue;
|
|
94
|
+
result.push(`${property(key)}:${value}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/** Build an inline style string from strings, objects, arrays, and falsey entries. */
|
|
98
|
+
var stlx = (...input) => {
|
|
99
|
+
const result = [];
|
|
100
|
+
for (const item of input) collectStyles(item, result);
|
|
101
|
+
return result.join(";");
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { ariaChecked, bool, clx, defaultResultsLabel, emptyChildren, flag, matchesTokens, popupStyle, resolveFilter, stlx, strings, syncCheckedState, text, toNumber, triggerAttrs, withSlot };
|