stimeo-ui 0.15.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 +209 -0
- data/README.md +128 -1
- data/dist/cable/index.js +392 -417
- data/dist/controllers/accordion_controller.js +228 -28
- data/dist/controllers/alert_dialog_controller.js +862 -221
- data/dist/controllers/announcer_controller.js +135 -191
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +295 -110
- data/dist/controllers/avatar_controller.js +172 -106
- data/dist/controllers/breadcrumb_controller.js +0 -129
- data/dist/controllers/bulk_select_controller.js +264 -53
- data/dist/controllers/calendar_controller.js +346 -180
- data/dist/controllers/carousel_controller.js +587 -300
- data/dist/controllers/character_counter_controller.js +271 -143
- data/dist/controllers/checkbox_controller.js +77 -75
- data/dist/controllers/clipboard_controller.js +187 -108
- data/dist/controllers/collapsible_controller.js +374 -108
- data/dist/controllers/color_picker_controller.js +183 -127
- data/dist/controllers/combobox_controller.js +426 -109
- data/dist/controllers/command_palette_controller.js +994 -350
- data/dist/controllers/conditional_fields_controller.js +212 -125
- data/dist/controllers/confirm_controller.js +939 -245
- data/dist/controllers/context_menu_controller.js +275 -127
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +235 -97
- data/dist/controllers/currency_input_controller.js +241 -118
- data/dist/controllers/data_grid_controller.js +229 -135
- data/dist/controllers/date_range_picker_controller.js +359 -165
- data/dist/controllers/dialog_controller.js +887 -225
- data/dist/controllers/direct_upload_controller.js +80 -142
- data/dist/controllers/dirty_form_controller.js +26 -47
- data/dist/controllers/dismissible_controller.js +175 -20
- data/dist/controllers/drawer_controller.js +963 -336
- data/dist/controllers/dropdown_controller.js +342 -85
- data/dist/controllers/editable_controller.js +182 -58
- data/dist/controllers/empty_state_controller.js +67 -53
- data/dist/controllers/file_dropzone_controller.js +426 -229
- data/dist/controllers/filter_controller.js +78 -36
- data/dist/controllers/flash_controller.js +328 -211
- data/dist/controllers/focus_controller.js +654 -239
- data/dist/controllers/form_field_controller.js +153 -136
- data/dist/controllers/form_validation_controller.js +29 -96
- data/dist/controllers/frame_loading_controller.js +263 -198
- data/dist/controllers/highlight_controller.js +119 -70
- data/dist/controllers/hover_card_controller.js +335 -118
- data/dist/controllers/idle_controller.js +316 -65
- data/dist/controllers/input_mask_controller.js +116 -61
- data/dist/controllers/intersection_controller.js +147 -103
- data/dist/controllers/lazy_frame_controller.js +9 -56
- data/dist/controllers/listbox_controller.js +413 -153
- data/dist/controllers/local_time_controller.js +54 -64
- data/dist/controllers/masonry_controller.js +70 -95
- data/dist/controllers/menu_controller.js +254 -167
- data/dist/controllers/menubar_controller.js +206 -331
- data/dist/controllers/meter_controller.js +159 -71
- data/dist/controllers/multi_select_controller.js +797 -388
- data/dist/controllers/navigation_menu_controller.js +156 -231
- data/dist/controllers/nested_form_controller.js +373 -174
- data/dist/controllers/network_status_controller.js +241 -45
- data/dist/controllers/number_input_controller.js +387 -227
- data/dist/controllers/optimistic_controller.js +28 -75
- data/dist/controllers/otp_controller.js +344 -196
- data/dist/controllers/overflow_indicator_controller.js +167 -93
- data/dist/controllers/overflow_menu_controller.js +408 -277
- data/dist/controllers/pagination_controller.js +342 -120
- data/dist/controllers/password_reveal_controller.js +424 -99
- data/dist/controllers/password_strength_controller.js +99 -153
- data/dist/controllers/persist_controller.js +252 -96
- data/dist/controllers/pointer_drag_controller.js +263 -164
- data/dist/controllers/popover_controller.js +267 -94
- data/dist/controllers/portal_controller.js +209 -72
- data/dist/controllers/preview_guard_controller.js +243 -112
- data/dist/controllers/progress_controller.js +132 -59
- data/dist/controllers/radio_group_controller.js +138 -133
- data/dist/controllers/range_slider_controller.js +245 -112
- data/dist/controllers/rating_controller.js +429 -194
- data/dist/controllers/read_more_controller.js +185 -47
- 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 +294 -118
- data/dist/controllers/roving_controller.js +39 -84
- data/dist/controllers/scroll_area_controller.js +428 -221
- data/dist/controllers/scroll_restore_controller.js +0 -53
- data/dist/controllers/scroll_visibility_controller.js +395 -146
- data/dist/controllers/scrollspy_controller.js +181 -236
- data/dist/controllers/separator_controller.js +288 -132
- data/dist/controllers/sidebar_controller.js +1071 -375
- data/dist/controllers/skeleton_controller.js +248 -92
- data/dist/controllers/slider_controller.js +211 -96
- data/dist/controllers/smart_sticky_header_controller.js +163 -54
- data/dist/controllers/sortable_controller.js +13 -110
- data/dist/controllers/spinner_controller.js +277 -161
- data/dist/controllers/step_indicator_controller.js +107 -53
- data/dist/controllers/stepper_controller.js +171 -48
- data/dist/controllers/stick_to_bottom_controller.js +310 -116
- data/dist/controllers/sticky_observer_controller.js +214 -49
- data/dist/controllers/submit_once_controller.js +294 -192
- data/dist/controllers/switch_controller.js +107 -43
- data/dist/controllers/tabs_controller.js +50 -25
- data/dist/controllers/tags_input_controller.js +290 -149
- data/dist/controllers/textarea_autosize_controller.js +71 -58
- data/dist/controllers/theme_controller.js +148 -80
- data/dist/controllers/time_picker_controller.js +162 -81
- data/dist/controllers/toast_controller.js +365 -193
- data/dist/controllers/toggle_group_controller.js +194 -99
- data/dist/controllers/toolbar_controller.js +92 -90
- data/dist/controllers/tooltip_controller.js +185 -125
- data/dist/controllers/transition_controller.js +123 -115
- data/dist/controllers/tree_view_controller.js +248 -290
- data/dist/index.js +8649 -8572
- data/dist/positioning/index.js +148 -45
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,44 +1,82 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var ListenerSet = class {
|
|
4
|
+
#abort = new AbortController();
|
|
5
|
+
add(target, type, handler, options) {
|
|
6
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
7
|
+
}
|
|
8
|
+
dispose() {
|
|
9
|
+
this.#abort.abort();
|
|
10
|
+
this.#abort = new AbortController();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
15
|
+
|
|
16
|
+
var NUMBER_BOUNDS = {
|
|
17
|
+
nonNegative: { finite: true, min: 0 },
|
|
18
|
+
positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS }
|
|
19
|
+
};
|
|
20
|
+
function matchesNumberBounds(value, bounds) {
|
|
21
|
+
if (!Number.isFinite(value)) {
|
|
22
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
23
|
+
if (direction === null) return false;
|
|
24
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
25
|
+
}
|
|
26
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
27
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
28
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
29
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
30
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readNumber(raw, fallback, bounds) {
|
|
35
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var NumberValueReader = class {
|
|
39
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
40
|
+
read(owner, name, raw, fallback, bounds) {
|
|
41
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
42
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
43
|
+
this.#lastRejected.delete(name);
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
47
|
+
const literal = owner.element.getAttribute(attribute);
|
|
48
|
+
if (literal === null) {
|
|
49
|
+
this.#lastRejected.delete(name);
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
53
|
+
this.#lastRejected.set(name, literal);
|
|
54
|
+
console.warn(
|
|
55
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return resolved;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
4
61
|
|
|
5
|
-
// src/utils/safe_timeout.ts
|
|
6
62
|
var TimerRegistry = class {
|
|
7
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
8
63
|
ids = /* @__PURE__ */ new Set();
|
|
9
|
-
/**
|
|
10
|
-
* Cancels a single tracked timer.
|
|
11
|
-
*
|
|
12
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
13
|
-
* registry), so callers can clear defensively without guarding.
|
|
14
|
-
*/
|
|
15
64
|
clear(id) {
|
|
16
65
|
if (this.ids.delete(id)) {
|
|
17
66
|
this.cancel(id);
|
|
18
67
|
}
|
|
19
68
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
22
|
-
* to guarantee no timer outlives the element.
|
|
23
|
-
*/
|
|
24
69
|
clearAll() {
|
|
25
70
|
for (const id of this.ids) {
|
|
26
71
|
this.cancel(id);
|
|
27
72
|
}
|
|
28
73
|
this.ids.clear();
|
|
29
74
|
}
|
|
30
|
-
/** Number of timers currently tracked (pending). */
|
|
31
75
|
get size() {
|
|
32
76
|
return this.ids.size;
|
|
33
77
|
}
|
|
34
78
|
};
|
|
35
79
|
var SafeTimeout = class extends TimerRegistry {
|
|
36
|
-
/**
|
|
37
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
38
|
-
*
|
|
39
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
40
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
41
|
-
*/
|
|
42
80
|
set(callback, delay) {
|
|
43
81
|
const id = this.schedule(() => {
|
|
44
82
|
this.ids.delete(id);
|
|
@@ -55,7 +93,161 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
55
93
|
}
|
|
56
94
|
};
|
|
57
95
|
|
|
58
|
-
|
|
96
|
+
function sharedRegistry(key, create) {
|
|
97
|
+
const symbol = Symbol.for(key);
|
|
98
|
+
const scope = globalThis;
|
|
99
|
+
const existing = scope[symbol];
|
|
100
|
+
if (existing !== void 0) return existing;
|
|
101
|
+
const registry = create();
|
|
102
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
103
|
+
return registry;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var StateRegions = class _StateRegions {
|
|
107
|
+
static #registry = sharedRegistry(
|
|
108
|
+
"stimeo-ui.state-regions.registry.v1",
|
|
109
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
110
|
+
);
|
|
111
|
+
#whenTrue;
|
|
112
|
+
#whenFalse;
|
|
113
|
+
#record;
|
|
114
|
+
constructor(options, owner) {
|
|
115
|
+
this.#whenTrue = options.whenTrue;
|
|
116
|
+
this.#whenFalse = options.whenFalse ?? null;
|
|
117
|
+
this.#record = `data-${owner}-hidden-region`;
|
|
118
|
+
_StateRegions.#registry.names.add(this.#record);
|
|
119
|
+
}
|
|
120
|
+
reflect(host, isTrue) {
|
|
121
|
+
const shown = this.#inside(host, this.#whenTrue());
|
|
122
|
+
if (!this.#whenFalse) {
|
|
123
|
+
for (const region of shown) this.#write(region, !isTrue);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const hiddenSide = this.#inside(host, this.#whenFalse());
|
|
127
|
+
if (shown.length === 0 || hiddenSide.length === 0) {
|
|
128
|
+
for (const region of shown) this.#give(region);
|
|
129
|
+
for (const region of hiddenSide) this.#give(region);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const region of shown) this.#write(region, !isTrue);
|
|
133
|
+
for (const region of hiddenSide) this.#write(region, isTrue);
|
|
134
|
+
}
|
|
135
|
+
release(host) {
|
|
136
|
+
for (const region of [host, ...host.querySelectorAll("*")]) {
|
|
137
|
+
if (!(region instanceof HTMLElement)) continue;
|
|
138
|
+
const stack = this.#held(region);
|
|
139
|
+
const hold = stack && this.#take(stack);
|
|
140
|
+
if (!stack || !hold) continue;
|
|
141
|
+
const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
|
|
142
|
+
const index = stack.indexOf(hold);
|
|
143
|
+
stack.splice(index, 1);
|
|
144
|
+
const above = stack[index];
|
|
145
|
+
if (above) above.linked &&= hold.linked;
|
|
146
|
+
if (owns) {
|
|
147
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
148
|
+
if (below) {
|
|
149
|
+
if (region.hidden !== below.written) region.hidden = below.written;
|
|
150
|
+
} else if (region.getAttribute("hidden") !== hold.authored) {
|
|
151
|
+
if (hold.authored === null) region.removeAttribute("hidden");
|
|
152
|
+
else region.setAttribute("hidden", hold.authored);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.#mark(region, stack);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
#write(region, hidden) {
|
|
159
|
+
const stack = this.#stack(region);
|
|
160
|
+
const hold = this.#take(stack) ?? this.#acquire(region, stack);
|
|
161
|
+
const shows = !this.#covered(region, stack, hold);
|
|
162
|
+
hold.written = hidden;
|
|
163
|
+
if (shows && region.hidden !== hidden) region.hidden = hidden;
|
|
164
|
+
this.#mark(region, stack);
|
|
165
|
+
}
|
|
166
|
+
#give(region) {
|
|
167
|
+
const stack = this.#held(region);
|
|
168
|
+
if (stack && this.#take(stack)?.written) this.#write(region, false);
|
|
169
|
+
}
|
|
170
|
+
#held(region) {
|
|
171
|
+
return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
|
|
172
|
+
}
|
|
173
|
+
#stack(region) {
|
|
174
|
+
const stacks = _StateRegions.#registry.stacks;
|
|
175
|
+
const stack = stacks.get(region) ?? this.#seed(region);
|
|
176
|
+
stacks.set(region, stack);
|
|
177
|
+
return stack;
|
|
178
|
+
}
|
|
179
|
+
#seed(region) {
|
|
180
|
+
const found = [];
|
|
181
|
+
for (const record of _StateRegions.#registry.names) {
|
|
182
|
+
const read = this.#recorded(region, record);
|
|
183
|
+
if (read) found.push({ ...read, record });
|
|
184
|
+
}
|
|
185
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
186
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
|
|
187
|
+
instance: null,
|
|
188
|
+
record,
|
|
189
|
+
authored,
|
|
190
|
+
written,
|
|
191
|
+
linked: linked && index > 0
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
#take(stack) {
|
|
195
|
+
const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
|
|
196
|
+
if (hold) hold.instance = this;
|
|
197
|
+
return hold;
|
|
198
|
+
}
|
|
199
|
+
#covered(region, stack, hold) {
|
|
200
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
|
|
201
|
+
}
|
|
202
|
+
#acquire(region, stack) {
|
|
203
|
+
const top = stack.at(-1);
|
|
204
|
+
const over = top && region.hidden === top.written ? top : void 0;
|
|
205
|
+
const hold = {
|
|
206
|
+
instance: this,
|
|
207
|
+
record: this.#record,
|
|
208
|
+
authored: over ? over.authored : region.getAttribute("hidden"),
|
|
209
|
+
written: false,
|
|
210
|
+
linked: over !== void 0
|
|
211
|
+
};
|
|
212
|
+
stack.push(hold);
|
|
213
|
+
return hold;
|
|
214
|
+
}
|
|
215
|
+
#mark(region, stack) {
|
|
216
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
217
|
+
stack.forEach((hold, index) => {
|
|
218
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
219
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
220
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
221
|
+
records.set(
|
|
222
|
+
hold.record,
|
|
223
|
+
region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
for (const [name, recorded] of records) {
|
|
227
|
+
if (region.getAttribute(name) === recorded) continue;
|
|
228
|
+
if (recorded === null) region.removeAttribute(name);
|
|
229
|
+
else region.setAttribute(name, recorded);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
#recorded(region, name) {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(region.getAttribute(name) ?? "null");
|
|
235
|
+
if (!Array.isArray(parsed)) return null;
|
|
236
|
+
const [authored, written, ...rest] = parsed;
|
|
237
|
+
if (authored !== null && typeof authored !== "string") return null;
|
|
238
|
+
const linked = rest[0] !== false;
|
|
239
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
240
|
+
if (!beneath.every((under) => typeof under === "string")) return null;
|
|
241
|
+
return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
#inside(host, regions) {
|
|
247
|
+
return regions.filter((region) => host.contains(region));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
59
251
|
function parseStringList(raw, fallback = []) {
|
|
60
252
|
const text = raw.trim();
|
|
61
253
|
if (text.length === 0) return [...fallback];
|
|
@@ -69,7 +261,6 @@ function parseStringList(raw, fallback = []) {
|
|
|
69
261
|
return parsed.filter((entry) => typeof entry === "string");
|
|
70
262
|
}
|
|
71
263
|
|
|
72
|
-
// src/controllers/idle_controller.ts
|
|
73
264
|
var DEFAULT_ACTIVITY_EVENTS = [
|
|
74
265
|
"mousemove",
|
|
75
266
|
"mousedown",
|
|
@@ -78,33 +269,38 @@ var DEFAULT_ACTIVITY_EVENTS = [
|
|
|
78
269
|
"touchstart",
|
|
79
270
|
"scroll"
|
|
80
271
|
];
|
|
81
|
-
var IdleController = class extends Controller {
|
|
272
|
+
var IdleController = class _IdleController extends Controller {
|
|
273
|
+
#numbers = new NumberValueReader();
|
|
82
274
|
static values = {
|
|
83
275
|
timeout: { type: Number, default: 9e5 },
|
|
84
276
|
promptBefore: { type: Number, default: 0 },
|
|
85
|
-
// A JSON list read through `parseStringList` rather than Stimulus's `Array`
|
|
86
|
-
// type: that reader throws out of the value observer before any callback
|
|
87
|
-
// runs, so one malformed attribute would stop the detector connecting.
|
|
88
277
|
events: { type: String, default: "" }
|
|
89
278
|
};
|
|
279
|
+
static valueConstraints = {
|
|
280
|
+
timeout: NUMBER_BOUNDS.positiveTimer,
|
|
281
|
+
promptBefore: NUMBER_BOUNDS.nonNegative
|
|
282
|
+
};
|
|
283
|
+
static targets = ["prompt", "idle"];
|
|
90
284
|
static events = ["prompt", "idle", "active"];
|
|
285
|
+
#promptRegions = new StateRegions(
|
|
286
|
+
{ whenTrue: () => this.promptTargets },
|
|
287
|
+
this.identifier
|
|
288
|
+
);
|
|
289
|
+
#idleRegions = new StateRegions({ whenTrue: () => this.idleTargets }, this.identifier);
|
|
91
290
|
#timeouts = new SafeTimeout();
|
|
92
291
|
#idle = false;
|
|
93
292
|
#prompted = false;
|
|
94
|
-
/** Timestamp of the last activity; the timers self-reschedule against it. */
|
|
95
293
|
#lastActivity = 0;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* the Value in place, and the removal must match the registration, not the Value).
|
|
100
|
-
*/
|
|
294
|
+
#connected = false;
|
|
295
|
+
#activityListeners = new ListenerSet();
|
|
296
|
+
#visibilityListeners = new ListenerSet();
|
|
101
297
|
#boundEvents = [];
|
|
102
298
|
#onActivity = () => {
|
|
103
299
|
this.#lastActivity = Date.now();
|
|
104
300
|
if (this.#idle || this.#prompted) {
|
|
105
301
|
this.#idle = false;
|
|
106
302
|
this.#prompted = false;
|
|
107
|
-
this
|
|
303
|
+
this.#reflect();
|
|
108
304
|
this.dispatch("active", { detail: {} });
|
|
109
305
|
this.#arm();
|
|
110
306
|
}
|
|
@@ -113,62 +309,117 @@ var IdleController = class extends Controller {
|
|
|
113
309
|
if (document.visibilityState === "visible") this.#onActivity();
|
|
114
310
|
};
|
|
115
311
|
connect() {
|
|
312
|
+
this.#connected = true;
|
|
116
313
|
this.#idle = false;
|
|
117
314
|
this.#prompted = false;
|
|
118
|
-
this
|
|
119
|
-
this.#
|
|
120
|
-
|
|
121
|
-
document.addEventListener(type, this.#onActivity, { passive: true, capture: true });
|
|
122
|
-
}
|
|
123
|
-
document.addEventListener("visibilitychange", this.#onVisibility);
|
|
315
|
+
this.#reflect();
|
|
316
|
+
this.#bindActivity(parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS));
|
|
317
|
+
this.#visibilityListeners.add(document, "visibilitychange", this.#onVisibility);
|
|
124
318
|
this.#arm();
|
|
125
319
|
}
|
|
126
320
|
disconnect() {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.#boundEvents = [];
|
|
131
|
-
document.removeEventListener("visibilitychange", this.#onVisibility);
|
|
321
|
+
this.#connected = false;
|
|
322
|
+
this.#activityListeners.dispose();
|
|
323
|
+
this.#visibilityListeners.dispose();
|
|
132
324
|
this.#timeouts.clearAll();
|
|
133
325
|
}
|
|
134
|
-
|
|
326
|
+
eventsValueChanged() {
|
|
327
|
+
if (!this.#connected) return;
|
|
328
|
+
const types = parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS);
|
|
329
|
+
if (types.length === this.#boundEvents.length && types.every((type, index) => type === this.#boundEvents[index]))
|
|
330
|
+
return;
|
|
331
|
+
this.#bindActivity(types);
|
|
332
|
+
}
|
|
333
|
+
timeoutValueChanged() {
|
|
334
|
+
if (this.#connected) this.#schedule();
|
|
335
|
+
}
|
|
336
|
+
promptBeforeValueChanged() {
|
|
337
|
+
if (this.#connected) this.#schedule();
|
|
338
|
+
}
|
|
339
|
+
#bindActivity(types) {
|
|
340
|
+
this.#activityListeners.dispose();
|
|
341
|
+
for (const type of types) {
|
|
342
|
+
this.#activityListeners.add(document, type, this.#onActivity, {
|
|
343
|
+
passive: true,
|
|
344
|
+
capture: true
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
this.#boundEvents = types;
|
|
348
|
+
}
|
|
135
349
|
#arm() {
|
|
136
|
-
this.#timeouts.clearAll();
|
|
137
350
|
this.#lastActivity = Date.now();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
351
|
+
this.#schedule();
|
|
352
|
+
}
|
|
353
|
+
#schedule() {
|
|
354
|
+
this.#timeouts.clearAll();
|
|
355
|
+
if (this.#idle) return;
|
|
356
|
+
const elapsed = Date.now() - this.#lastActivity;
|
|
357
|
+
const timeout = this.#safeTimeout;
|
|
358
|
+
const prompt = this.#safePromptBefore;
|
|
359
|
+
this.#timeouts.set(() => this.#checkIdle(), this.#delayFor(timeout - elapsed));
|
|
360
|
+
if (this.#prompted || prompt <= 0 || prompt >= timeout) return;
|
|
361
|
+
const left = Math.min(prompt, timeout - elapsed);
|
|
362
|
+
this.#timeouts.set(() => this.#checkPrompt(left), this.#delayFor(timeout - prompt - elapsed));
|
|
363
|
+
}
|
|
364
|
+
#delayFor(remaining) {
|
|
365
|
+
return Math.min(Math.max(remaining, 0), MAX_TIMER_DELAY_MS);
|
|
143
366
|
}
|
|
144
|
-
/**
|
|
145
|
-
* Idle-timer callback: go idle only if there has genuinely been no activity for
|
|
146
|
-
* `timeout`; otherwise reschedule for the remaining time. This lets activity events
|
|
147
|
-
* stay O(1) (a timestamp write) while the deadline still tracks the last activity.
|
|
148
|
-
*/
|
|
149
367
|
#checkIdle() {
|
|
150
|
-
const remaining = this
|
|
368
|
+
const remaining = this.#safeTimeout - (Date.now() - this.#lastActivity);
|
|
151
369
|
if (remaining > 0) {
|
|
152
|
-
this.#timeouts.set(() => this.#checkIdle(), remaining);
|
|
370
|
+
this.#timeouts.set(() => this.#checkIdle(), Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
153
371
|
return;
|
|
154
372
|
}
|
|
155
373
|
this.#idle = true;
|
|
156
374
|
this.#prompted = false;
|
|
157
|
-
this
|
|
375
|
+
this.#reflect();
|
|
158
376
|
this.dispatch("idle", { detail: {} });
|
|
159
377
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const remaining = this
|
|
378
|
+
#checkPrompt(left = this.#safePromptBefore) {
|
|
379
|
+
if (this.#idle) return;
|
|
380
|
+
const remaining = this.#safeTimeout - this.#safePromptBefore - (Date.now() - this.#lastActivity);
|
|
163
381
|
if (remaining > 0) {
|
|
164
|
-
this.#timeouts.set(() => this.#checkPrompt(), remaining);
|
|
382
|
+
this.#timeouts.set(() => this.#checkPrompt(), Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
165
383
|
return;
|
|
166
384
|
}
|
|
167
385
|
this.#prompted = true;
|
|
168
|
-
this
|
|
386
|
+
this.#reflect();
|
|
387
|
+
this.dispatch("prompt", { detail: { remaining: left } });
|
|
388
|
+
}
|
|
389
|
+
promptTargetConnected() {
|
|
390
|
+
this.#promptRegions.reflect(this.element, this.#prompted);
|
|
391
|
+
}
|
|
392
|
+
idleTargetConnected() {
|
|
393
|
+
this.#idleRegions.reflect(this.element, this.#idle);
|
|
394
|
+
}
|
|
395
|
+
#reflect() {
|
|
396
|
+
const { element } = this;
|
|
397
|
+
if (this.#prompted) element.setAttribute("data-prompt", "true");
|
|
398
|
+
else element.removeAttribute("data-prompt");
|
|
399
|
+
if (this.#idle) element.setAttribute("data-idle", "true");
|
|
400
|
+
else element.removeAttribute("data-idle");
|
|
401
|
+
this.#promptRegions.reflect(element, this.#prompted);
|
|
402
|
+
this.#idleRegions.reflect(element, this.#idle);
|
|
403
|
+
}
|
|
404
|
+
get #safeTimeout() {
|
|
405
|
+
return this.#numbers.read(
|
|
406
|
+
this,
|
|
407
|
+
"timeout",
|
|
408
|
+
this.timeoutValue,
|
|
409
|
+
_IdleController.values.timeout.default,
|
|
410
|
+
_IdleController.valueConstraints.timeout
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
get #safePromptBefore() {
|
|
414
|
+
return this.#numbers.read(
|
|
415
|
+
this,
|
|
416
|
+
"promptBefore",
|
|
417
|
+
this.promptBeforeValue,
|
|
418
|
+
_IdleController.values.promptBefore.default,
|
|
419
|
+
_IdleController.valueConstraints.promptBefore
|
|
420
|
+
);
|
|
169
421
|
}
|
|
170
422
|
};
|
|
171
423
|
|
|
172
424
|
export { IdleController };
|
|
173
|
-
//# sourceMappingURL=idle_controller.js.map
|
|
174
425
|
//# sourceMappingURL=idle_controller.js.map
|