stimeo-ui 0.16.0 → 0.17.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 +31 -0
- data/README.md +8 -1
- data/dist/cable/index.js +363 -436
- data/dist/controllers/accordion_controller.js +166 -55
- data/dist/controllers/alert_dialog_controller.js +862 -221
- data/dist/controllers/announcer_controller.js +135 -198
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +271 -127
- data/dist/controllers/avatar_controller.js +171 -105
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +262 -52
- data/dist/controllers/calendar_controller.js +175 -209
- data/dist/controllers/carousel_controller.js +461 -399
- data/dist/controllers/character_counter_controller.js +246 -156
- data/dist/controllers/checkbox_controller.js +32 -74
- data/dist/controllers/clipboard_controller.js +168 -147
- data/dist/controllers/collapsible_controller.js +301 -120
- data/dist/controllers/color_picker_controller.js +149 -139
- data/dist/controllers/combobox_controller.js +360 -133
- data/dist/controllers/command_palette_controller.js +970 -358
- data/dist/controllers/conditional_fields_controller.js +166 -124
- data/dist/controllers/confirm_controller.js +939 -248
- data/dist/controllers/context_menu_controller.js +256 -128
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +183 -148
- data/dist/controllers/currency_input_controller.js +134 -158
- data/dist/controllers/data_grid_controller.js +114 -186
- data/dist/controllers/date_range_picker_controller.js +288 -206
- data/dist/controllers/dialog_controller.js +858 -223
- data/dist/controllers/direct_upload_controller.js +73 -153
- data/dist/controllers/dirty_form_controller.js +14 -48
- data/dist/controllers/dismissible_controller.js +175 -21
- data/dist/controllers/drawer_controller.js +933 -341
- data/dist/controllers/dropdown_controller.js +312 -82
- data/dist/controllers/editable_controller.js +158 -68
- data/dist/controllers/empty_state_controller.js +67 -53
- data/dist/controllers/file_dropzone_controller.js +311 -207
- data/dist/controllers/filter_controller.js +67 -39
- data/dist/controllers/flash_controller.js +172 -314
- data/dist/controllers/focus_controller.js +654 -240
- data/dist/controllers/form_field_controller.js +147 -132
- data/dist/controllers/form_validation_controller.js +13 -86
- data/dist/controllers/frame_loading_controller.js +264 -213
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +309 -118
- data/dist/controllers/idle_controller.js +267 -101
- data/dist/controllers/input_mask_controller.js +71 -72
- data/dist/controllers/intersection_controller.js +147 -106
- data/dist/controllers/lazy_frame_controller.js +9 -65
- data/dist/controllers/listbox_controller.js +306 -204
- data/dist/controllers/local_time_controller.js +54 -64
- data/dist/controllers/masonry_controller.js +70 -93
- data/dist/controllers/menu_controller.js +214 -156
- data/dist/controllers/menubar_controller.js +170 -329
- data/dist/controllers/meter_controller.js +151 -67
- data/dist/controllers/multi_select_controller.js +593 -358
- data/dist/controllers/navigation_menu_controller.js +124 -232
- data/dist/controllers/nested_form_controller.js +352 -182
- data/dist/controllers/network_status_controller.js +242 -54
- data/dist/controllers/number_input_controller.js +320 -263
- data/dist/controllers/optimistic_controller.js +16 -76
- data/dist/controllers/otp_controller.js +198 -193
- data/dist/controllers/overflow_indicator_controller.js +116 -113
- data/dist/controllers/overflow_menu_controller.js +343 -283
- data/dist/controllers/pagination_controller.js +295 -119
- data/dist/controllers/password_reveal_controller.js +389 -121
- data/dist/controllers/password_strength_controller.js +99 -153
- data/dist/controllers/persist_controller.js +237 -103
- data/dist/controllers/pointer_drag_controller.js +185 -165
- data/dist/controllers/popover_controller.js +236 -97
- data/dist/controllers/portal_controller.js +209 -76
- data/dist/controllers/preview_guard_controller.js +232 -116
- data/dist/controllers/progress_controller.js +125 -56
- data/dist/controllers/radio_group_controller.js +117 -137
- data/dist/controllers/range_slider_controller.js +192 -105
- data/dist/controllers/rating_controller.js +418 -207
- data/dist/controllers/read_more_controller.js +134 -63
- data/dist/controllers/reading_progress_controller.js +147 -135
- data/dist/controllers/relative_time_controller.js +145 -110
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- data/dist/controllers/resizable_controller.js +255 -139
- data/dist/controllers/roving_controller.js +39 -84
- data/dist/controllers/scroll_area_controller.js +428 -225
- data/dist/controllers/scroll_restore_controller.js +0 -53
- data/dist/controllers/scroll_visibility_controller.js +395 -145
- data/dist/controllers/scrollspy_controller.js +181 -242
- data/dist/controllers/separator_controller.js +267 -140
- data/dist/controllers/sidebar_controller.js +1023 -386
- data/dist/controllers/skeleton_controller.js +248 -97
- data/dist/controllers/slider_controller.js +159 -79
- data/dist/controllers/smart_sticky_header_controller.js +162 -63
- data/dist/controllers/sortable_controller.js +0 -112
- data/dist/controllers/spinner_controller.js +277 -169
- data/dist/controllers/step_indicator_controller.js +99 -46
- data/dist/controllers/stepper_controller.js +114 -73
- data/dist/controllers/stick_to_bottom_controller.js +262 -138
- data/dist/controllers/sticky_observer_controller.js +214 -49
- data/dist/controllers/submit_once_controller.js +294 -205
- data/dist/controllers/switch_controller.js +33 -60
- data/dist/controllers/tabs_controller.js +35 -29
- data/dist/controllers/tags_input_controller.js +136 -145
- data/dist/controllers/textarea_autosize_controller.js +71 -58
- data/dist/controllers/theme_controller.js +130 -112
- data/dist/controllers/time_picker_controller.js +146 -80
- data/dist/controllers/toast_controller.js +186 -297
- data/dist/controllers/toggle_group_controller.js +95 -136
- data/dist/controllers/toolbar_controller.js +68 -98
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +150 -345
- data/dist/index.js +6370 -9346
- data/dist/positioning/index.js +148 -47
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,37 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/relative_time_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/before_cache_reset.ts
|
|
6
|
-
var BeforeCacheReset = class _BeforeCacheReset {
|
|
7
|
-
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
8
|
-
static #subscribers = /* @__PURE__ */ new Set();
|
|
9
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
10
|
-
static #onBeforeCache = () => {
|
|
11
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
12
|
-
};
|
|
13
|
-
#rewind;
|
|
14
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
15
|
-
constructor(rewind) {
|
|
16
|
-
this.#rewind = rewind;
|
|
17
|
-
}
|
|
18
|
-
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
19
|
-
activate() {
|
|
20
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
21
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
22
|
-
if (first) {
|
|
23
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
27
|
-
deactivate() {
|
|
28
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
29
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
30
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// src/utils/intl_format.ts
|
|
35
3
|
var formatters = /* @__PURE__ */ new WeakMap();
|
|
36
4
|
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
37
5
|
const cache = cacheFor(factory);
|
|
@@ -60,32 +28,37 @@ function construct(factory, locale, options, fallbackLocale) {
|
|
|
60
28
|
}
|
|
61
29
|
}
|
|
62
30
|
|
|
63
|
-
// src/utils/locale.ts
|
|
64
31
|
function resolveLocale(element, declared = "") {
|
|
65
32
|
return declared || element.closest("[lang]")?.getAttribute("lang") || void 0;
|
|
66
33
|
}
|
|
67
34
|
|
|
68
|
-
|
|
35
|
+
var ListenerSet = class {
|
|
36
|
+
#abort = new AbortController();
|
|
37
|
+
add(target, type, handler, options) {
|
|
38
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
39
|
+
}
|
|
40
|
+
dispose() {
|
|
41
|
+
this.#abort.abort();
|
|
42
|
+
this.#abort = new AbortController();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
69
46
|
var MicrotaskCoalescer = class {
|
|
70
47
|
#run;
|
|
71
48
|
#queued = false;
|
|
72
49
|
#active = false;
|
|
73
50
|
#generation = 0;
|
|
74
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
75
51
|
constructor(run) {
|
|
76
52
|
this.#run = run;
|
|
77
53
|
}
|
|
78
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
79
54
|
activate() {
|
|
80
55
|
this.#active = true;
|
|
81
56
|
}
|
|
82
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
83
57
|
cancel() {
|
|
84
58
|
this.#active = false;
|
|
85
59
|
this.#queued = false;
|
|
86
60
|
this.#generation += 1;
|
|
87
61
|
}
|
|
88
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
89
62
|
schedule() {
|
|
90
63
|
if (!this.#active || this.#queued) return;
|
|
91
64
|
this.#queued = true;
|
|
@@ -98,43 +71,96 @@ var MicrotaskCoalescer = class {
|
|
|
98
71
|
}
|
|
99
72
|
};
|
|
100
73
|
|
|
101
|
-
|
|
74
|
+
var MorphRenderWatcher = class {
|
|
75
|
+
#listeners = new ListenerSet();
|
|
76
|
+
#pass;
|
|
77
|
+
#element = null;
|
|
78
|
+
#onMorph = () => this.#pass.schedule();
|
|
79
|
+
constructor(run) {
|
|
80
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
81
|
+
}
|
|
82
|
+
observe(element) {
|
|
83
|
+
if (this.#element === element) return;
|
|
84
|
+
this.disconnect();
|
|
85
|
+
this.#element = element;
|
|
86
|
+
this.#pass.activate();
|
|
87
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
88
|
+
}
|
|
89
|
+
schedule() {
|
|
90
|
+
this.#pass.schedule();
|
|
91
|
+
}
|
|
92
|
+
disconnect() {
|
|
93
|
+
this.#listeners.dispose();
|
|
94
|
+
this.#pass.cancel();
|
|
95
|
+
this.#element = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
100
|
+
|
|
101
|
+
var NUMBER_BOUNDS = {
|
|
102
|
+
nonNegative: { finite: true, min: 0 },
|
|
103
|
+
timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
|
|
104
|
+
function matchesNumberBounds(value, bounds) {
|
|
105
|
+
if (!Number.isFinite(value)) {
|
|
106
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
107
|
+
if (direction === null) return false;
|
|
108
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
109
|
+
}
|
|
110
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
111
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
112
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
113
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
114
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readNumber(raw, fallback, bounds) {
|
|
119
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var NumberValueReader = class {
|
|
123
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
124
|
+
read(owner, name, raw, fallback, bounds) {
|
|
125
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
126
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
127
|
+
this.#lastRejected.delete(name);
|
|
128
|
+
return resolved;
|
|
129
|
+
}
|
|
130
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
131
|
+
const literal = owner.element.getAttribute(attribute);
|
|
132
|
+
if (literal === null) {
|
|
133
|
+
this.#lastRejected.delete(name);
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
137
|
+
this.#lastRejected.set(name, literal);
|
|
138
|
+
console.warn(
|
|
139
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return resolved;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
102
146
|
var TimerRegistry = class {
|
|
103
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
104
147
|
ids = /* @__PURE__ */ new Set();
|
|
105
|
-
/**
|
|
106
|
-
* Cancels a single tracked timer.
|
|
107
|
-
*
|
|
108
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
109
|
-
* registry), so callers can clear defensively without guarding.
|
|
110
|
-
*/
|
|
111
148
|
clear(id) {
|
|
112
149
|
if (this.ids.delete(id)) {
|
|
113
150
|
this.cancel(id);
|
|
114
151
|
}
|
|
115
152
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
118
|
-
* to guarantee no timer outlives the element.
|
|
119
|
-
*/
|
|
120
153
|
clearAll() {
|
|
121
154
|
for (const id of this.ids) {
|
|
122
155
|
this.cancel(id);
|
|
123
156
|
}
|
|
124
157
|
this.ids.clear();
|
|
125
158
|
}
|
|
126
|
-
/** Number of timers currently tracked (pending). */
|
|
127
159
|
get size() {
|
|
128
160
|
return this.ids.size;
|
|
129
161
|
}
|
|
130
162
|
};
|
|
131
163
|
var SafeTimeout = class extends TimerRegistry {
|
|
132
|
-
/**
|
|
133
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
134
|
-
*
|
|
135
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
136
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
137
|
-
*/
|
|
138
164
|
set(callback, delay) {
|
|
139
165
|
const id = this.schedule(() => {
|
|
140
166
|
this.ids.delete(id);
|
|
@@ -151,7 +177,6 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
151
177
|
}
|
|
152
178
|
};
|
|
153
179
|
|
|
154
|
-
// src/controllers/relative_time_controller.ts
|
|
155
180
|
var YEAR_SCALE = { limit: Number.POSITIVE_INFINITY, unit: "year", ms: 315576e5 };
|
|
156
181
|
var UNITS = [
|
|
157
182
|
{ limit: 60, unit: "second", ms: 1e3 },
|
|
@@ -163,112 +188,105 @@ var UNITS = [
|
|
|
163
188
|
YEAR_SCALE
|
|
164
189
|
];
|
|
165
190
|
var RELATIVE_OPTIONS = { numeric: "auto" };
|
|
166
|
-
var
|
|
191
|
+
var TEXT_RECORD = "text";
|
|
192
|
+
var RelativeTimeController = class _RelativeTimeController extends Controller {
|
|
193
|
+
#numbers = new NumberValueReader();
|
|
167
194
|
static values = {
|
|
168
195
|
locale: { type: String, default: "" },
|
|
169
196
|
threshold: { type: Number, default: 0 },
|
|
170
197
|
tickInterval: { type: Number, default: 6e4 }
|
|
171
198
|
};
|
|
199
|
+
static valueConstraints = {
|
|
200
|
+
threshold: NUMBER_BOUNDS.nonNegative,
|
|
201
|
+
tickInterval: NUMBER_BOUNDS.timer
|
|
202
|
+
};
|
|
172
203
|
#timers = new SafeTimeout();
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
#resync = new MorphRenderWatcher(() => {
|
|
205
|
+
if (this.#valuesDirty) {
|
|
206
|
+
this.#valuesDirty = false;
|
|
207
|
+
this.#resyncToValues();
|
|
208
|
+
} else if (!Number.isNaN(this.#targetMs)) {
|
|
209
|
+
this.#applyAndComputeDelay();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
#valuesDirty = false;
|
|
177
213
|
#targetMs = Number.NaN;
|
|
178
|
-
/** The authored absolute text, restored when the threshold fallback kicks in. */
|
|
179
214
|
#absoluteText = "";
|
|
180
215
|
connect() {
|
|
181
|
-
this.#
|
|
182
|
-
this.#
|
|
216
|
+
this.#valuesDirty = false;
|
|
217
|
+
this.#resync.observe(this.element);
|
|
183
218
|
if (this.element.getAttribute("data-state") !== "relative") {
|
|
184
219
|
this.#absoluteText = (this.element.textContent ?? "").trim();
|
|
220
|
+
} else {
|
|
221
|
+
const recorded = this.#recordedText();
|
|
222
|
+
if (recorded !== null) this.#absoluteText = recorded;
|
|
185
223
|
}
|
|
186
224
|
this.#targetMs = Date.parse(this.element.getAttribute("datetime") ?? "");
|
|
187
225
|
if (Number.isNaN(this.#targetMs)) return;
|
|
188
226
|
this.#schedule();
|
|
189
227
|
}
|
|
190
228
|
disconnect() {
|
|
191
|
-
this.#resync.
|
|
192
|
-
this.#beforeCache.deactivate();
|
|
229
|
+
this.#resync.disconnect();
|
|
193
230
|
this.#timers.clearAll();
|
|
194
231
|
}
|
|
195
|
-
|
|
232
|
+
get #textRecord() {
|
|
233
|
+
return `data-${this.identifier}-${TEXT_RECORD}`;
|
|
234
|
+
}
|
|
235
|
+
#recordedText() {
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(this.element.getAttribute(this.#textRecord) ?? "null");
|
|
238
|
+
return typeof parsed === "string" ? parsed : null;
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
196
243
|
localeValueChanged() {
|
|
244
|
+
this.#valuesDirty = true;
|
|
197
245
|
this.#resync.schedule();
|
|
198
246
|
}
|
|
199
|
-
/** Repaints when application code (or a Turbo morph) changes `threshold` at runtime. */
|
|
200
247
|
thresholdValueChanged() {
|
|
248
|
+
this.#valuesDirty = true;
|
|
201
249
|
this.#resync.schedule();
|
|
202
250
|
}
|
|
203
|
-
/** Repaints when application code (or a Turbo morph) changes `tickInterval` at runtime. */
|
|
204
251
|
tickIntervalValueChanged() {
|
|
252
|
+
this.#valuesDirty = true;
|
|
205
253
|
this.#resync.schedule();
|
|
206
254
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Renders against the current Values and re-arms the poll from now.
|
|
209
|
-
*
|
|
210
|
-
* Render only: it emits no event, and clearing first keeps the single self-arming
|
|
211
|
-
* timer single — scheduling on top of a pending one would double the poll rate for
|
|
212
|
-
* the rest of the session. A stamp whose `datetime` never parsed has nothing to
|
|
213
|
-
* render, and one that already reached its terminal fallback simply renders it
|
|
214
|
-
* again and stops.
|
|
215
|
-
*/
|
|
216
255
|
#resyncToValues() {
|
|
217
256
|
if (Number.isNaN(this.#targetMs)) return;
|
|
218
257
|
this.#timers.clearAll();
|
|
219
258
|
this.#schedule();
|
|
220
259
|
}
|
|
221
|
-
/**
|
|
222
|
-
* Restores the authored absolute text and the pre-render state for the snapshot
|
|
223
|
-
* Turbo is about to take, leaving the live page's poll timer alone.
|
|
224
|
-
*
|
|
225
|
-
* With no authored text held there is nothing to restore, and `data-state` has to
|
|
226
|
-
* stay as it is: that marker is what tells the next `connect()` the visible text is
|
|
227
|
-
* a rendered relative form rather than an absolute fallback to hold on to.
|
|
228
|
-
*/
|
|
229
|
-
#rewindForCache() {
|
|
230
|
-
if (!this.#absoluteText) return;
|
|
231
|
-
this.element.textContent = this.#absoluteText;
|
|
232
|
-
this.element.removeAttribute("data-state");
|
|
233
|
-
}
|
|
234
|
-
/** Renders the current representation and reschedules unless polling can stop. */
|
|
235
260
|
#schedule() {
|
|
236
261
|
const nextDelay = this.#applyAndComputeDelay();
|
|
237
262
|
if (nextDelay !== null) {
|
|
238
263
|
this.#timers.set(() => this.#schedule(), nextDelay);
|
|
239
264
|
}
|
|
240
265
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Updates the visible text and returns the next poll delay (ms), or `null` when
|
|
243
|
-
* polling can stop: a *past* timestamp that fell back to the absolute text can
|
|
244
|
-
* never leave it again, and a locale the runtime rejects has nothing to render
|
|
245
|
-
* until that value is corrected.
|
|
246
|
-
*
|
|
247
|
-
* @stimeoRenderRoot
|
|
248
|
-
*/
|
|
249
266
|
#applyAndComputeDelay() {
|
|
250
267
|
const deltaMs = this.#targetMs - Date.now();
|
|
251
268
|
const absSeconds = Math.abs(deltaMs) / 1e3;
|
|
252
269
|
const scale = UNITS.find((u) => absSeconds < u.limit) ?? YEAR_SCALE;
|
|
253
270
|
const unitFloor = scale.unit === "second" || scale.unit === "minute" ? 6e4 : scale.ms;
|
|
254
|
-
const nextDelay = Math.max(this
|
|
255
|
-
if (this
|
|
271
|
+
const nextDelay = Math.max(this.#safeTickInterval, Math.min(unitFloor, 864e5));
|
|
272
|
+
if (this.#safeThreshold > 0 && absSeconds >= this.#safeThreshold && this.#absoluteText) {
|
|
256
273
|
this.element.textContent = this.#absoluteText;
|
|
257
274
|
this.element.setAttribute("data-state", "absolute");
|
|
275
|
+
this.element.removeAttribute(this.#textRecord);
|
|
258
276
|
if (deltaMs <= 0) return null;
|
|
259
|
-
return Math.min(nextDelay, deltaMs - this
|
|
277
|
+
return Math.max(1, Math.min(nextDelay, deltaMs - this.#safeThreshold * 1e3 + 1));
|
|
260
278
|
}
|
|
261
279
|
const formatter = this.#formatter;
|
|
262
280
|
if (formatter === null) return null;
|
|
263
281
|
const value = Math.round(deltaMs / scale.ms);
|
|
264
282
|
this.element.textContent = formatter.format(value, scale.unit);
|
|
265
283
|
this.element.setAttribute("data-state", "relative");
|
|
284
|
+
const record = JSON.stringify(this.#absoluteText);
|
|
285
|
+
if (this.element.getAttribute(this.#textRecord) !== record) {
|
|
286
|
+
this.element.setAttribute(this.#textRecord, record);
|
|
287
|
+
}
|
|
266
288
|
return nextDelay;
|
|
267
289
|
}
|
|
268
|
-
/**
|
|
269
|
-
* A `RelativeTimeFormat` for the resolved locale (`numeric: "auto"`), or `null`
|
|
270
|
-
* when the runtime rejects that locale.
|
|
271
|
-
*/
|
|
272
290
|
get #formatter() {
|
|
273
291
|
return intlFormatter(
|
|
274
292
|
Intl.RelativeTimeFormat,
|
|
@@ -276,8 +294,25 @@ var RelativeTimeController = class extends Controller {
|
|
|
276
294
|
RELATIVE_OPTIONS
|
|
277
295
|
);
|
|
278
296
|
}
|
|
297
|
+
get #safeThreshold() {
|
|
298
|
+
return this.#numbers.read(
|
|
299
|
+
this,
|
|
300
|
+
"threshold",
|
|
301
|
+
this.thresholdValue,
|
|
302
|
+
_RelativeTimeController.values.threshold.default,
|
|
303
|
+
_RelativeTimeController.valueConstraints.threshold
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
get #safeTickInterval() {
|
|
307
|
+
return this.#numbers.read(
|
|
308
|
+
this,
|
|
309
|
+
"tickInterval",
|
|
310
|
+
this.tickIntervalValue,
|
|
311
|
+
_RelativeTimeController.values.tickInterval.default,
|
|
312
|
+
_RelativeTimeController.valueConstraints.tickInterval
|
|
313
|
+
);
|
|
314
|
+
}
|
|
279
315
|
};
|
|
280
316
|
|
|
281
317
|
export { RelativeTimeController };
|
|
282
|
-
//# sourceMappingURL=relative_time_controller.js.map
|
|
283
318
|
//# sourceMappingURL=relative_time_controller.js.map
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/reset_before_cache_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/declared_value.ts
|
|
6
3
|
function parseDeclared(raw, parse, fallback) {
|
|
7
4
|
try {
|
|
8
5
|
return parse(raw);
|
|
@@ -22,7 +19,94 @@ function validSelector(element, raw, fallback) {
|
|
|
22
19
|
);
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
var ListenerSet = class {
|
|
23
|
+
#abort = new AbortController();
|
|
24
|
+
add(target, type, handler, options) {
|
|
25
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
26
|
+
}
|
|
27
|
+
dispose() {
|
|
28
|
+
this.#abort.abort();
|
|
29
|
+
this.#abort = new AbortController();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var MicrotaskCoalescer = class {
|
|
34
|
+
#run;
|
|
35
|
+
#queued = false;
|
|
36
|
+
#active = false;
|
|
37
|
+
#generation = 0;
|
|
38
|
+
constructor(run) {
|
|
39
|
+
this.#run = run;
|
|
40
|
+
}
|
|
41
|
+
activate() {
|
|
42
|
+
this.#active = true;
|
|
43
|
+
}
|
|
44
|
+
cancel() {
|
|
45
|
+
this.#active = false;
|
|
46
|
+
this.#queued = false;
|
|
47
|
+
this.#generation += 1;
|
|
48
|
+
}
|
|
49
|
+
schedule() {
|
|
50
|
+
if (!this.#active || this.#queued) return;
|
|
51
|
+
this.#queued = true;
|
|
52
|
+
const generation = this.#generation;
|
|
53
|
+
queueMicrotask(() => {
|
|
54
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
55
|
+
this.#queued = false;
|
|
56
|
+
this.#run();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
var MorphRenderWatcher = class {
|
|
62
|
+
#listeners = new ListenerSet();
|
|
63
|
+
#pass;
|
|
64
|
+
#element = null;
|
|
65
|
+
#onMorph = () => this.#pass.schedule();
|
|
66
|
+
constructor(run) {
|
|
67
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
68
|
+
}
|
|
69
|
+
observe(element) {
|
|
70
|
+
if (this.#element === element) return;
|
|
71
|
+
this.disconnect();
|
|
72
|
+
this.#element = element;
|
|
73
|
+
this.#pass.activate();
|
|
74
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
75
|
+
}
|
|
76
|
+
schedule() {
|
|
77
|
+
this.#pass.schedule();
|
|
78
|
+
}
|
|
79
|
+
disconnect() {
|
|
80
|
+
this.#listeners.dispose();
|
|
81
|
+
this.#pass.cancel();
|
|
82
|
+
this.#element = null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
var LivedMark = class {
|
|
87
|
+
#name;
|
|
88
|
+
#lived = false;
|
|
89
|
+
#element = null;
|
|
90
|
+
#morph = new MorphRenderWatcher(() => this.#write());
|
|
91
|
+
constructor(identifier) {
|
|
92
|
+
this.#name = `data-${identifier}-lived`;
|
|
93
|
+
}
|
|
94
|
+
connect(element) {
|
|
95
|
+
const connection = this.#lived ? "reconnect" : element.hasAttribute(this.#name) ? "restored" : "fresh";
|
|
96
|
+
this.#lived = true;
|
|
97
|
+
this.#element = element;
|
|
98
|
+
this.#write();
|
|
99
|
+
this.#morph.observe(element);
|
|
100
|
+
return connection;
|
|
101
|
+
}
|
|
102
|
+
disconnect() {
|
|
103
|
+
this.#morph.disconnect();
|
|
104
|
+
}
|
|
105
|
+
#write() {
|
|
106
|
+
this.#element?.setAttribute(this.#name, "");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
26
110
|
var STATELESS_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden", "submit", "reset", "button", "image"]);
|
|
27
111
|
function restoreField(element) {
|
|
28
112
|
if (element instanceof HTMLTextAreaElement) {
|
|
@@ -43,48 +127,25 @@ function restoreField(element) {
|
|
|
43
127
|
}
|
|
44
128
|
}
|
|
45
129
|
}
|
|
46
|
-
var
|
|
130
|
+
var ResetOnRestoreController = class extends Controller {
|
|
47
131
|
static values = {
|
|
48
|
-
scope: { type: String, default: "" }
|
|
49
|
-
dispatchReset: { type: Boolean, default: true }
|
|
132
|
+
scope: { type: String, default: "" }
|
|
50
133
|
};
|
|
51
134
|
static actions = ["reset"];
|
|
52
|
-
static events = ["reset"
|
|
53
|
-
/** The `scope` declaration after validation; empty when it cannot be parsed. */
|
|
135
|
+
static events = ["reset"];
|
|
54
136
|
#scopeSelector = "";
|
|
55
|
-
|
|
56
|
-
#onBeforeCache = () => this.reset();
|
|
57
|
-
/**
|
|
58
|
-
* Validates the scope declaration once, keeping only a selector the engine can
|
|
59
|
-
* read.
|
|
60
|
-
*
|
|
61
|
-
* A selector reads back as an ordinary string, so a malformed one survives
|
|
62
|
-
* until it is handed to the DOM — and this part runs from a single listener
|
|
63
|
-
* whose whole job is to keep a cached page from freezing. Falling back to the
|
|
64
|
-
* default keeps that job running with a visible, findable result instead of
|
|
65
|
-
* silently taking the sweep down.
|
|
66
|
-
*/
|
|
137
|
+
#lived = new LivedMark(this.identifier);
|
|
67
138
|
scopeValueChanged() {
|
|
68
139
|
this.#scopeSelector = validSelector(this.element, this.scopeValue, "");
|
|
69
140
|
}
|
|
70
141
|
connect() {
|
|
71
|
-
|
|
142
|
+
if (this.#lived.connect(this.element) === "restored") this.reset();
|
|
72
143
|
}
|
|
73
144
|
disconnect() {
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Resets transient UI within scope to its initial state. Asks controllers to
|
|
78
|
-
* close (via `request`) first, then applies the declarative `data-reset-*` cleanup,
|
|
79
|
-
* and finally emits `reset`. Safe to call any number of times (idempotent).
|
|
80
|
-
*
|
|
81
|
-
* `dispatchReset` decides only whether the `request` ask goes out; the cleanup
|
|
82
|
-
* and the closing `reset` run either way. Both the listener and this action
|
|
83
|
-
* reach the same sweep, so `reset` is emitted for a manual call too.
|
|
84
|
-
*/
|
|
145
|
+
this.#lived.disconnect();
|
|
146
|
+
}
|
|
85
147
|
reset() {
|
|
86
148
|
const root = this.#scopeRoot();
|
|
87
|
-
if (this.dispatchResetValue) this.dispatch("request");
|
|
88
149
|
for (const element of root.querySelectorAll("[data-reset-attr]")) {
|
|
89
150
|
for (const name of (element.getAttribute("data-reset-attr") ?? "").split(/\s+/)) {
|
|
90
151
|
if (name) element.removeAttribute(name);
|
|
@@ -109,13 +170,11 @@ var ResetBeforeCacheController = class extends Controller {
|
|
|
109
170
|
}
|
|
110
171
|
this.dispatch("reset");
|
|
111
172
|
}
|
|
112
|
-
/** The scan root: a `scope` descendant when set, else the controller element. */
|
|
113
173
|
#scopeRoot() {
|
|
114
174
|
if (!this.#scopeSelector) return this.element;
|
|
115
175
|
return this.element.querySelector(this.#scopeSelector) ?? this.element;
|
|
116
176
|
}
|
|
117
177
|
};
|
|
118
178
|
|
|
119
|
-
export {
|
|
120
|
-
//# sourceMappingURL=
|
|
121
|
-
//# sourceMappingURL=reset_before_cache_controller.js.map
|
|
179
|
+
export { ResetOnRestoreController };
|
|
180
|
+
//# sourceMappingURL=reset_on_restore_controller.js.map
|