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,28 +1,32 @@
|
|
|
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
|
+
};
|
|
4
13
|
|
|
5
|
-
// src/utils/microtask_coalescer.ts
|
|
6
14
|
var MicrotaskCoalescer = class {
|
|
7
15
|
#run;
|
|
8
16
|
#queued = false;
|
|
9
17
|
#active = false;
|
|
10
18
|
#generation = 0;
|
|
11
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
12
19
|
constructor(run) {
|
|
13
20
|
this.#run = run;
|
|
14
21
|
}
|
|
15
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
16
22
|
activate() {
|
|
17
23
|
this.#active = true;
|
|
18
24
|
}
|
|
19
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
20
25
|
cancel() {
|
|
21
26
|
this.#active = false;
|
|
22
27
|
this.#queued = false;
|
|
23
28
|
this.#generation += 1;
|
|
24
29
|
}
|
|
25
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
26
30
|
schedule() {
|
|
27
31
|
if (!this.#active || this.#queued) return;
|
|
28
32
|
this.#queued = true;
|
|
@@ -35,71 +39,119 @@ var MicrotaskCoalescer = class {
|
|
|
35
39
|
}
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
var MorphRenderWatcher = class {
|
|
43
|
+
#listeners = new ListenerSet();
|
|
44
|
+
#pass;
|
|
45
|
+
#element = null;
|
|
46
|
+
#onMorph = () => this.#pass.schedule();
|
|
47
|
+
constructor(run) {
|
|
48
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
49
|
+
}
|
|
50
|
+
observe(element) {
|
|
51
|
+
if (this.#element === element) return;
|
|
52
|
+
this.disconnect();
|
|
53
|
+
this.#element = element;
|
|
54
|
+
this.#pass.activate();
|
|
55
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
56
|
+
}
|
|
57
|
+
schedule() {
|
|
58
|
+
this.#pass.schedule();
|
|
59
|
+
}
|
|
60
|
+
disconnect() {
|
|
61
|
+
this.#listeners.dispose();
|
|
62
|
+
this.#pass.cancel();
|
|
63
|
+
this.#element = null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
var NUMBER_BOUNDS = {
|
|
68
|
+
finite: { finite: true }};
|
|
69
|
+
function matchesNumberBounds(value, bounds) {
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
72
|
+
if (direction === null) return false;
|
|
73
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
74
|
+
}
|
|
75
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
76
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
77
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
78
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
79
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readNumber(raw, fallback, bounds) {
|
|
84
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
var NumberValueReader = class {
|
|
88
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
89
|
+
read(owner, name, raw, fallback, bounds) {
|
|
90
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
91
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
92
|
+
this.#lastRejected.delete(name);
|
|
93
|
+
return resolved;
|
|
94
|
+
}
|
|
95
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
96
|
+
const literal = owner.element.getAttribute(attribute);
|
|
97
|
+
if (literal === null) {
|
|
98
|
+
this.#lastRejected.delete(name);
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
102
|
+
this.#lastRejected.set(name, literal);
|
|
103
|
+
console.warn(
|
|
104
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
var StepIndicatorController = class _StepIndicatorController extends Controller {
|
|
112
|
+
#numbers = new NumberValueReader();
|
|
40
113
|
static targets = ["step"];
|
|
41
114
|
static values = {
|
|
42
|
-
|
|
115
|
+
index: { type: Number, default: 0 }
|
|
43
116
|
};
|
|
44
|
-
static
|
|
117
|
+
static valueConstraints = {
|
|
118
|
+
index: NUMBER_BOUNDS.finite
|
|
119
|
+
};
|
|
120
|
+
static actions = ["setIndex"];
|
|
45
121
|
static events = ["change"];
|
|
46
|
-
|
|
47
|
-
* Collapses a batch of step callbacks — and a morph that swaps `current` with
|
|
48
|
-
* them — into one repaint. Replacing a list of N steps delivers N callbacks, and
|
|
49
|
-
* each one would otherwise rewrite every step's state.
|
|
50
|
-
*/
|
|
51
|
-
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
52
|
-
/** Renders the initial state from the `current` value. */
|
|
122
|
+
#repaint = new MorphRenderWatcher(() => this.#render());
|
|
53
123
|
connect() {
|
|
54
|
-
this.#repaint.
|
|
124
|
+
this.#repaint.observe(this.element);
|
|
55
125
|
this.#render();
|
|
56
126
|
}
|
|
57
|
-
/** Closes the window in which a queued repaint may still run. */
|
|
58
127
|
disconnect() {
|
|
59
|
-
this.#repaint.
|
|
128
|
+
this.#repaint.disconnect();
|
|
60
129
|
}
|
|
61
|
-
/** Syncs a step appended or replaced at runtime (the consumer owns the list). */
|
|
62
130
|
stepTargetConnected() {
|
|
63
131
|
this.#repaint.schedule();
|
|
64
132
|
}
|
|
65
|
-
/** Re-derives the remaining steps when one is removed at runtime. */
|
|
66
133
|
stepTargetDisconnected() {
|
|
67
134
|
this.#repaint.schedule();
|
|
68
135
|
}
|
|
69
|
-
|
|
70
|
-
currentValueChanged() {
|
|
136
|
+
indexValueChanged() {
|
|
71
137
|
this.#repaint.schedule();
|
|
72
138
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* and dispatches `change`. Out-of-range indices are clamped to the step set,
|
|
76
|
-
* and both sides of the no-op test are clamped, so moving onto the step an
|
|
77
|
-
* out-of-range `current` already renders is not reported as a change.
|
|
78
|
-
*/
|
|
79
|
-
setCurrent(event) {
|
|
80
|
-
const next = event.detail?.current;
|
|
139
|
+
setIndex(event) {
|
|
140
|
+
const next = event.detail?.index;
|
|
81
141
|
if (typeof next !== "number" || !Number.isFinite(next)) return;
|
|
82
142
|
const clamped = this.#clamp(next);
|
|
83
|
-
const
|
|
84
|
-
|
|
143
|
+
const previous = this.#clamp(this.#safeIndex);
|
|
144
|
+
const moved = clamped !== previous;
|
|
145
|
+
this.indexValue = clamped;
|
|
85
146
|
if (!moved) return;
|
|
86
147
|
this.#render();
|
|
87
148
|
this.dispatch("change", {
|
|
88
|
-
detail: {
|
|
149
|
+
detail: { index: clamped, previous, total: this.stepTargets.length }
|
|
89
150
|
});
|
|
90
151
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Applies `data-state`, `aria-current`, and the progress ratio custom property.
|
|
93
|
-
*
|
|
94
|
-
* A pure function of the step set and `current`, so running it again writes the
|
|
95
|
-
* same values — which is what lets the action path paint synchronously (the event
|
|
96
|
-
* goes out after the DOM is updated) while a coalesced pass may still follow.
|
|
97
|
-
*
|
|
98
|
-
* @stimeoRenderRoot
|
|
99
|
-
*/
|
|
100
152
|
#render() {
|
|
101
153
|
const total = this.stepTargets.length;
|
|
102
|
-
const current = this.#clamp(this
|
|
154
|
+
const current = this.#clamp(this.#safeIndex);
|
|
103
155
|
this.stepTargets.forEach((step, index) => {
|
|
104
156
|
step.dataset.state = index < current ? "complete" : index === current ? "current" : "upcoming";
|
|
105
157
|
if (index === current) {
|
|
@@ -111,19 +163,21 @@ var StepIndicatorController = class extends Controller {
|
|
|
111
163
|
const ratio = total > 1 ? current / (total - 1) : 0;
|
|
112
164
|
this.element.style.setProperty("--stimeo--step-indicator-ratio", String(ratio));
|
|
113
165
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Constrains an index to `[0, total-1]` (or `0` when there are no steps). A
|
|
116
|
-
* non-finite index falls back to the first step: `current` is read from markup,
|
|
117
|
-
* so an unparsable attribute arrives as `NaN` and would otherwise propagate
|
|
118
|
-
* into every state hook.
|
|
119
|
-
*/
|
|
120
166
|
#clamp(index) {
|
|
121
167
|
const last = this.stepTargets.length - 1;
|
|
122
|
-
if (last < 0
|
|
168
|
+
if (last < 0) return 0;
|
|
123
169
|
return Math.min(last, Math.max(0, Math.trunc(index)));
|
|
124
170
|
}
|
|
171
|
+
get #safeIndex() {
|
|
172
|
+
return this.#numbers.read(
|
|
173
|
+
this,
|
|
174
|
+
"index",
|
|
175
|
+
this.indexValue,
|
|
176
|
+
_StepIndicatorController.values.index.default,
|
|
177
|
+
_StepIndicatorController.valueConstraints.index
|
|
178
|
+
);
|
|
179
|
+
}
|
|
125
180
|
};
|
|
126
181
|
|
|
127
182
|
export { StepIndicatorController };
|
|
128
|
-
//# sourceMappingURL=step_indicator_controller.js.map
|
|
129
183
|
//# sourceMappingURL=step_indicator_controller.js.map
|
|
@@ -1,77 +1,193 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var NUMBER_BOUNDS = {
|
|
4
|
+
finite: { finite: true }};
|
|
5
|
+
function matchesNumberBounds(value, bounds) {
|
|
6
|
+
if (!Number.isFinite(value)) {
|
|
7
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
8
|
+
if (direction === null) return false;
|
|
9
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
10
|
+
}
|
|
11
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
12
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
13
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
14
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
15
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toFiniteNumber(raw) {
|
|
20
|
+
if (typeof raw !== "number" && typeof raw !== "string") return null;
|
|
21
|
+
if (typeof raw === "string" && raw.trim().length === 0) return null;
|
|
22
|
+
const value = Number(raw);
|
|
23
|
+
return Number.isFinite(value) ? value : null;
|
|
24
|
+
}
|
|
25
|
+
function readNumber(raw, fallback, bounds) {
|
|
26
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var ListenerSet = class {
|
|
30
|
+
#abort = new AbortController();
|
|
31
|
+
add(target, type, handler, options) {
|
|
32
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
33
|
+
}
|
|
34
|
+
dispose() {
|
|
35
|
+
this.#abort.abort();
|
|
36
|
+
this.#abort = new AbortController();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
var MicrotaskCoalescer = class {
|
|
41
|
+
#run;
|
|
42
|
+
#queued = false;
|
|
43
|
+
#active = false;
|
|
44
|
+
#generation = 0;
|
|
45
|
+
constructor(run) {
|
|
46
|
+
this.#run = run;
|
|
47
|
+
}
|
|
48
|
+
activate() {
|
|
49
|
+
this.#active = true;
|
|
50
|
+
}
|
|
51
|
+
cancel() {
|
|
52
|
+
this.#active = false;
|
|
53
|
+
this.#queued = false;
|
|
54
|
+
this.#generation += 1;
|
|
55
|
+
}
|
|
56
|
+
schedule() {
|
|
57
|
+
if (!this.#active || this.#queued) return;
|
|
58
|
+
this.#queued = true;
|
|
59
|
+
const generation = this.#generation;
|
|
60
|
+
queueMicrotask(() => {
|
|
61
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
62
|
+
this.#queued = false;
|
|
63
|
+
this.#run();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
var MorphRenderWatcher = class {
|
|
69
|
+
#listeners = new ListenerSet();
|
|
70
|
+
#pass;
|
|
71
|
+
#element = null;
|
|
72
|
+
#onMorph = () => this.#pass.schedule();
|
|
73
|
+
constructor(run) {
|
|
74
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
75
|
+
}
|
|
76
|
+
observe(element) {
|
|
77
|
+
if (this.#element === element) return;
|
|
78
|
+
this.disconnect();
|
|
79
|
+
this.#element = element;
|
|
80
|
+
this.#pass.activate();
|
|
81
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
82
|
+
}
|
|
83
|
+
schedule() {
|
|
84
|
+
this.#pass.schedule();
|
|
85
|
+
}
|
|
86
|
+
disconnect() {
|
|
87
|
+
this.#listeners.dispose();
|
|
88
|
+
this.#pass.cancel();
|
|
89
|
+
this.#element = null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
var NumberValueReader = class {
|
|
94
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
95
|
+
read(owner, name, raw, fallback, bounds) {
|
|
96
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
97
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
98
|
+
this.#lastRejected.delete(name);
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
102
|
+
const literal = owner.element.getAttribute(attribute);
|
|
103
|
+
if (literal === null) {
|
|
104
|
+
this.#lastRejected.delete(name);
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
108
|
+
this.#lastRejected.set(name, literal);
|
|
109
|
+
console.warn(
|
|
110
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return resolved;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
var StepperController = class _StepperController extends Controller {
|
|
118
|
+
#numbers = new NumberValueReader();
|
|
5
119
|
static targets = ["step"];
|
|
6
120
|
static values = {
|
|
7
121
|
index: { type: Number, default: 0 },
|
|
8
122
|
linear: { type: Boolean, default: false }
|
|
9
123
|
};
|
|
124
|
+
static valueConstraints = {
|
|
125
|
+
index: NUMBER_BOUNDS.finite
|
|
126
|
+
};
|
|
10
127
|
static actions = ["goto", "next", "prev"];
|
|
11
|
-
static events = ["change"];
|
|
12
|
-
#
|
|
13
|
-
|
|
128
|
+
static events = ["change", "reconcile"];
|
|
129
|
+
#repaint = new MorphRenderWatcher(() => this.#reconcileStep());
|
|
130
|
+
#shown = null;
|
|
14
131
|
connect() {
|
|
15
|
-
this.#
|
|
16
|
-
this.#
|
|
132
|
+
this.#repaint.observe(this.element);
|
|
133
|
+
this.#shown = this.#currentStep;
|
|
134
|
+
this.#render();
|
|
17
135
|
}
|
|
18
136
|
disconnect() {
|
|
19
|
-
this.#
|
|
137
|
+
this.#repaint.disconnect();
|
|
20
138
|
}
|
|
21
|
-
/** Re-renders when Turbo Morph or application code changes `index` at runtime. */
|
|
22
139
|
indexValueChanged() {
|
|
23
|
-
|
|
24
|
-
|
|
140
|
+
this.#repaint.schedule();
|
|
141
|
+
}
|
|
142
|
+
stepTargetConnected() {
|
|
143
|
+
this.#repaint.schedule();
|
|
144
|
+
}
|
|
145
|
+
stepTargetDisconnected() {
|
|
146
|
+
this.#repaint.schedule();
|
|
25
147
|
}
|
|
26
|
-
/** Advances to the next step (ignored at the last step). */
|
|
27
148
|
next() {
|
|
28
|
-
this.#moveTo(this.#clampIndex(this
|
|
149
|
+
this.#moveTo(this.#clampIndex(this.#safeIndex) + 1);
|
|
29
150
|
}
|
|
30
|
-
/** Returns to the previous step (ignored at the first step). */
|
|
31
151
|
prev() {
|
|
32
|
-
this.#moveTo(this.#clampIndex(this
|
|
152
|
+
this.#moveTo(this.#clampIndex(this.#safeIndex) - 1);
|
|
33
153
|
}
|
|
34
|
-
/** Jumps to the step carried in the action's `index` param. */
|
|
35
154
|
goto(event) {
|
|
36
|
-
const
|
|
37
|
-
|
|
155
|
+
const { index } = event.params;
|
|
156
|
+
const target = toFiniteNumber(index);
|
|
157
|
+
if (target === null || !Number.isInteger(target)) return;
|
|
38
158
|
this.#moveTo(target);
|
|
39
159
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Moves the current step to `target` when allowed: in range, not a no-op, and
|
|
42
|
-
* — under `linear` — not skipping more than one step ahead. Re-renders and
|
|
43
|
-
* dispatches `change`.
|
|
44
|
-
*/
|
|
45
160
|
#moveTo(target) {
|
|
46
161
|
const total = this.stepTargets.length;
|
|
47
|
-
if (!Number.isFinite(target) || !Number.isInteger(target)) return;
|
|
48
162
|
if (target < 0 || target >= total) return;
|
|
49
|
-
const current = this.#clampIndex(this
|
|
50
|
-
if (target === current) return;
|
|
163
|
+
const current = this.#clampIndex(this.#safeIndex);
|
|
164
|
+
if (target === current && target === this.#shown) return;
|
|
51
165
|
if (this.linearValue && target > current + 1) return;
|
|
52
|
-
const previous = current;
|
|
166
|
+
const previous = this.#shown ?? current;
|
|
53
167
|
this.indexValue = target;
|
|
54
|
-
this.#
|
|
168
|
+
this.#shown = target;
|
|
169
|
+
this.#render();
|
|
170
|
+
if (target === previous) return;
|
|
55
171
|
this.dispatch("change", {
|
|
56
|
-
detail: { index: target, previous, step: this.stepTargets[target] }
|
|
172
|
+
detail: { index: target, previous, total, step: this.stepTargets[target] }
|
|
57
173
|
});
|
|
58
174
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
this.#render(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
175
|
+
#reconcileStep() {
|
|
176
|
+
const previous = this.#shown;
|
|
177
|
+
const index = this.#currentStep;
|
|
178
|
+
if (index !== null) this.#shown = index;
|
|
179
|
+
this.#render();
|
|
180
|
+
if (index === null || previous === null || index === previous) return;
|
|
181
|
+
const steps = this.stepTargets;
|
|
182
|
+
this.dispatch("reconcile", {
|
|
183
|
+
detail: { index, previous, total: steps.length, step: steps[index] }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
get #currentStep() {
|
|
187
|
+
return this.stepTargets.length > 0 ? this.#clampIndex(this.#safeIndex) : null;
|
|
188
|
+
}
|
|
189
|
+
#render() {
|
|
190
|
+
const current = this.#clampIndex(this.#safeIndex);
|
|
75
191
|
this.stepTargets.forEach((step, index) => {
|
|
76
192
|
step.dataset.state = index < current ? "complete" : index === current ? "current" : "upcoming";
|
|
77
193
|
const button = step.querySelector("button");
|
|
@@ -83,14 +199,21 @@ var StepperController = class extends Controller {
|
|
|
83
199
|
}
|
|
84
200
|
});
|
|
85
201
|
}
|
|
86
|
-
/** Constrains an index to `[0, total-1]` (or `0` when there are no steps). */
|
|
87
202
|
#clampIndex(index) {
|
|
88
203
|
const last = this.stepTargets.length - 1;
|
|
89
|
-
if (last < 0
|
|
204
|
+
if (last < 0) return 0;
|
|
90
205
|
return Math.min(last, Math.max(0, Math.trunc(index)));
|
|
91
206
|
}
|
|
207
|
+
get #safeIndex() {
|
|
208
|
+
return this.#numbers.read(
|
|
209
|
+
this,
|
|
210
|
+
"index",
|
|
211
|
+
this.indexValue,
|
|
212
|
+
_StepperController.values.index.default,
|
|
213
|
+
_StepperController.valueConstraints.index
|
|
214
|
+
);
|
|
215
|
+
}
|
|
92
216
|
};
|
|
93
217
|
|
|
94
218
|
export { StepperController };
|
|
95
|
-
//# sourceMappingURL=stepper_controller.js.map
|
|
96
219
|
//# sourceMappingURL=stepper_controller.js.map
|