stimeo-ui 0.7.0 → 0.8.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 +62 -0
- data/dist/controllers/auto_submit_controller.js +94 -16
- data/dist/controllers/carousel_controller.js +451 -100
- data/dist/controllers/currency_input_controller.js +305 -74
- data/dist/controllers/direct_upload_controller.js +12 -2
- data/dist/controllers/file_dropzone_controller.js +386 -63
- data/dist/controllers/flash_controller.js +3 -1
- data/dist/controllers/frame_loading_controller.js +2 -1
- data/dist/controllers/input_mask_controller.js +251 -76
- data/dist/controllers/nested_form_controller.js +450 -42
- data/dist/controllers/otp_controller.js +472 -114
- data/dist/controllers/spinner_controller.js +7 -5
- data/dist/controllers/submit_once_controller.js +3 -1
- data/dist/controllers/textarea_autosize_controller.js +131 -3
- data/dist/index.js +1927 -607
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -222,7 +222,7 @@ var SpinnerController = class extends Controller {
|
|
|
222
222
|
timeout: { type: Number, default: 0 }
|
|
223
223
|
};
|
|
224
224
|
static actions = ["start", "stop"];
|
|
225
|
-
static events = ["hide", "show", "timeout"];
|
|
225
|
+
static events = ["hide", "show", "timeout", "reconcile"];
|
|
226
226
|
#timers = new SafeTimeout();
|
|
227
227
|
#floor = new MinDurationFloor(this.#timers);
|
|
228
228
|
#gate = new DetachGate();
|
|
@@ -326,16 +326,18 @@ var SpinnerController = class extends Controller {
|
|
|
326
326
|
* Returns the loading state to idle for the snapshot Turbo is about to take,
|
|
327
327
|
* so a page reached with the Back button is not restored mid-load with a
|
|
328
328
|
* spinner nothing can stop. State only: `data-state`, the indicator's `hidden`,
|
|
329
|
-
* and `aria-busy`. No `hide` is dispatched —
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* leaves the running cycle intact.
|
|
329
|
+
* and `aria-busy`. No `hide` is dispatched — that would claim the load finished.
|
|
330
|
+
* The rewind reports itself as `reconcile` instead, so a consumer painting from
|
|
331
|
+
* `show` can drop it before the snapshot is taken. The live page keeps its
|
|
332
|
+
* timers, so a navigation that never completes leaves the running cycle intact.
|
|
333
333
|
*/
|
|
334
334
|
#rewindForCache() {
|
|
335
|
+
const running = this.element.getAttribute("data-state") !== "idle";
|
|
335
336
|
this.#cancelTimeout();
|
|
336
337
|
this.#setBusy(false);
|
|
337
338
|
if (this.hasIndicatorTarget) this.indicatorTarget.hidden = true;
|
|
338
339
|
this.element.setAttribute("data-state", "idle");
|
|
340
|
+
if (running) this.dispatch("reconcile", { detail: {} });
|
|
339
341
|
}
|
|
340
342
|
/** Reflects busy state onto the controlled region (if present). */
|
|
341
343
|
#setBusy(busy) {
|
|
@@ -203,7 +203,7 @@ var SubmitOnceController = class extends Controller {
|
|
|
203
203
|
restoreFocus: { type: Boolean, default: false }
|
|
204
204
|
};
|
|
205
205
|
static actions = ["cancel", "finish", "start"];
|
|
206
|
-
static events = ["start", "end"];
|
|
206
|
+
static events = ["start", "end", "reconcile"];
|
|
207
207
|
#timers = new SafeTimeout();
|
|
208
208
|
#gate = new DetachGate();
|
|
209
209
|
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
@@ -419,7 +419,9 @@ var SubmitOnceController = class extends Controller {
|
|
|
419
419
|
}
|
|
420
420
|
/** Keeps Turbo's cached snapshot idle, without events, announcements, or focus moves. */
|
|
421
421
|
#rewindForCache() {
|
|
422
|
+
const forms = [...this.#sessions.keys()];
|
|
422
423
|
this.#abandonSessions();
|
|
424
|
+
if (forms.length > 0) this.dispatch("reconcile", { detail: { forms } });
|
|
423
425
|
}
|
|
424
426
|
/** Tears down a true detach; an in-page move is canceled by the next `connect()`. */
|
|
425
427
|
#teardown() {
|
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
// src/controllers/textarea_autosize_controller.ts
|
|
4
|
+
|
|
5
|
+
// src/utils/layout_observer.ts
|
|
6
|
+
var LayoutObserver = class {
|
|
7
|
+
#callback;
|
|
8
|
+
#resizeObserverFactory;
|
|
9
|
+
#resizeObserver = null;
|
|
10
|
+
#observingViewport = false;
|
|
11
|
+
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
12
|
+
#handleViewportResize = () => {
|
|
13
|
+
this.#callback();
|
|
14
|
+
};
|
|
15
|
+
constructor(callback, options = {}) {
|
|
16
|
+
this.#callback = callback;
|
|
17
|
+
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Starts observing an element's size. Repeated calls observe additional
|
|
21
|
+
* elements through the same shared observer. No-ops when no
|
|
22
|
+
* `ResizeObserver` implementation is available.
|
|
23
|
+
*/
|
|
24
|
+
observe(element) {
|
|
25
|
+
if (!this.#resizeObserverFactory) return;
|
|
26
|
+
if (!this.#resizeObserver) {
|
|
27
|
+
this.#resizeObserver = this.#resizeObserverFactory(() => {
|
|
28
|
+
this.#callback();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
this.#resizeObserver.observe(element);
|
|
32
|
+
}
|
|
33
|
+
/** Stops observing a single element while leaving any others in place. */
|
|
34
|
+
unobserve(element) {
|
|
35
|
+
this.#resizeObserver?.unobserve(element);
|
|
36
|
+
}
|
|
37
|
+
/** Starts observing viewport resizes. Idempotent: the listener is added once. */
|
|
38
|
+
observeViewport() {
|
|
39
|
+
if (this.#observingViewport) return;
|
|
40
|
+
this.#observingViewport = true;
|
|
41
|
+
window.addEventListener("resize", this.#handleViewportResize);
|
|
42
|
+
}
|
|
43
|
+
/** Stops observing viewport resizes without affecting element observation. */
|
|
44
|
+
unobserveViewport() {
|
|
45
|
+
if (!this.#observingViewport) return;
|
|
46
|
+
this.#observingViewport = false;
|
|
47
|
+
window.removeEventListener("resize", this.#handleViewportResize);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Releases every observation: disconnects the {@link ResizeObserver} and
|
|
51
|
+
* removes the viewport listener. Safe to call multiple times. Call this from a
|
|
52
|
+
* controller's `disconnect()`.
|
|
53
|
+
*/
|
|
54
|
+
disconnect() {
|
|
55
|
+
this.#resizeObserver?.disconnect();
|
|
56
|
+
this.#resizeObserver = null;
|
|
57
|
+
this.unobserveViewport();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
3
61
|
// src/controllers/textarea_autosize_controller.ts
|
|
4
62
|
function px(value) {
|
|
5
63
|
const n = Number.parseFloat(value);
|
|
@@ -13,18 +71,69 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
13
71
|
static actions = ["resize"];
|
|
14
72
|
static events = ["resize"];
|
|
15
73
|
#lastHeight = -1;
|
|
16
|
-
#
|
|
74
|
+
#lastWidth = -1;
|
|
75
|
+
#started = false;
|
|
76
|
+
#pending = false;
|
|
77
|
+
#fonts = null;
|
|
78
|
+
/**
|
|
79
|
+
* Content triggers arriving while the box is collapsed (`display: none`
|
|
80
|
+
* ancestors) are deferred: `scrollHeight` is 0 there, so measuring would
|
|
81
|
+
* clamp the kept height to the `minRows` floor and dispatch a bogus
|
|
82
|
+
* `resize`. The next report from a rendered box flushes the deferral.
|
|
83
|
+
*/
|
|
84
|
+
#remeasure = () => {
|
|
85
|
+
if (this.element.clientWidth === 0) {
|
|
86
|
+
this.#pending = true;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
17
89
|
this.resize();
|
|
18
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Re-measures when the content width changed (re-wrapping, including a
|
|
93
|
+
* horizontal padding change under `border-box`), the applied inline height
|
|
94
|
+
* was stripped (a Turbo morph syncing attributes from server HTML that has no
|
|
95
|
+
* `style`), or a content trigger was deferred while the box was collapsed.
|
|
96
|
+
* This instance's own height writes match none of these signals, so they
|
|
97
|
+
* cannot re-enter; an authored inline height (e.g. a user dragging the native
|
|
98
|
+
* resize handle) is left alone until the next content trigger. Reports from a
|
|
99
|
+
* still-collapsed box are skipped (nothing is measurable there).
|
|
100
|
+
*/
|
|
101
|
+
#layout = new LayoutObserver(() => {
|
|
102
|
+
if (this.element.clientWidth === 0) return;
|
|
103
|
+
const width = this.#contentWidth();
|
|
104
|
+
const stripped = this.element.style.height === "";
|
|
105
|
+
if (width === this.#lastWidth && !stripped && !this.#pending) return;
|
|
106
|
+
this.#lastWidth = width;
|
|
107
|
+
this.resize();
|
|
108
|
+
});
|
|
19
109
|
connect() {
|
|
20
|
-
this.element.
|
|
110
|
+
this.#lastHeight = Math.round(px(this.element.style.height));
|
|
111
|
+
this.#lastWidth = this.#contentWidth();
|
|
112
|
+
this.element.addEventListener("input", this.#remeasure);
|
|
113
|
+
this.element.addEventListener("change", this.#remeasure);
|
|
114
|
+
this.#layout.observe(this.element);
|
|
115
|
+
this.#bindFonts();
|
|
116
|
+
this.#started = true;
|
|
21
117
|
this.resize();
|
|
22
118
|
}
|
|
23
119
|
disconnect() {
|
|
24
|
-
this
|
|
120
|
+
this.#started = false;
|
|
121
|
+
this.element.removeEventListener("input", this.#remeasure);
|
|
122
|
+
this.element.removeEventListener("change", this.#remeasure);
|
|
123
|
+
this.#layout.disconnect();
|
|
124
|
+
this.#unbindFonts();
|
|
125
|
+
}
|
|
126
|
+
/** Re-clamps when application code or a Turbo morph changes `minRows`. */
|
|
127
|
+
minRowsValueChanged() {
|
|
128
|
+
if (this.#started) this.#remeasure();
|
|
129
|
+
}
|
|
130
|
+
/** Re-clamps when application code or a Turbo morph changes `maxRows`. */
|
|
131
|
+
maxRowsValueChanged() {
|
|
132
|
+
if (this.#started) this.#remeasure();
|
|
25
133
|
}
|
|
26
134
|
/** Re-measures the content and applies the clamped height. */
|
|
27
135
|
resize() {
|
|
136
|
+
this.#pending = false;
|
|
28
137
|
const el = this.element;
|
|
29
138
|
const style = window.getComputedStyle(el);
|
|
30
139
|
const lineHeight = this.#lineHeight(style);
|
|
@@ -58,6 +167,11 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
58
167
|
this.dispatch("resize", { detail: { height, rows } });
|
|
59
168
|
}
|
|
60
169
|
}
|
|
170
|
+
/** Content-box width — the wrapping input, unaffected by this instance's height writes. */
|
|
171
|
+
#contentWidth() {
|
|
172
|
+
const style = window.getComputedStyle(this.element);
|
|
173
|
+
return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));
|
|
174
|
+
}
|
|
61
175
|
/** Resolved line height, falling back to ~1.2× font-size when `normal`. */
|
|
62
176
|
#lineHeight(style) {
|
|
63
177
|
const lh = px(style.lineHeight);
|
|
@@ -65,6 +179,20 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
65
179
|
const fontSize = px(style.fontSize);
|
|
66
180
|
return fontSize > 0 ? fontSize * 1.2 : 16;
|
|
67
181
|
}
|
|
182
|
+
/** Subscribes to font completion when the current engine exposes `document.fonts`. */
|
|
183
|
+
#bindFonts() {
|
|
184
|
+
const fonts = this.element.ownerDocument.fonts;
|
|
185
|
+
if (!fonts) return;
|
|
186
|
+
this.#fonts = fonts;
|
|
187
|
+
fonts.addEventListener("loadingdone", this.#remeasure);
|
|
188
|
+
fonts.addEventListener("loadingerror", this.#remeasure);
|
|
189
|
+
}
|
|
190
|
+
/** Removes the font completion subscriptions. */
|
|
191
|
+
#unbindFonts() {
|
|
192
|
+
this.#fonts?.removeEventListener("loadingdone", this.#remeasure);
|
|
193
|
+
this.#fonts?.removeEventListener("loadingerror", this.#remeasure);
|
|
194
|
+
this.#fonts = null;
|
|
195
|
+
}
|
|
68
196
|
};
|
|
69
197
|
|
|
70
198
|
export { TextareaAutosizeController };
|