stimeo-ui 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +116 -0
- data/dist/controllers/aspect_ratio_controller.js +19 -11
- data/dist/controllers/avatar_controller.js +195 -40
- data/dist/controllers/carousel_controller.js +85 -9
- data/dist/controllers/checkbox_controller.js +136 -25
- data/dist/controllers/color_picker_controller.js +35 -9
- data/dist/controllers/date_range_picker_controller.js +157 -30
- data/dist/controllers/file_dropzone_controller.js +26 -3
- data/dist/controllers/idle_controller.js +27 -5
- data/dist/controllers/menubar_controller.js +5 -3
- data/dist/controllers/multi_select_controller.js +460 -151
- data/dist/controllers/number_input_controller.js +275 -51
- data/dist/controllers/overflow_menu_controller.js +4 -0
- data/dist/controllers/pagination_controller.js +33 -0
- data/dist/controllers/password_strength_controller.js +20 -2
- data/dist/controllers/persist_controller.js +24 -5
- data/dist/controllers/radio_group_controller.js +540 -56
- data/dist/controllers/rating_controller.js +272 -89
- data/dist/controllers/resizable_controller.js +33 -0
- data/dist/controllers/roving_controller.js +60 -5
- data/dist/controllers/scroll_area_controller.js +154 -22
- data/dist/controllers/scroll_visibility_controller.js +33 -0
- data/dist/controllers/tags_input_controller.js +356 -120
- data/dist/controllers/time_picker_controller.js +296 -107
- data/dist/controllers/toggle_group_controller.js +378 -55
- data/dist/controllers/toolbar_controller.js +5 -3
- data/dist/controllers/tree_view_controller.js +5 -3
- data/dist/index.js +2637 -781
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -2,6 +2,43 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/scroll_area_controller.ts
|
|
4
4
|
|
|
5
|
+
// src/utils/attribute_lease.ts
|
|
6
|
+
var AttributeLease = class {
|
|
7
|
+
#attribute;
|
|
8
|
+
#records = /* @__PURE__ */ new Map();
|
|
9
|
+
/** @param attribute - The attribute whose temporary values this lease owns. */
|
|
10
|
+
constructor(attribute) {
|
|
11
|
+
this.#attribute = attribute;
|
|
12
|
+
}
|
|
13
|
+
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
14
|
+
write(element, value) {
|
|
15
|
+
const existing = this.#records.get(element);
|
|
16
|
+
if (existing) {
|
|
17
|
+
existing.written = value;
|
|
18
|
+
} else {
|
|
19
|
+
this.#records.set(element, {
|
|
20
|
+
original: element.getAttribute(this.#attribute),
|
|
21
|
+
written: value
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
25
|
+
else element.setAttribute(this.#attribute, value);
|
|
26
|
+
}
|
|
27
|
+
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
28
|
+
return(element) {
|
|
29
|
+
const record = this.#records.get(element);
|
|
30
|
+
if (!record) return;
|
|
31
|
+
this.#records.delete(element);
|
|
32
|
+
if (element.getAttribute(this.#attribute) !== record.written) return;
|
|
33
|
+
if (record.original === null) element.removeAttribute(this.#attribute);
|
|
34
|
+
else element.setAttribute(this.#attribute, record.original);
|
|
35
|
+
}
|
|
36
|
+
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
37
|
+
returnAll() {
|
|
38
|
+
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
5
42
|
// src/utils/layout_observer.ts
|
|
6
43
|
var LayoutObserver = class {
|
|
7
44
|
#callback;
|
|
@@ -72,10 +109,74 @@ function logicalScrollMetrics(element, horizontal) {
|
|
|
72
109
|
return { position: Math.min(max, Math.max(0, position)), max };
|
|
73
110
|
}
|
|
74
111
|
|
|
112
|
+
// src/utils/microtask_coalescer.ts
|
|
113
|
+
var MicrotaskCoalescer = class {
|
|
114
|
+
#run;
|
|
115
|
+
#queued = false;
|
|
116
|
+
#active = false;
|
|
117
|
+
#generation = 0;
|
|
118
|
+
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
119
|
+
constructor(run) {
|
|
120
|
+
this.#run = run;
|
|
121
|
+
}
|
|
122
|
+
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
123
|
+
activate() {
|
|
124
|
+
this.#active = true;
|
|
125
|
+
}
|
|
126
|
+
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
127
|
+
cancel() {
|
|
128
|
+
this.#active = false;
|
|
129
|
+
this.#queued = false;
|
|
130
|
+
this.#generation += 1;
|
|
131
|
+
}
|
|
132
|
+
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
133
|
+
schedule() {
|
|
134
|
+
if (!this.#active || this.#queued) return;
|
|
135
|
+
this.#queued = true;
|
|
136
|
+
const generation = this.#generation;
|
|
137
|
+
queueMicrotask(() => {
|
|
138
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
139
|
+
this.#queued = false;
|
|
140
|
+
this.#run();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/utils/before_cache_reset.ts
|
|
146
|
+
var BeforeCacheReset = class _BeforeCacheReset {
|
|
147
|
+
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
148
|
+
static #subscribers = /* @__PURE__ */ new Set();
|
|
149
|
+
/** The shared listener; installed while at least one instance is subscribed. */
|
|
150
|
+
static #onBeforeCache = () => {
|
|
151
|
+
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
152
|
+
};
|
|
153
|
+
#rewind;
|
|
154
|
+
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
155
|
+
constructor(rewind) {
|
|
156
|
+
this.#rewind = rewind;
|
|
157
|
+
}
|
|
158
|
+
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
159
|
+
activate() {
|
|
160
|
+
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
161
|
+
_BeforeCacheReset.#subscribers.add(this);
|
|
162
|
+
if (first) {
|
|
163
|
+
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
167
|
+
deactivate() {
|
|
168
|
+
_BeforeCacheReset.#subscribers.delete(this);
|
|
169
|
+
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
170
|
+
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
75
174
|
// src/utils/tabindex_loan.ts
|
|
76
175
|
var TabindexLoan = class {
|
|
77
176
|
#value;
|
|
78
177
|
#lent = /* @__PURE__ */ new Set();
|
|
178
|
+
/** Returns live loans before Turbo can copy them into its page snapshot. */
|
|
179
|
+
#beforeCache = new BeforeCacheReset(() => this.returnAll());
|
|
79
180
|
/**
|
|
80
181
|
* @param value - the `tabindex` to lend. `"-1"` (the default) is
|
|
81
182
|
* programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
|
|
@@ -89,6 +190,7 @@ var TabindexLoan = class {
|
|
|
89
190
|
if (element.hasAttribute("tabindex")) return;
|
|
90
191
|
element.setAttribute("tabindex", this.#value);
|
|
91
192
|
this.#lent.add(element);
|
|
193
|
+
this.#beforeCache.activate();
|
|
92
194
|
}
|
|
93
195
|
/** Takes back every loan whose value is still the one that was lent. */
|
|
94
196
|
returnAll() {
|
|
@@ -96,6 +198,7 @@ var TabindexLoan = class {
|
|
|
96
198
|
if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
|
|
97
199
|
}
|
|
98
200
|
this.#lent.clear();
|
|
201
|
+
this.#beforeCache.deactivate();
|
|
99
202
|
}
|
|
100
203
|
};
|
|
101
204
|
|
|
@@ -117,29 +220,56 @@ var ScrollAreaController = class extends Controller {
|
|
|
117
220
|
};
|
|
118
221
|
static events = ["reach"];
|
|
119
222
|
#layout = new LayoutObserver(() => this.#update());
|
|
223
|
+
/** Current element receiving scroll, resize, mutation, and keyboard-reach behavior. */
|
|
224
|
+
#viewport = null;
|
|
225
|
+
/** Guards target callbacks before connect and after disconnect. */
|
|
226
|
+
#connected = false;
|
|
227
|
+
/** Collapses target replacement callbacks into one final-DOM rebind. */
|
|
228
|
+
#rebind = new MicrotaskCoalescer(() => this.#syncViewport());
|
|
120
229
|
/** Re-checks the tab stop when the viewport's focusable content comes or goes. */
|
|
121
230
|
#content = null;
|
|
122
231
|
/** Last edge reported via `reach`, so the event fires once per arrival. */
|
|
123
232
|
#lastEdge = null;
|
|
124
233
|
/** Whether this controller added `tabindex`, so teardown only removes its own. */
|
|
125
234
|
#tabindex = new TabindexLoan("0");
|
|
126
|
-
/**
|
|
127
|
-
#
|
|
235
|
+
/** Temporarily owns a derived `role="region"` without losing an authored replacement. */
|
|
236
|
+
#role = new AttributeLease("role");
|
|
128
237
|
#onScroll = () => {
|
|
129
238
|
this.#update();
|
|
130
239
|
};
|
|
131
240
|
connect() {
|
|
132
|
-
|
|
133
|
-
this.
|
|
134
|
-
this.#layout.observe(this.viewportTarget);
|
|
241
|
+
this.#connected = true;
|
|
242
|
+
this.#rebind.activate();
|
|
135
243
|
this.#layout.observeViewport();
|
|
244
|
+
this.#syncViewport();
|
|
245
|
+
}
|
|
246
|
+
/** Schedules a complete observer/listener rebind for a runtime viewport target. */
|
|
247
|
+
viewportTargetConnected() {
|
|
248
|
+
this.#rebind.schedule();
|
|
249
|
+
}
|
|
250
|
+
/** Schedules cleanup or replacement binding after a viewport leaves. */
|
|
251
|
+
viewportTargetDisconnected() {
|
|
252
|
+
this.#rebind.schedule();
|
|
253
|
+
}
|
|
254
|
+
/** Rebinds every viewport-owned resource against the final target in this mutation batch. */
|
|
255
|
+
#syncViewport() {
|
|
256
|
+
if (!this.#connected) return;
|
|
257
|
+
const next = this.hasViewportTarget ? this.viewportTarget : null;
|
|
258
|
+
if (next === this.#viewport) {
|
|
259
|
+
this.#update();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (this.#viewport) this.#unbindViewport(this.#viewport);
|
|
263
|
+
this.#viewport = next;
|
|
264
|
+
if (!next) return;
|
|
265
|
+
next.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
266
|
+
this.#layout.observe(next);
|
|
136
267
|
if (typeof MutationObserver !== "undefined") {
|
|
137
268
|
this.#content = new MutationObserver(() => {
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
this.#syncKeyboardReach(vp, this.#syncOverflow(vp));
|
|
269
|
+
if (this.#viewport !== next) return;
|
|
270
|
+
this.#syncKeyboardReach(next, this.#syncOverflow(next));
|
|
141
271
|
});
|
|
142
|
-
this.#content.observe(
|
|
272
|
+
this.#content.observe(next, {
|
|
143
273
|
subtree: true,
|
|
144
274
|
childList: true,
|
|
145
275
|
attributes: true
|
|
@@ -148,19 +278,26 @@ var ScrollAreaController = class extends Controller {
|
|
|
148
278
|
this.#update();
|
|
149
279
|
}
|
|
150
280
|
disconnect() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
281
|
+
this.#connected = false;
|
|
282
|
+
this.#rebind.cancel();
|
|
283
|
+
if (this.#viewport) this.#unbindViewport(this.#viewport);
|
|
155
284
|
this.#layout.disconnect();
|
|
285
|
+
this.#lastEdge = null;
|
|
286
|
+
}
|
|
287
|
+
/** Releases every resource and derived attribute owned by one former viewport. */
|
|
288
|
+
#unbindViewport(viewport) {
|
|
289
|
+
viewport.removeEventListener("scroll", this.#onScroll);
|
|
290
|
+
this.#layout.unobserve(viewport);
|
|
156
291
|
this.#content?.disconnect();
|
|
157
292
|
this.#content = null;
|
|
293
|
+
this.#clearAddedAttributes(viewport);
|
|
294
|
+
if (this.#viewport === viewport) this.#viewport = null;
|
|
158
295
|
this.#lastEdge = null;
|
|
159
296
|
}
|
|
160
297
|
/** Re-measures overflow and scroll position and reflects the state hooks. */
|
|
161
298
|
#update() {
|
|
162
|
-
|
|
163
|
-
|
|
299
|
+
const vp = this.#viewport;
|
|
300
|
+
if (!vp) return;
|
|
164
301
|
const overflowing = this.#syncOverflow(vp);
|
|
165
302
|
this.#syncKeyboardReach(vp, overflowing);
|
|
166
303
|
const { position, progress } = this.#measurePosition(vp);
|
|
@@ -174,7 +311,6 @@ var ScrollAreaController = class extends Controller {
|
|
|
174
311
|
this.#lastEdge = null;
|
|
175
312
|
}
|
|
176
313
|
}
|
|
177
|
-
/** Whether the viewport can scroll on the configured axis. */
|
|
178
314
|
/**
|
|
179
315
|
* Measures overflow and reflects the `data-overflow` hook.
|
|
180
316
|
*
|
|
@@ -219,8 +355,7 @@ var ScrollAreaController = class extends Controller {
|
|
|
219
355
|
if (wantsTabindex) {
|
|
220
356
|
this.#tabindex.lend(vp);
|
|
221
357
|
if (!vp.hasAttribute("role") && this.#hasAccessibleName(vp)) {
|
|
222
|
-
|
|
223
|
-
this.#addedRole = true;
|
|
358
|
+
this.#role.write(vp, "region");
|
|
224
359
|
}
|
|
225
360
|
} else {
|
|
226
361
|
this.#clearAddedAttributes(vp);
|
|
@@ -229,10 +364,7 @@ var ScrollAreaController = class extends Controller {
|
|
|
229
364
|
/** Removes (and resets the flags for) only the attributes this controller added. */
|
|
230
365
|
#clearAddedAttributes(vp) {
|
|
231
366
|
this.#tabindex.returnAll();
|
|
232
|
-
|
|
233
|
-
vp.removeAttribute("role");
|
|
234
|
-
this.#addedRole = false;
|
|
235
|
-
}
|
|
367
|
+
this.#role.return(vp);
|
|
236
368
|
}
|
|
237
369
|
/**
|
|
238
370
|
* Whether the viewport owns something the user can Tab to *right now*.
|
|
@@ -7,10 +7,41 @@ function prefersReducedMotion() {
|
|
|
7
7
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// src/utils/before_cache_reset.ts
|
|
11
|
+
var BeforeCacheReset = class _BeforeCacheReset {
|
|
12
|
+
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
13
|
+
static #subscribers = /* @__PURE__ */ new Set();
|
|
14
|
+
/** The shared listener; installed while at least one instance is subscribed. */
|
|
15
|
+
static #onBeforeCache = () => {
|
|
16
|
+
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
17
|
+
};
|
|
18
|
+
#rewind;
|
|
19
|
+
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
20
|
+
constructor(rewind) {
|
|
21
|
+
this.#rewind = rewind;
|
|
22
|
+
}
|
|
23
|
+
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
24
|
+
activate() {
|
|
25
|
+
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
26
|
+
_BeforeCacheReset.#subscribers.add(this);
|
|
27
|
+
if (first) {
|
|
28
|
+
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
32
|
+
deactivate() {
|
|
33
|
+
_BeforeCacheReset.#subscribers.delete(this);
|
|
34
|
+
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
35
|
+
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
10
39
|
// src/utils/tabindex_loan.ts
|
|
11
40
|
var TabindexLoan = class {
|
|
12
41
|
#value;
|
|
13
42
|
#lent = /* @__PURE__ */ new Set();
|
|
43
|
+
/** Returns live loans before Turbo can copy them into its page snapshot. */
|
|
44
|
+
#beforeCache = new BeforeCacheReset(() => this.returnAll());
|
|
14
45
|
/**
|
|
15
46
|
* @param value - the `tabindex` to lend. `"-1"` (the default) is
|
|
16
47
|
* programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
|
|
@@ -24,6 +55,7 @@ var TabindexLoan = class {
|
|
|
24
55
|
if (element.hasAttribute("tabindex")) return;
|
|
25
56
|
element.setAttribute("tabindex", this.#value);
|
|
26
57
|
this.#lent.add(element);
|
|
58
|
+
this.#beforeCache.activate();
|
|
27
59
|
}
|
|
28
60
|
/** Takes back every loan whose value is still the one that was lent. */
|
|
29
61
|
returnAll() {
|
|
@@ -31,6 +63,7 @@ var TabindexLoan = class {
|
|
|
31
63
|
if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
|
|
32
64
|
}
|
|
33
65
|
this.#lent.clear();
|
|
66
|
+
this.#beforeCache.deactivate();
|
|
34
67
|
}
|
|
35
68
|
};
|
|
36
69
|
|