@joewinke/jatui 0.2.12 → 0.2.13
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/dist/actions/attention.js +21 -13
- package/dist/actions/collapse.js +15 -29
- package/dist/actions/floating.js +29 -37
- package/dist/actions/listNav.js +108 -137
- package/dist/actions/railNav.js +72 -89
- package/dist/actions/staggerEntrance.js +19 -23
- package/dist/actions/tooltip.js +63 -51
- package/dist/assets/sounds/error.js +1 -1
- package/dist/assets/sounds/notify.js +1 -1
- package/dist/assets/sounds/pop.js +1 -1
- package/dist/assets/sounds/send.js +1 -1
- package/dist/assets/sounds/success.js +1 -1
- package/dist/assets/sounds/toggle.js +1 -1
- package/dist/components/graph/theme.js +26 -36
- package/dist/components/markup.js +59 -62
- package/dist/components/session-nav/transcriptModel.js +49 -66
- package/dist/components/shell/shell.js +14 -15
- package/dist/components/table/flexRender.js +2 -2
- package/dist/data/emojis.js +4 -4
- package/dist/dnd/autoScroll.js +10 -12
- package/dist/dnd/createDndBoard.js +94 -185
- package/dist/dnd/draggable.js +15 -23
- package/dist/dnd/dropTarget.js +15 -29
- package/dist/dnd/types.d.ts +9 -0
- package/dist/dnd/types.js +2 -14
- package/dist/showcase/generative/intent.js +19 -21
- package/dist/showcase/generative/manifest.js +27 -80
- package/dist/showcase/scroll/scrollProgress.js +20 -22
- package/dist/showcase/scroll/scrollReveal.js +17 -20
- package/dist/stores/commandPalette.js +18 -46
- package/dist/stores/confirmDialog.js +9 -9
- package/dist/stores/inputDialog.js +9 -9
- package/dist/stores/toast.js +10 -33
- package/dist/transitions/glide.js +29 -25
- package/dist/types/booking.js +4 -4
- package/dist/types/comments.js +2 -2
- package/dist/types/messaging.js +3 -3
- package/dist/types/pipeline.js +2 -2
- package/dist/utils/currency.js +2 -4
- package/dist/utils/daisyuiColors.js +25 -27
- package/dist/utils/dateFormatters.js +44 -44
- package/dist/utils/googleMapsLoader.js +46 -100
- package/dist/utils/mentionParser.js +30 -30
- package/dist/utils/phone.js +21 -27
- package/dist/utils/phoneFormat.js +9 -9
- package/dist/utils/sound.js +27 -78
- package/dist/utils/splitText.js +15 -26
- package/dist/utils/taskUtils.js +1 -1
- package/dist/utils/themeWipe.js +27 -72
- package/dist/utils/viewTransition.js +6 -51
- package/package.json +7 -3
- package/dist/dnd/createDndBoard.test.d.ts +0 -1
- package/dist/dnd/createDndBoard.test.js +0 -261
package/dist/actions/railNav.js
CHANGED
|
@@ -60,17 +60,6 @@
|
|
|
60
60
|
* 3. Handle Left/Right in `onAction` and call `rail.bump(host)` on change:
|
|
61
61
|
* onAction: (id, dir, host) => { formData[id] = cycle(opts[id], …, dir); rail.bump(host); }
|
|
62
62
|
*/
|
|
63
|
-
var __assign = (this && this.__assign) || function () {
|
|
64
|
-
__assign = Object.assign || function(t) {
|
|
65
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
66
|
-
s = arguments[i];
|
|
67
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
68
|
-
t[p] = s[p];
|
|
69
|
-
}
|
|
70
|
-
return t;
|
|
71
|
-
};
|
|
72
|
-
return __assign.apply(this, arguments);
|
|
73
|
-
};
|
|
74
63
|
// ─── Exported helpers ──────────────────────────────────────────────────────────
|
|
75
64
|
/**
|
|
76
65
|
* Cycle through `list`, returning the item `dir` steps from `current`.
|
|
@@ -84,40 +73,39 @@ var __assign = (this && this.__assign) || function () {
|
|
|
84
73
|
export function cycle(list, current, dir) {
|
|
85
74
|
if (list.length === 0)
|
|
86
75
|
return current;
|
|
87
|
-
|
|
76
|
+
const i = list.indexOf(current);
|
|
88
77
|
// When `current` is not in the list, treat it as if we're at position -1
|
|
89
78
|
// so that `dir=1` lands at index 0 (the first item) and `dir=-1` lands at
|
|
90
79
|
// the last item — matching the "not found → start from the beginning" idiom.
|
|
91
|
-
|
|
80
|
+
const base = i === -1 ? -1 : i;
|
|
92
81
|
return list[(((base + dir) % list.length) + list.length) % list.length];
|
|
93
82
|
}
|
|
94
83
|
// ─── Internals ─────────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
const BUMP_DURATION_MS = 280;
|
|
85
|
+
const RAIL_FOCUS_CLASS = 'rail-focus';
|
|
86
|
+
const RAIL_CYCLED_CLASS = 'rail-cycled';
|
|
98
87
|
function resolveOrder(opts) {
|
|
99
88
|
return typeof opts.order === 'function' ? opts.order() : opts.order;
|
|
100
89
|
}
|
|
101
90
|
function resolveAttr(opts) {
|
|
102
|
-
|
|
103
|
-
return (_a = opts.attr) !== null && _a !== void 0 ? _a : 'data-rail-id';
|
|
91
|
+
return opts.attr ?? 'data-rail-id';
|
|
104
92
|
}
|
|
105
93
|
/** Return all currently-rendered rung hosts in the rail order. */
|
|
106
94
|
function resolveHosts(optsRef) {
|
|
107
|
-
|
|
95
|
+
const { container } = optsRef.current;
|
|
108
96
|
if (!container)
|
|
109
97
|
return [];
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
const attr = resolveAttr(optsRef.current);
|
|
99
|
+
const order = resolveOrder(optsRef.current);
|
|
112
100
|
return order
|
|
113
|
-
.map(
|
|
101
|
+
.map((id) => container.querySelector(`[${attr}="${id}"]`))
|
|
114
102
|
// checkVisibility() correctly returns false for elements inside a closed <details>
|
|
115
103
|
// (browsers keep offsetParent non-null for those, so that check alone is insufficient).
|
|
116
104
|
// Where checkVisibility is unavailable (jsdom, older engines) treat the rung as
|
|
117
105
|
// visible — a rendered element in `order` is navigable by default; only the
|
|
118
106
|
// closed-<details> case needs the extra exclusion, and that only exists in a
|
|
119
107
|
// real layout engine.
|
|
120
|
-
.filter(
|
|
108
|
+
.filter((el) => !!el && (el.checkVisibility?.() ?? true));
|
|
121
109
|
}
|
|
122
110
|
/**
|
|
123
111
|
* Focus the most appropriate control inside a rung host:
|
|
@@ -132,17 +120,17 @@ function resolveHosts(optsRef) {
|
|
|
132
120
|
* 4. Last resort: focus the host itself (requires tabindex on the host).
|
|
133
121
|
*/
|
|
134
122
|
function focusRailHost(host) {
|
|
135
|
-
var _a;
|
|
136
123
|
if (host.matches('input,select,textarea,button,[contenteditable="true"],[tabindex]')) {
|
|
137
124
|
host.focus();
|
|
138
125
|
return;
|
|
139
126
|
}
|
|
140
|
-
|
|
141
|
-
|
|
127
|
+
const inner = host.querySelector('[contenteditable="true"],input,select,textarea') ??
|
|
128
|
+
host.querySelector('.sd-trigger,button');
|
|
129
|
+
(inner ?? host).focus();
|
|
142
130
|
}
|
|
143
131
|
function makeController(optsRef) {
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
let _currentRailId = '';
|
|
133
|
+
let _lastFocusedHost = null;
|
|
146
134
|
function applyFocusClass(host) {
|
|
147
135
|
if (_lastFocusedHost && _lastFocusedHost !== host) {
|
|
148
136
|
_lastFocusedHost.classList.remove(RAIL_FOCUS_CLASS);
|
|
@@ -157,14 +145,14 @@ function makeController(optsRef) {
|
|
|
157
145
|
host.classList.remove(RAIL_CYCLED_CLASS);
|
|
158
146
|
void host.offsetWidth; // reflow so the animation restarts on rapid presses
|
|
159
147
|
host.classList.add(RAIL_CYCLED_CLASS);
|
|
160
|
-
setTimeout(
|
|
148
|
+
setTimeout(() => host.classList.remove(RAIL_CYCLED_CLASS), BUMP_DURATION_MS);
|
|
161
149
|
}
|
|
162
150
|
function focus(id) {
|
|
163
|
-
|
|
151
|
+
const { container } = optsRef.current;
|
|
164
152
|
if (!container)
|
|
165
153
|
return;
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
const attr = resolveAttr(optsRef.current);
|
|
155
|
+
const host = container.querySelector(`[${attr}="${id}"]`);
|
|
168
156
|
if (host) {
|
|
169
157
|
focusRailHost(host);
|
|
170
158
|
applyFocusClass(host);
|
|
@@ -175,21 +163,20 @@ function makeController(optsRef) {
|
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
165
|
function moveRung(dir) {
|
|
178
|
-
|
|
179
|
-
var hosts = resolveHosts(optsRef);
|
|
166
|
+
const hosts = resolveHosts(optsRef);
|
|
180
167
|
if (hosts.length === 0)
|
|
181
168
|
return;
|
|
182
|
-
|
|
183
|
-
|
|
169
|
+
const attr = resolveAttr(optsRef.current);
|
|
170
|
+
const ids = hosts.map((h) => h.getAttribute(attr));
|
|
184
171
|
// Anchor on the actually-focused rung first. Fall back to the last tracked
|
|
185
172
|
// rung (covers focus parked on the container after an Escape pop-out).
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
const active = document.activeElement;
|
|
174
|
+
const activeFocused = (active?.closest(`[${attr}]`))?.getAttribute(attr);
|
|
175
|
+
const fromId = activeFocused && ids.includes(activeFocused) ? activeFocused : _currentRailId;
|
|
176
|
+
let pos = fromId ? ids.indexOf(fromId) : -1;
|
|
190
177
|
if (pos === -1)
|
|
191
178
|
pos = dir === 1 ? -1 : hosts.length;
|
|
192
|
-
|
|
179
|
+
const next = (((pos + dir) % hosts.length) + hosts.length) % hosts.length;
|
|
193
180
|
focusRailHost(hosts[next]);
|
|
194
181
|
applyFocusClass(hosts[next]);
|
|
195
182
|
_currentRailId = ids[next];
|
|
@@ -197,10 +184,9 @@ function makeController(optsRef) {
|
|
|
197
184
|
function refresh() {
|
|
198
185
|
// Re-derive hosts; re-apply focus class if the current rung is still rendered
|
|
199
186
|
// (DOM may have been reconstructed by Svelte's reactive updates).
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
for (
|
|
203
|
-
var h = hosts_1[_i];
|
|
187
|
+
const hosts = resolveHosts(optsRef);
|
|
188
|
+
const attr = resolveAttr(optsRef.current);
|
|
189
|
+
for (const h of hosts) {
|
|
204
190
|
if (h.getAttribute(attr) === _currentRailId) {
|
|
205
191
|
applyFocusClass(h);
|
|
206
192
|
return;
|
|
@@ -208,11 +194,10 @@ function makeController(optsRef) {
|
|
|
208
194
|
}
|
|
209
195
|
}
|
|
210
196
|
function handleFocusin(event) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
var id = host === null || host === void 0 ? void 0 : host.getAttribute(attr);
|
|
197
|
+
const attr = resolveAttr(optsRef.current);
|
|
198
|
+
const order = resolveOrder(optsRef.current);
|
|
199
|
+
const host = event.target?.closest(`[${attr}]`);
|
|
200
|
+
const id = host?.getAttribute(attr);
|
|
216
201
|
// Only track rungs that are in the rail order. Tabbing into an off-rail
|
|
217
202
|
// control (e.g. a description textarea) leaves the last real rung intact
|
|
218
203
|
// so Up / Down can resume from it after the user tabs back out.
|
|
@@ -222,18 +207,17 @@ function makeController(optsRef) {
|
|
|
222
207
|
}
|
|
223
208
|
}
|
|
224
209
|
function handleKeydown(event) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
var isTextField = !!el &&
|
|
210
|
+
const opts = optsRef.current;
|
|
211
|
+
const el = document.activeElement;
|
|
212
|
+
const tag = el?.tagName;
|
|
213
|
+
const isTextField = !!el &&
|
|
230
214
|
(tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
231
|
-
|
|
232
|
-
|
|
215
|
+
const isMultiline = !!el && (tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
216
|
+
const hasMod = event.metaKey || event.ctrlKey || event.altKey;
|
|
233
217
|
// Modal trap — stop non-text keystrokes from bubbling to window-level page
|
|
234
218
|
// listeners (e.g., a listNav attached to `window`). Text-field keystrokes
|
|
235
219
|
// propagate normally so typing in the drawer doesn't interfere.
|
|
236
|
-
|
|
220
|
+
const trap = opts.trap !== false;
|
|
237
221
|
if (trap && !isTextField)
|
|
238
222
|
event.stopPropagation();
|
|
239
223
|
// Escape cascade:
|
|
@@ -241,33 +225,33 @@ function makeController(optsRef) {
|
|
|
241
225
|
// 2. focus inside a descendant control → pop back to container
|
|
242
226
|
// 3. focus already on the container (or just popped) → call onEscape
|
|
243
227
|
if (event.key === 'Escape' && !hasMod) {
|
|
244
|
-
|
|
245
|
-
if (
|
|
228
|
+
const popoverOpen = opts.isPopoverOpen?.() ?? false;
|
|
229
|
+
if (popoverOpen) {
|
|
246
230
|
event.preventDefault();
|
|
247
|
-
|
|
231
|
+
opts.onPopoverEscape?.();
|
|
248
232
|
return true;
|
|
249
233
|
}
|
|
250
|
-
|
|
234
|
+
const { container } = opts;
|
|
251
235
|
if (container && el && el !== container && container.contains(el)) {
|
|
252
236
|
event.preventDefault();
|
|
253
237
|
container.focus();
|
|
254
238
|
return true;
|
|
255
239
|
}
|
|
256
240
|
event.preventDefault();
|
|
257
|
-
|
|
241
|
+
opts.onEscape?.();
|
|
258
242
|
return true;
|
|
259
243
|
}
|
|
260
244
|
// Rail motion — suppressed when a popover is open or a modifier is held.
|
|
261
|
-
|
|
245
|
+
const popoverOpen = opts.isPopoverOpen?.() ?? false;
|
|
262
246
|
if (!hasMod && !popoverOpen) {
|
|
263
247
|
// Vertical: ArrowDown / j step down; ArrowUp / k step up.
|
|
264
248
|
// j / k are blocked in ANY text field; arrows are blocked only inside
|
|
265
249
|
// multiline fields so the caret can still move line-by-line.
|
|
266
|
-
|
|
267
|
-
|
|
250
|
+
const goDown = event.key === 'ArrowDown' || event.key === 'j';
|
|
251
|
+
const goUp = event.key === 'ArrowUp' || event.key === 'k';
|
|
268
252
|
if (goDown || goUp) {
|
|
269
|
-
|
|
270
|
-
|
|
253
|
+
const isVimKey = event.key === 'j' || event.key === 'k';
|
|
254
|
+
const blocked = isVimKey ? isTextField : isMultiline;
|
|
271
255
|
if (!blocked) {
|
|
272
256
|
event.preventDefault();
|
|
273
257
|
moveRung(goDown ? 1 : -1);
|
|
@@ -279,14 +263,15 @@ function makeController(optsRef) {
|
|
|
279
263
|
if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') &&
|
|
280
264
|
!isTextField) {
|
|
281
265
|
event.preventDefault();
|
|
282
|
-
|
|
266
|
+
const attr = resolveAttr(opts);
|
|
283
267
|
// Prefer the element that actually has DOM focus, then fall back to
|
|
284
268
|
// the last tracked rung (focus parked on the container after pop-out).
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
269
|
+
const focusedHost = (el?.closest(`[${attr}]`)) ??
|
|
270
|
+
(opts.container && _currentRailId
|
|
271
|
+
? opts.container.querySelector(`[${attr}="${_currentRailId}"]`)
|
|
272
|
+
: null);
|
|
288
273
|
if (focusedHost && opts.onAction) {
|
|
289
|
-
|
|
274
|
+
const rungId = focusedHost.getAttribute(attr) ?? '';
|
|
290
275
|
opts.onAction(rungId, event.key === 'ArrowRight' ? 1 : -1, focusedHost);
|
|
291
276
|
}
|
|
292
277
|
return true;
|
|
@@ -295,13 +280,13 @@ function makeController(optsRef) {
|
|
|
295
280
|
return false;
|
|
296
281
|
}
|
|
297
282
|
return {
|
|
298
|
-
handleKeydown
|
|
299
|
-
handleFocusin
|
|
300
|
-
moveRung
|
|
301
|
-
focus
|
|
302
|
-
refresh
|
|
303
|
-
currentId:
|
|
304
|
-
bump
|
|
283
|
+
handleKeydown,
|
|
284
|
+
handleFocusin,
|
|
285
|
+
moveRung,
|
|
286
|
+
focus,
|
|
287
|
+
refresh,
|
|
288
|
+
currentId: () => _currentRailId,
|
|
289
|
+
bump,
|
|
305
290
|
};
|
|
306
291
|
}
|
|
307
292
|
// ─── Composable ────────────────────────────────────────────────────────────────
|
|
@@ -316,7 +301,7 @@ function makeController(optsRef) {
|
|
|
316
301
|
* panel.addEventListener('focusin', (e) => rail.handleFocusin(e));
|
|
317
302
|
*/
|
|
318
303
|
export function createRailNav(options) {
|
|
319
|
-
|
|
304
|
+
const ref = { current: options };
|
|
320
305
|
return makeController(ref);
|
|
321
306
|
}
|
|
322
307
|
// ─── Action ────────────────────────────────────────────────────────────────────
|
|
@@ -331,10 +316,9 @@ export function createRailNav(options) {
|
|
|
331
316
|
* <div data-rail-id="priority" tabindex="-1">…</div>
|
|
332
317
|
* </div>
|
|
333
318
|
*/
|
|
334
|
-
export
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
var controller = makeController(ref);
|
|
319
|
+
export const railNav = (node, initial = { order: [] }) => {
|
|
320
|
+
const ref = { current: { ...initial, container: node } };
|
|
321
|
+
const controller = makeController(ref);
|
|
338
322
|
function onKeydown(e) {
|
|
339
323
|
controller.handleKeydown(e);
|
|
340
324
|
}
|
|
@@ -344,12 +328,11 @@ export var railNav = function (node, initial) {
|
|
|
344
328
|
node.addEventListener('keydown', onKeydown);
|
|
345
329
|
node.addEventListener('focusin', onFocusin);
|
|
346
330
|
return {
|
|
347
|
-
update:
|
|
348
|
-
|
|
349
|
-
ref.current = __assign(__assign({}, next), { container: node });
|
|
331
|
+
update(next = { order: [] }) {
|
|
332
|
+
ref.current = { ...next, container: node };
|
|
350
333
|
controller.refresh();
|
|
351
334
|
},
|
|
352
|
-
destroy
|
|
335
|
+
destroy() {
|
|
353
336
|
node.removeEventListener('keydown', onKeydown);
|
|
354
337
|
node.removeEventListener('focusin', onFocusin);
|
|
355
338
|
},
|
|
@@ -15,53 +15,49 @@
|
|
|
15
15
|
* {/each}
|
|
16
16
|
* </ul>
|
|
17
17
|
*/
|
|
18
|
-
export function staggerEntrance(node, options) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var children = Array.from(node.children);
|
|
18
|
+
export function staggerEntrance(node, options = {}) {
|
|
19
|
+
const { stepMs = 35, maxTotalMs = 250, rise = 8 } = options;
|
|
20
|
+
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
21
|
+
const children = Array.from(node.children);
|
|
23
22
|
if (prefersReduced || children.length === 0) {
|
|
24
23
|
// Render final state immediately — no animation
|
|
25
|
-
return { destroy
|
|
24
|
+
return { destroy() { } };
|
|
26
25
|
}
|
|
27
26
|
// Cap the per-item step so total time doesn't exceed maxTotalMs
|
|
28
|
-
|
|
27
|
+
const effectiveStep = children.length > 1
|
|
29
28
|
? Math.min(stepMs, Math.floor(maxTotalMs / (children.length - 1)))
|
|
30
29
|
: stepMs;
|
|
31
|
-
|
|
30
|
+
const duration = 200; // var(--motion-enter) = 200ms
|
|
32
31
|
// Set all children to their start state (opacity 0, risen)
|
|
33
|
-
for (
|
|
34
|
-
var child = children_1[_i];
|
|
32
|
+
for (const child of children) {
|
|
35
33
|
child.style.opacity = '0';
|
|
36
|
-
child.style.transform =
|
|
34
|
+
child.style.transform = `translateY(${rise}px)`;
|
|
37
35
|
child.style.transition = 'none';
|
|
38
36
|
}
|
|
39
37
|
// Trigger entrance in next frame so the start state has been painted
|
|
40
|
-
|
|
41
|
-
children.forEach(
|
|
42
|
-
|
|
43
|
-
child.style.transition =
|
|
38
|
+
const rafId = requestAnimationFrame(() => {
|
|
39
|
+
children.forEach((child, i) => {
|
|
40
|
+
const delay = i * effectiveStep;
|
|
41
|
+
child.style.transition = `opacity ${duration}ms var(--motion-glide) ${delay}ms, transform ${duration}ms var(--motion-glide) ${delay}ms`;
|
|
44
42
|
child.style.opacity = '1';
|
|
45
43
|
child.style.transform = 'translateY(0)';
|
|
46
44
|
});
|
|
47
45
|
// Clean up inline styles after all transitions complete
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
for (
|
|
51
|
-
var child = children_2[_i];
|
|
46
|
+
const totalMs = (children.length - 1) * effectiveStep + duration + 50;
|
|
47
|
+
const cleanupId = setTimeout(() => {
|
|
48
|
+
for (const child of children) {
|
|
52
49
|
child.style.opacity = '';
|
|
53
50
|
child.style.transform = '';
|
|
54
51
|
child.style.transition = '';
|
|
55
52
|
}
|
|
56
53
|
}, totalMs);
|
|
57
|
-
return
|
|
54
|
+
return () => clearTimeout(cleanupId);
|
|
58
55
|
});
|
|
59
56
|
return {
|
|
60
|
-
destroy
|
|
57
|
+
destroy() {
|
|
61
58
|
cancelAnimationFrame(rafId);
|
|
62
59
|
// Ensure children are visible if action is destroyed mid-animation
|
|
63
|
-
for (
|
|
64
|
-
var child = children_3[_i];
|
|
60
|
+
for (const child of children) {
|
|
65
61
|
child.style.opacity = '';
|
|
66
62
|
child.style.transform = '';
|
|
67
63
|
child.style.transition = '';
|
package/dist/actions/tooltip.js
CHANGED
|
@@ -16,81 +16,93 @@
|
|
|
16
16
|
* Escape key hides the active tooltip.
|
|
17
17
|
* Focus events (focusin/focusout) trigger alongside mouse hover.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
t[p] = s[p];
|
|
25
|
-
}
|
|
26
|
-
return t;
|
|
27
|
-
};
|
|
28
|
-
return __assign.apply(this, arguments);
|
|
29
|
-
};
|
|
30
|
-
var COOL_WINDOW_MS = 500;
|
|
31
|
-
var GAP = 8;
|
|
32
|
-
var tooltipEl = null;
|
|
33
|
-
var lastHideTime = 0;
|
|
34
|
-
var idCounter = 0;
|
|
19
|
+
const COOL_WINDOW_MS = 500;
|
|
20
|
+
const GAP = 8;
|
|
21
|
+
let tooltipEl = null;
|
|
22
|
+
let lastHideTime = 0;
|
|
23
|
+
let idCounter = 0;
|
|
35
24
|
function getOrCreatePortal() {
|
|
36
25
|
if (tooltipEl)
|
|
37
26
|
return tooltipEl;
|
|
38
27
|
tooltipEl = document.createElement('div');
|
|
39
28
|
tooltipEl.id = 'jatui-tooltip-portal';
|
|
40
|
-
tooltipEl.style.cssText =
|
|
29
|
+
tooltipEl.style.cssText = `
|
|
30
|
+
position: fixed;
|
|
31
|
+
top: 0; left: 0;
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
z-index: 9999;
|
|
34
|
+
`;
|
|
41
35
|
document.body.appendChild(tooltipEl);
|
|
42
36
|
return tooltipEl;
|
|
43
37
|
}
|
|
44
38
|
function createBubble(text) {
|
|
45
|
-
|
|
39
|
+
const el = document.createElement('div');
|
|
46
40
|
el.role = 'tooltip';
|
|
47
41
|
el.textContent = text;
|
|
48
|
-
el.style.cssText =
|
|
42
|
+
el.style.cssText = `
|
|
43
|
+
position: absolute;
|
|
44
|
+
padding: 4px 10px;
|
|
45
|
+
border-radius: 6px;
|
|
46
|
+
font-size: 0.8125rem;
|
|
47
|
+
line-height: 1.4;
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
max-width: 240px;
|
|
50
|
+
white-space: normal;
|
|
51
|
+
word-break: break-word;
|
|
52
|
+
background: oklch(var(--b3, 20% 0 0));
|
|
53
|
+
color: oklch(var(--bc, 95% 0 0));
|
|
54
|
+
box-shadow: 0 2px 6px oklch(0% 0 0 / 18%);
|
|
55
|
+
opacity: 0;
|
|
56
|
+
transform: translateY(4px);
|
|
57
|
+
transition:
|
|
58
|
+
opacity var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
|
|
59
|
+
transform var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
|
|
60
|
+
`;
|
|
49
61
|
// reduced-motion: remove transform, keep opacity fade
|
|
50
62
|
el.dataset.tooltipBubble = '1';
|
|
51
63
|
return el;
|
|
52
64
|
}
|
|
53
65
|
function positionBubble(bubble, anchor, placement) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
const rect = anchor.getBoundingClientRect();
|
|
67
|
+
const bw = bubble.offsetWidth;
|
|
68
|
+
const bh = bubble.offsetHeight;
|
|
69
|
+
const vw = window.innerWidth;
|
|
70
|
+
const vh = window.innerHeight;
|
|
71
|
+
let top;
|
|
72
|
+
let left;
|
|
73
|
+
let resolved = placement;
|
|
62
74
|
// Compute primary position, then flip if needed
|
|
63
|
-
|
|
64
|
-
top:
|
|
65
|
-
bottom:
|
|
66
|
-
left:
|
|
67
|
-
right:
|
|
75
|
+
const placements = {
|
|
76
|
+
top: () => ({ top: rect.top - bh - GAP, left: rect.left + rect.width / 2 - bw / 2 }),
|
|
77
|
+
bottom: () => ({ top: rect.bottom + GAP, left: rect.left + rect.width / 2 - bw / 2 }),
|
|
78
|
+
left: () => ({ top: rect.top + rect.height / 2 - bh / 2, left: rect.left - bw - GAP }),
|
|
79
|
+
right: () => ({ top: rect.top + rect.height / 2 - bh / 2, left: rect.right + GAP }),
|
|
68
80
|
};
|
|
69
|
-
|
|
81
|
+
const pos = placements[resolved]();
|
|
70
82
|
top = pos.top;
|
|
71
83
|
left = pos.left;
|
|
72
84
|
// Flip on viewport edge
|
|
73
85
|
if (resolved === 'top' && top < 0) {
|
|
74
86
|
resolved = 'bottom';
|
|
75
|
-
|
|
87
|
+
const p = placements.bottom();
|
|
76
88
|
top = p.top;
|
|
77
89
|
left = p.left;
|
|
78
90
|
}
|
|
79
91
|
else if (resolved === 'bottom' && top + bh > vh) {
|
|
80
92
|
resolved = 'top';
|
|
81
|
-
|
|
93
|
+
const p = placements.top();
|
|
82
94
|
top = p.top;
|
|
83
95
|
left = p.left;
|
|
84
96
|
}
|
|
85
97
|
else if (resolved === 'left' && left < 0) {
|
|
86
98
|
resolved = 'right';
|
|
87
|
-
|
|
99
|
+
const p = placements.right();
|
|
88
100
|
top = p.top;
|
|
89
101
|
left = p.left;
|
|
90
102
|
}
|
|
91
103
|
else if (resolved === 'right' && left + bw > vw) {
|
|
92
104
|
resolved = 'left';
|
|
93
|
-
|
|
105
|
+
const p = placements.left();
|
|
94
106
|
top = p.top;
|
|
95
107
|
left = p.left;
|
|
96
108
|
}
|
|
@@ -98,21 +110,21 @@ function positionBubble(bubble, anchor, placement) {
|
|
|
98
110
|
left = Math.max(GAP, Math.min(left, vw - bw - GAP));
|
|
99
111
|
top = Math.max(GAP, Math.min(top, vh - bh - GAP));
|
|
100
112
|
// Rise direction depends on resolved placement
|
|
101
|
-
|
|
102
|
-
bubble.style.setProperty('--tooltip-rise-y',
|
|
103
|
-
bubble.style.top =
|
|
104
|
-
bubble.style.left =
|
|
113
|
+
const riseY = resolved === 'bottom' ? -4 : 4;
|
|
114
|
+
bubble.style.setProperty('--tooltip-rise-y', `${riseY}px`);
|
|
115
|
+
bubble.style.top = `${top}px`;
|
|
116
|
+
bubble.style.left = `${left}px`;
|
|
105
117
|
}
|
|
106
118
|
function isWarm() {
|
|
107
119
|
return Date.now() - lastHideTime < COOL_WINDOW_MS;
|
|
108
120
|
}
|
|
109
121
|
export function tooltip(node, options) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
let opts = { placement: 'top', delayMs: 400, ...options };
|
|
123
|
+
let bubble = null;
|
|
124
|
+
let showTimer = null;
|
|
125
|
+
const tooltipId = `jatui-tt-${++idCounter}`;
|
|
114
126
|
function show() {
|
|
115
|
-
|
|
127
|
+
const portal = getOrCreatePortal();
|
|
116
128
|
if (bubble) {
|
|
117
129
|
// Already showing — update text and reposition
|
|
118
130
|
bubble.textContent = opts.text;
|
|
@@ -146,12 +158,12 @@ export function tooltip(node, options) {
|
|
|
146
158
|
function scheduleShow() {
|
|
147
159
|
if (showTimer || bubble)
|
|
148
160
|
return;
|
|
149
|
-
|
|
161
|
+
const delay = isWarm() ? 0 : opts.delayMs;
|
|
150
162
|
if (delay === 0) {
|
|
151
163
|
show();
|
|
152
164
|
}
|
|
153
165
|
else {
|
|
154
|
-
showTimer = setTimeout(
|
|
166
|
+
showTimer = setTimeout(() => {
|
|
155
167
|
showTimer = null;
|
|
156
168
|
show();
|
|
157
169
|
}, delay);
|
|
@@ -167,14 +179,14 @@ export function tooltip(node, options) {
|
|
|
167
179
|
node.addEventListener('focusout', hide);
|
|
168
180
|
document.addEventListener('keydown', handleKeydown);
|
|
169
181
|
return {
|
|
170
|
-
update
|
|
171
|
-
opts =
|
|
182
|
+
update(next) {
|
|
183
|
+
opts = { placement: 'top', delayMs: 400, ...next };
|
|
172
184
|
if (bubble) {
|
|
173
185
|
bubble.textContent = opts.text;
|
|
174
186
|
positionBubble(bubble, node, opts.placement);
|
|
175
187
|
}
|
|
176
188
|
},
|
|
177
|
-
destroy
|
|
189
|
+
destroy() {
|
|
178
190
|
hide();
|
|
179
191
|
node.removeEventListener('mouseenter', scheduleShow);
|
|
180
192
|
node.removeEventListener('mouseleave', hide);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Source: https://github.com/kapishdima/soundcn/blob/main/registry/soundcn/sounds/error-buzz/error-buzz.ts
|
|
2
2
|
// License: CC0 — Kenney (kenney.nl/assets/interface-sounds)
|
|
3
3
|
// Error or destructive action
|
|
4
|
-
export
|
|
4
|
+
export const errorSound = "data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYyLjMuMTAwAAAAAAAAAAAAAAD/+1DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAUAAATkAFVVVVVVVVVVVVVVVVVVVVVVVVV/f39/f39/f39/f39/f39/f39/f6qqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dX//////////////////////////wAAAABMYXZjNjIuMTEAAAAAAAAAAAAAAAAkAkAAAAAAAAAE5Ct0VKUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//tQxAAABrBja7SRAAHpFK03MPAAAAADpAAQG24rFaPVBQCAYJG5znPz/b6NqcOBgZzv/QIBgYs7/6lBj4Ph///3cuH+c5c//+GAAQHa6AAAAASCUWIBQCyQJZStElAWcc9uAG+mh4XttXtzdiUNiuVScgn20gM5uFsLE6Fxhn+ooJIxfQBCzFUpjz1wPUomKsWXV38+X77GWGKcTC8hH7Co3sLLRijb/OSZ8+EokIpc0wKgoFjAlEQPHQa/EoSES2//LFVoN1sAAAFygDXWUv/7UsQFgArspWW9hAARbhFq6rKwAqVF/AdqejzEmlOy1F5XbkjDHJAIyEgPA+AKAqSGQaGguBULhCkD3qDWeB8DzUNJbb7lrlVWa/rjaIbq/7ygo9INWMicMl4dEirDxb3I3gAAAnMANIK2GREbZA8FCWtPoXkVeZzwrfKGDLmaM47+A5HoEIbi4gyFJy6Lmkkmkkdp1KkjzrddOq2w5zrn//3Nb8JPBk7SoFXc7lVA0eBoqdd2w6VOjw0oNAAAAAUKKBQgQAQAi/uhmyAnxM15//tSxAoADGU5Z5j2gAGGky63mPAAjm0TcAIi5zgBMLwtS483d1dBIkP8lB5jsGA/8YMkx5gGgOZ/+aKWbkcKh//iUBzyXNzyaf//5o0uMgOcvp///+hpqQJc3Bx3//EBlEuHxpKKAdn8DBSAiMlz2HZmqAgCAHn1q11bWu4/1m+IicMtXoeozcJYmSXkwOp65uK0nVEe7qD41qvYsXN5ILFBbn7x2zNszFuDq0bFoVg6sFiOvelwLQEx5WIgKdJSURXEINB2aJcjaCAKTlorySL/+1LEBYAL0Etrp72BUXuPKaqwkAD4DYLuOE5S4k4J4hxEGgahzphEVjuagsILJWXG/d9eq7/vOigiDq0gqDbknASNA0YvDQbJESwUU8kImtSMLJLDGicxMKiQe4VORi841zX1q6GfTvAYEAgAgvgaCLXAQXsZE6LXsnhdaMzDtOVL3drZqkuERpYLEy3TQoclLx9xjGMaRISEysTNRQilEiRbFCAg4DRXKuWCuInh0RA0DR46CoalUc7EUsGv9ySwNBxUis9ztQVJJbJBdbhcBv/7UsQEgAuknU+5iIARao9j256QAAGAgAAIlpEiPnR6OUh6uOfPDAyW+TEqJml80EGSJscj0FieQbVBbINzdM3A8AKYG0QGf80QNEIWLhZwPzBsSFx//wbOBc0IIixkmLI/hcOAmz/SlQBAADMWYATAD4rgqQAkAxJKukNQ0tpcVC9ep1DQ0/+SyJEijkVUKHEQqZWIhUKiaZCKWlUKFnyWayUrVQsuEoaiUNo8qGiwcEQdWCvWdWdg1PXeV///f2f7OJVMQU1FMy4xMDBVVVVV";
|