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,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,16 +93,30 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
55
93
|
}
|
|
56
94
|
};
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
);
|
|
60
111
|
#whenTrue;
|
|
61
112
|
#whenFalse;
|
|
62
|
-
#
|
|
63
|
-
constructor(options) {
|
|
113
|
+
#record;
|
|
114
|
+
constructor(options, owner) {
|
|
64
115
|
this.#whenTrue = options.whenTrue;
|
|
65
116
|
this.#whenFalse = options.whenFalse ?? null;
|
|
117
|
+
this.#record = `data-${owner}-hidden-region`;
|
|
118
|
+
_StateRegions.#registry.names.add(this.#record);
|
|
66
119
|
}
|
|
67
|
-
/** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
|
|
68
120
|
reflect(host, isTrue) {
|
|
69
121
|
const shown = this.#inside(host, this.#whenTrue());
|
|
70
122
|
if (!this.#whenFalse) {
|
|
@@ -80,23 +132,122 @@ var StateRegions = class {
|
|
|
80
132
|
for (const region of shown) this.#write(region, !isTrue);
|
|
81
133
|
for (const region of hiddenSide) this.#write(region, isTrue);
|
|
82
134
|
}
|
|
83
|
-
|
|
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
|
+
}
|
|
84
158
|
#write(region, hidden) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
165
|
}
|
|
89
|
-
/** Returns a region this instance hid; one it never hid keeps what it carries. */
|
|
90
166
|
#give(region) {
|
|
91
|
-
|
|
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
|
+
}
|
|
92
245
|
}
|
|
93
|
-
/** The declared regions that sit within `host`, which may be the host itself. */
|
|
94
246
|
#inside(host, regions) {
|
|
95
247
|
return regions.filter((region) => host.contains(region));
|
|
96
248
|
}
|
|
97
249
|
};
|
|
98
250
|
|
|
99
|
-
// src/utils/string_list.ts
|
|
100
251
|
function parseStringList(raw, fallback = []) {
|
|
101
252
|
const text = raw.trim();
|
|
102
253
|
if (text.length === 0) return [...fallback];
|
|
@@ -110,7 +261,6 @@ function parseStringList(raw, fallback = []) {
|
|
|
110
261
|
return parsed.filter((entry) => typeof entry === "string");
|
|
111
262
|
}
|
|
112
263
|
|
|
113
|
-
// src/controllers/idle_controller.ts
|
|
114
264
|
var DEFAULT_ACTIVITY_EVENTS = [
|
|
115
265
|
"mousemove",
|
|
116
266
|
"mousedown",
|
|
@@ -119,31 +269,31 @@ var DEFAULT_ACTIVITY_EVENTS = [
|
|
|
119
269
|
"touchstart",
|
|
120
270
|
"scroll"
|
|
121
271
|
];
|
|
122
|
-
var IdleController = class extends Controller {
|
|
272
|
+
var IdleController = class _IdleController extends Controller {
|
|
273
|
+
#numbers = new NumberValueReader();
|
|
123
274
|
static values = {
|
|
124
275
|
timeout: { type: Number, default: 9e5 },
|
|
125
276
|
promptBefore: { type: Number, default: 0 },
|
|
126
|
-
// A JSON list read through `parseStringList` rather than Stimulus's `Array`
|
|
127
|
-
// type: that reader throws out of the value observer before any callback
|
|
128
|
-
// runs, so one malformed attribute would stop the detector connecting.
|
|
129
277
|
events: { type: String, default: "" }
|
|
130
278
|
};
|
|
279
|
+
static valueConstraints = {
|
|
280
|
+
timeout: NUMBER_BOUNDS.positiveTimer,
|
|
281
|
+
promptBefore: NUMBER_BOUNDS.nonNegative
|
|
282
|
+
};
|
|
131
283
|
static targets = ["prompt", "idle"];
|
|
132
284
|
static events = ["prompt", "idle", "active"];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
285
|
+
#promptRegions = new StateRegions(
|
|
286
|
+
{ whenTrue: () => this.promptTargets },
|
|
287
|
+
this.identifier
|
|
288
|
+
);
|
|
289
|
+
#idleRegions = new StateRegions({ whenTrue: () => this.idleTargets }, this.identifier);
|
|
137
290
|
#timeouts = new SafeTimeout();
|
|
138
291
|
#idle = false;
|
|
139
292
|
#prompted = false;
|
|
140
|
-
/** Timestamp of the last activity; the timers self-reschedule against it. */
|
|
141
293
|
#lastActivity = 0;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* the Value in place, and the removal must match the registration, not the Value).
|
|
146
|
-
*/
|
|
294
|
+
#connected = false;
|
|
295
|
+
#activityListeners = new ListenerSet();
|
|
296
|
+
#visibilityListeners = new ListenerSet();
|
|
147
297
|
#boundEvents = [];
|
|
148
298
|
#onActivity = () => {
|
|
149
299
|
this.#lastActivity = Date.now();
|
|
@@ -159,50 +309,65 @@ var IdleController = class extends Controller {
|
|
|
159
309
|
if (document.visibilityState === "visible") this.#onActivity();
|
|
160
310
|
};
|
|
161
311
|
connect() {
|
|
312
|
+
this.#connected = true;
|
|
162
313
|
this.#idle = false;
|
|
163
314
|
this.#prompted = false;
|
|
164
315
|
this.#reflect();
|
|
165
|
-
this.#
|
|
166
|
-
|
|
167
|
-
document.addEventListener(type, this.#onActivity, { passive: true, capture: true });
|
|
168
|
-
}
|
|
169
|
-
document.addEventListener("visibilitychange", this.#onVisibility);
|
|
316
|
+
this.#bindActivity(parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS));
|
|
317
|
+
this.#visibilityListeners.add(document, "visibilitychange", this.#onVisibility);
|
|
170
318
|
this.#arm();
|
|
171
319
|
}
|
|
172
320
|
disconnect() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.#boundEvents = [];
|
|
177
|
-
document.removeEventListener("visibilitychange", this.#onVisibility);
|
|
321
|
+
this.#connected = false;
|
|
322
|
+
this.#activityListeners.dispose();
|
|
323
|
+
this.#visibilityListeners.dispose();
|
|
178
324
|
this.#timeouts.clearAll();
|
|
179
325
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
}
|
|
185
349
|
#arm() {
|
|
186
|
-
this.#timeouts.clearAll();
|
|
187
350
|
this.#lastActivity = Date.now();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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);
|
|
366
|
+
}
|
|
202
367
|
#checkIdle() {
|
|
203
|
-
const remaining = this
|
|
368
|
+
const remaining = this.#safeTimeout - (Date.now() - this.#lastActivity);
|
|
204
369
|
if (remaining > 0) {
|
|
205
|
-
this.#timeouts.set(() => this.#checkIdle(), remaining);
|
|
370
|
+
this.#timeouts.set(() => this.#checkIdle(), Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
206
371
|
return;
|
|
207
372
|
}
|
|
208
373
|
this.#idle = true;
|
|
@@ -210,39 +375,23 @@ var IdleController = class extends Controller {
|
|
|
210
375
|
this.#reflect();
|
|
211
376
|
this.dispatch("idle", { detail: {} });
|
|
212
377
|
}
|
|
213
|
-
|
|
214
|
-
* Prompt-timer callback: warn at `promptBefore` before the idle deadline. A window
|
|
215
|
-
* narrowed while the cycle runs can push the warning onto the deadline or past it,
|
|
216
|
-
* and the phase it belongs to is the one before idle, so a lapsed cycle keeps the
|
|
217
|
-
* phase it reached. The next window opens with the cycle that activity arms.
|
|
218
|
-
*
|
|
219
|
-
* @stimeoRuntimeOnly `timeout` and `promptBefore` set the deadlines this one check compares
|
|
220
|
-
* against; the phase it shows follows the elapsed time.
|
|
221
|
-
*/
|
|
222
|
-
#checkPrompt() {
|
|
378
|
+
#checkPrompt(left = this.#safePromptBefore) {
|
|
223
379
|
if (this.#idle) return;
|
|
224
|
-
const remaining = this
|
|
380
|
+
const remaining = this.#safeTimeout - this.#safePromptBefore - (Date.now() - this.#lastActivity);
|
|
225
381
|
if (remaining > 0) {
|
|
226
|
-
this.#timeouts.set(() => this.#checkPrompt(), remaining);
|
|
382
|
+
this.#timeouts.set(() => this.#checkPrompt(), Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
227
383
|
return;
|
|
228
384
|
}
|
|
229
385
|
this.#prompted = true;
|
|
230
386
|
this.#reflect();
|
|
231
|
-
this.dispatch("prompt", { detail: { remaining:
|
|
387
|
+
this.dispatch("prompt", { detail: { remaining: left } });
|
|
232
388
|
}
|
|
233
|
-
/** Applies the current phase to a warning region inserted or replaced at runtime. */
|
|
234
389
|
promptTargetConnected() {
|
|
235
390
|
this.#promptRegions.reflect(this.element, this.#prompted);
|
|
236
391
|
}
|
|
237
|
-
/** Applies the current phase to an idle region inserted or replaced at runtime. */
|
|
238
392
|
idleTargetConnected() {
|
|
239
393
|
this.#idleRegions.reflect(this.element, this.#idle);
|
|
240
394
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Writes the phase to the element and to the declared regions. Both hooks and both
|
|
243
|
-
* regions are a pure function of the two flags, so every place that moves a flag ends
|
|
244
|
-
* here and the markup can never disagree with the phase the detector is in.
|
|
245
|
-
*/
|
|
246
395
|
#reflect() {
|
|
247
396
|
const { element } = this;
|
|
248
397
|
if (this.#prompted) element.setAttribute("data-prompt", "true");
|
|
@@ -252,8 +401,25 @@ var IdleController = class extends Controller {
|
|
|
252
401
|
this.#promptRegions.reflect(element, this.#prompted);
|
|
253
402
|
this.#idleRegions.reflect(element, this.#idle);
|
|
254
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
|
+
);
|
|
421
|
+
}
|
|
255
422
|
};
|
|
256
423
|
|
|
257
424
|
export { IdleController };
|
|
258
|
-
//# sourceMappingURL=idle_controller.js.map
|
|
259
425
|
//# sourceMappingURL=idle_controller.js.map
|