stimeo-ui 0.16.0 → 0.18.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 +136 -0
- data/README.md +27 -11
- data/dist/cable/index.js +384 -449
- data/dist/controllers/accordion_controller.js +174 -55
- data/dist/controllers/alert_dialog_controller.js +903 -235
- data/dist/controllers/announcer_controller.js +238 -205
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +414 -145
- data/dist/controllers/avatar_controller.js +185 -107
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +282 -58
- data/dist/controllers/calendar_controller.js +228 -260
- data/dist/controllers/carousel_controller.js +536 -436
- data/dist/controllers/character_counter_controller.js +493 -207
- data/dist/controllers/checkbox_controller.js +38 -74
- data/dist/controllers/clipboard_controller.js +261 -149
- data/dist/controllers/collapsible_controller.js +304 -120
- data/dist/controllers/color_picker_controller.js +178 -146
- data/dist/controllers/combobox_controller.js +522 -156
- data/dist/controllers/command_palette_controller.js +1156 -395
- data/dist/controllers/conditional_fields_controller.js +300 -171
- data/dist/controllers/confirm_controller.js +980 -262
- data/dist/controllers/context_menu_controller.js +346 -153
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +281 -158
- data/dist/controllers/currency_input_controller.js +575 -225
- data/dist/controllers/data_grid_controller.js +248 -214
- data/dist/controllers/date_range_picker_controller.js +338 -235
- data/dist/controllers/dialog_controller.js +899 -237
- data/dist/controllers/direct_upload_controller.js +97 -167
- data/dist/controllers/dirty_form_controller.js +72 -69
- data/dist/controllers/dismissible_controller.js +196 -24
- data/dist/controllers/drawer_controller.js +974 -355
- data/dist/controllers/dropdown_controller.js +339 -96
- data/dist/controllers/editable_controller.js +315 -88
- data/dist/controllers/empty_state_controller.js +79 -58
- data/dist/controllers/file_dropzone_controller.js +322 -211
- data/dist/controllers/filter_controller.js +77 -42
- data/dist/controllers/flash_controller.js +238 -347
- data/dist/controllers/focus_controller.js +679 -240
- data/dist/controllers/form_field_controller.js +156 -132
- data/dist/controllers/form_validation_controller.js +31 -97
- data/dist/controllers/frame_loading_controller.js +275 -217
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +327 -130
- data/dist/controllers/idle_controller.js +271 -101
- data/dist/controllers/input_mask_controller.js +483 -97
- data/dist/controllers/intersection_controller.js +150 -106
- data/dist/controllers/lazy_frame_controller.js +24 -68
- data/dist/controllers/listbox_controller.js +523 -240
- data/dist/controllers/local_time_controller.js +102 -77
- data/dist/controllers/masonry_controller.js +80 -96
- data/dist/controllers/menu_controller.js +302 -188
- data/dist/controllers/menubar_controller.js +232 -345
- data/dist/controllers/meter_controller.js +417 -87
- data/dist/controllers/multi_select_controller.js +785 -393
- data/dist/controllers/navigation_menu_controller.js +190 -250
- data/dist/controllers/nested_form_controller.js +396 -192
- data/dist/controllers/network_status_controller.js +268 -62
- data/dist/controllers/number_input_controller.js +610 -303
- data/dist/controllers/optimistic_controller.js +45 -87
- data/dist/controllers/otp_controller.js +658 -255
- data/dist/controllers/overflow_indicator_controller.js +122 -113
- data/dist/controllers/overflow_menu_controller.js +470 -295
- data/dist/controllers/pagination_controller.js +332 -123
- data/dist/controllers/password_reveal_controller.js +392 -121
- data/dist/controllers/password_strength_controller.js +111 -158
- data/dist/controllers/persist_controller.js +165 -111
- data/dist/controllers/pointer_drag_controller.js +434 -188
- data/dist/controllers/popover_controller.js +269 -113
- data/dist/controllers/portal_controller.js +215 -76
- data/dist/controllers/preview_guard_controller.js +243 -116
- data/dist/controllers/progress_controller.js +391 -78
- data/dist/controllers/radio_group_controller.js +278 -211
- data/dist/controllers/range_slider_controller.js +220 -113
- data/dist/controllers/rating_controller.js +458 -211
- data/dist/controllers/read_more_controller.js +138 -63
- data/dist/controllers/reading_progress_controller.js +166 -137
- data/dist/controllers/relative_time_controller.js +211 -112
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
- data/dist/controllers/resizable_controller.js +299 -150
- data/dist/controllers/roving_controller.js +49 -88
- data/dist/controllers/scroll_area_controller.js +490 -246
- data/dist/controllers/scroll_restore_controller.js +14 -55
- data/dist/controllers/scroll_visibility_controller.js +432 -149
- data/dist/controllers/scrollspy_controller.js +193 -248
- data/dist/controllers/separator_controller.js +285 -142
- data/dist/controllers/sidebar_controller.js +1069 -404
- data/dist/controllers/skeleton_controller.js +259 -101
- data/dist/controllers/slider_controller.js +187 -86
- data/dist/controllers/smart_sticky_header_controller.js +173 -66
- data/dist/controllers/sortable_controller.js +26 -125
- data/dist/controllers/spinner_controller.js +288 -173
- data/dist/controllers/step_indicator_controller.js +105 -46
- data/dist/controllers/stepper_controller.js +120 -73
- data/dist/controllers/stick_to_bottom_controller.js +275 -140
- data/dist/controllers/sticky_observer_controller.js +220 -49
- data/dist/controllers/submit_once_controller.js +376 -227
- data/dist/controllers/switch_controller.js +201 -84
- data/dist/controllers/tabs_controller.js +41 -30
- data/dist/controllers/tags_input_controller.js +293 -173
- data/dist/controllers/textarea_autosize_controller.js +108 -74
- data/dist/controllers/theme_controller.js +140 -116
- data/dist/controllers/time_picker_controller.js +331 -127
- data/dist/controllers/toast_controller.js +244 -324
- data/dist/controllers/toggle_group_controller.js +252 -191
- data/dist/controllers/toolbar_controller.js +219 -120
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +283 -369
- data/dist/index.js +8573 -10410
- data/dist/positioning/index.js +154 -47
- data/lib/generators/stimeo/install/install_generator.rb +7 -2
- data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,28 +1,35 @@
|
|
|
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
|
-
|
|
30
|
+
get pending() {
|
|
31
|
+
return this.#queued;
|
|
32
|
+
}
|
|
26
33
|
schedule() {
|
|
27
34
|
if (!this.#active || this.#queued) return;
|
|
28
35
|
this.#queued = true;
|
|
@@ -35,52 +42,111 @@ var MicrotaskCoalescer = class {
|
|
|
35
42
|
}
|
|
36
43
|
};
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
var MorphRenderWatcher = class {
|
|
46
|
+
#listeners = new ListenerSet();
|
|
47
|
+
#pass;
|
|
48
|
+
#element = null;
|
|
49
|
+
#onMorph = () => this.#pass.schedule();
|
|
50
|
+
constructor(run) {
|
|
51
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
52
|
+
}
|
|
53
|
+
observe(element) {
|
|
54
|
+
if (this.#element === element) return;
|
|
55
|
+
this.disconnect();
|
|
56
|
+
this.#element = element;
|
|
57
|
+
this.#pass.activate();
|
|
58
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
59
|
+
}
|
|
60
|
+
schedule() {
|
|
61
|
+
this.#pass.schedule();
|
|
62
|
+
}
|
|
63
|
+
get pending() {
|
|
64
|
+
return this.#pass.pending;
|
|
65
|
+
}
|
|
66
|
+
disconnect() {
|
|
67
|
+
this.#listeners.dispose();
|
|
68
|
+
this.#pass.cancel();
|
|
69
|
+
this.#element = null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
var NUMBER_BOUNDS = {
|
|
74
|
+
finite: { finite: true }};
|
|
75
|
+
function matchesNumberBounds(value, bounds) {
|
|
76
|
+
if (!Number.isFinite(value)) {
|
|
77
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
78
|
+
if (direction === null) return false;
|
|
79
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
80
|
+
}
|
|
81
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
82
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
83
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
84
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
85
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readNumber(raw, fallback, bounds) {
|
|
90
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
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 StepIndicatorController = class _StepIndicatorController extends Controller {
|
|
118
|
+
#numbers = new NumberValueReader();
|
|
40
119
|
static targets = ["step"];
|
|
41
120
|
static values = {
|
|
42
121
|
index: { type: Number, default: 0 }
|
|
43
122
|
};
|
|
123
|
+
static valueConstraints = {
|
|
124
|
+
index: NUMBER_BOUNDS.finite
|
|
125
|
+
};
|
|
44
126
|
static actions = ["setIndex"];
|
|
45
127
|
static events = ["change"];
|
|
46
|
-
|
|
47
|
-
* Collapses a batch of step callbacks — and a morph that swaps `index` 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 `index` value. */
|
|
128
|
+
#repaint = new MorphRenderWatcher(() => this.#render());
|
|
53
129
|
connect() {
|
|
54
|
-
this.#repaint.
|
|
130
|
+
this.#repaint.observe(this.element);
|
|
55
131
|
this.#render();
|
|
56
132
|
}
|
|
57
|
-
/** Closes the window in which a queued repaint may still run. */
|
|
58
133
|
disconnect() {
|
|
59
|
-
this.#repaint.
|
|
134
|
+
this.#repaint.disconnect();
|
|
60
135
|
}
|
|
61
|
-
/** Syncs a step appended or replaced at runtime (the consumer owns the list). */
|
|
62
136
|
stepTargetConnected() {
|
|
63
137
|
this.#repaint.schedule();
|
|
64
138
|
}
|
|
65
|
-
/** Re-derives the remaining steps when one is removed at runtime. */
|
|
66
139
|
stepTargetDisconnected() {
|
|
67
140
|
this.#repaint.schedule();
|
|
68
141
|
}
|
|
69
|
-
/** Repaints when application code (or a Turbo morph) changes `index` at runtime. */
|
|
70
142
|
indexValueChanged() {
|
|
71
143
|
this.#repaint.schedule();
|
|
72
144
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Moves the indicator from an external event (`detail.index`, 0-based) and
|
|
75
|
-
* dispatches `change`. Out-of-range positions 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 `index` already renders is not reported as a change.
|
|
78
|
-
*/
|
|
79
145
|
setIndex(event) {
|
|
80
146
|
const next = event.detail?.index;
|
|
81
147
|
if (typeof next !== "number" || !Number.isFinite(next)) return;
|
|
82
148
|
const clamped = this.#clamp(next);
|
|
83
|
-
const previous = this.#clamp(this
|
|
149
|
+
const previous = this.#clamp(this.#safeIndex);
|
|
84
150
|
const moved = clamped !== previous;
|
|
85
151
|
this.indexValue = clamped;
|
|
86
152
|
if (!moved) return;
|
|
@@ -89,18 +155,9 @@ var StepIndicatorController = class extends Controller {
|
|
|
89
155
|
detail: { index: clamped, previous, total: this.stepTargets.length }
|
|
90
156
|
});
|
|
91
157
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Applies `data-state`, `aria-current`, and the progress ratio custom property.
|
|
94
|
-
*
|
|
95
|
-
* A pure function of the step set and `index`, so running it again writes the
|
|
96
|
-
* same values — which is what lets the action path paint synchronously (the event
|
|
97
|
-
* goes out after the DOM is updated) while a coalesced pass may still follow.
|
|
98
|
-
*
|
|
99
|
-
* @stimeoRenderRoot
|
|
100
|
-
*/
|
|
101
158
|
#render() {
|
|
102
159
|
const total = this.stepTargets.length;
|
|
103
|
-
const current = this.#clamp(this
|
|
160
|
+
const current = this.#clamp(this.#safeIndex);
|
|
104
161
|
this.stepTargets.forEach((step, index) => {
|
|
105
162
|
step.dataset.state = index < current ? "complete" : index === current ? "current" : "upcoming";
|
|
106
163
|
if (index === current) {
|
|
@@ -112,19 +169,21 @@ var StepIndicatorController = class extends Controller {
|
|
|
112
169
|
const ratio = total > 1 ? current / (total - 1) : 0;
|
|
113
170
|
this.element.style.setProperty("--stimeo--step-indicator-ratio", String(ratio));
|
|
114
171
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Constrains an index to `[0, total-1]` (or `0` when there are no steps). A
|
|
117
|
-
* non-finite index falls back to the first step: `index` is read from markup,
|
|
118
|
-
* so an unparsable attribute arrives as `NaN` and would otherwise propagate
|
|
119
|
-
* into every state hook.
|
|
120
|
-
*/
|
|
121
172
|
#clamp(index) {
|
|
122
173
|
const last = this.stepTargets.length - 1;
|
|
123
|
-
if (last < 0
|
|
174
|
+
if (last < 0) return 0;
|
|
124
175
|
return Math.min(last, Math.max(0, Math.trunc(index)));
|
|
125
176
|
}
|
|
177
|
+
get #safeIndex() {
|
|
178
|
+
return this.#numbers.read(
|
|
179
|
+
this,
|
|
180
|
+
"index",
|
|
181
|
+
this.indexValue,
|
|
182
|
+
_StepIndicatorController.values.index.default,
|
|
183
|
+
_StepIndicatorController.valueConstraints.index
|
|
184
|
+
);
|
|
185
|
+
}
|
|
126
186
|
};
|
|
127
187
|
|
|
128
188
|
export { StepIndicatorController };
|
|
129
|
-
//# sourceMappingURL=step_indicator_controller.js.map
|
|
130
189
|
//# sourceMappingURL=step_indicator_controller.js.map
|
|
@@ -1,28 +1,61 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
};
|
|
4
39
|
|
|
5
|
-
// src/utils/microtask_coalescer.ts
|
|
6
40
|
var MicrotaskCoalescer = class {
|
|
7
41
|
#run;
|
|
8
42
|
#queued = false;
|
|
9
43
|
#active = false;
|
|
10
44
|
#generation = 0;
|
|
11
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
12
45
|
constructor(run) {
|
|
13
46
|
this.#run = run;
|
|
14
47
|
}
|
|
15
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
16
48
|
activate() {
|
|
17
49
|
this.#active = true;
|
|
18
50
|
}
|
|
19
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
20
51
|
cancel() {
|
|
21
52
|
this.#active = false;
|
|
22
53
|
this.#queued = false;
|
|
23
54
|
this.#generation += 1;
|
|
24
55
|
}
|
|
25
|
-
|
|
56
|
+
get pending() {
|
|
57
|
+
return this.#queued;
|
|
58
|
+
}
|
|
26
59
|
schedule() {
|
|
27
60
|
if (!this.#active || this.#queued) return;
|
|
28
61
|
this.#queued = true;
|
|
@@ -35,95 +68,116 @@ var MicrotaskCoalescer = class {
|
|
|
35
68
|
}
|
|
36
69
|
};
|
|
37
70
|
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
var MorphRenderWatcher = class {
|
|
72
|
+
#listeners = new ListenerSet();
|
|
73
|
+
#pass;
|
|
74
|
+
#element = null;
|
|
75
|
+
#onMorph = () => this.#pass.schedule();
|
|
76
|
+
constructor(run) {
|
|
77
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
78
|
+
}
|
|
79
|
+
observe(element) {
|
|
80
|
+
if (this.#element === element) return;
|
|
81
|
+
this.disconnect();
|
|
82
|
+
this.#element = element;
|
|
83
|
+
this.#pass.activate();
|
|
84
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
85
|
+
}
|
|
86
|
+
schedule() {
|
|
87
|
+
this.#pass.schedule();
|
|
88
|
+
}
|
|
89
|
+
get pending() {
|
|
90
|
+
return this.#pass.pending;
|
|
91
|
+
}
|
|
92
|
+
disconnect() {
|
|
93
|
+
this.#listeners.dispose();
|
|
94
|
+
this.#pass.cancel();
|
|
95
|
+
this.#element = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
var NumberValueReader = class {
|
|
100
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
101
|
+
read(owner, name, raw, fallback, bounds) {
|
|
102
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
103
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
104
|
+
this.#lastRejected.delete(name);
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
108
|
+
const literal = owner.element.getAttribute(attribute);
|
|
109
|
+
if (literal === null) {
|
|
110
|
+
this.#lastRejected.delete(name);
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
114
|
+
this.#lastRejected.set(name, literal);
|
|
115
|
+
console.warn(
|
|
116
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
var StepperController = class _StepperController extends Controller {
|
|
124
|
+
#numbers = new NumberValueReader();
|
|
40
125
|
static targets = ["step"];
|
|
41
126
|
static values = {
|
|
42
127
|
index: { type: Number, default: 0 },
|
|
43
128
|
linear: { type: Boolean, default: false }
|
|
44
129
|
};
|
|
130
|
+
static valueConstraints = {
|
|
131
|
+
index: NUMBER_BOUNDS.finite
|
|
132
|
+
};
|
|
45
133
|
static actions = ["goto", "next", "prev"];
|
|
46
134
|
static events = ["change", "reconcile"];
|
|
47
|
-
|
|
48
|
-
* Collapses the Value and target callbacks of one mutation into one pass, and
|
|
49
|
-
* refuses the ones Stimulus delivers before `connect()`, which renders itself.
|
|
50
|
-
*/
|
|
51
|
-
#repaint = new MicrotaskCoalescer(() => this.#reconcileStep());
|
|
52
|
-
/**
|
|
53
|
-
* The position shown current last, which the next move is measured from, or
|
|
54
|
-
* `null` before any step has been shown. While no step is present it keeps the
|
|
55
|
-
* last one.
|
|
56
|
-
*/
|
|
135
|
+
#repaint = new MorphRenderWatcher(() => this.#reconcileStep());
|
|
57
136
|
#shown = null;
|
|
58
|
-
/** Renders the initial state from `index`, clamped into the step range. */
|
|
59
137
|
connect() {
|
|
60
|
-
this.#repaint.
|
|
138
|
+
this.#repaint.observe(this.element);
|
|
61
139
|
this.#shown = this.#currentStep;
|
|
62
140
|
this.#render();
|
|
63
141
|
}
|
|
64
|
-
/** Drops a pending pass, so nothing renders or reports for a stepper that left. */
|
|
65
142
|
disconnect() {
|
|
66
|
-
this.#repaint.
|
|
143
|
+
this.#repaint.disconnect();
|
|
67
144
|
}
|
|
68
|
-
/** Re-renders when Turbo Morph or application code changes `index` at runtime. */
|
|
69
145
|
indexValueChanged() {
|
|
70
146
|
this.#repaint.schedule();
|
|
71
147
|
}
|
|
72
|
-
/** Re-derives every step's state for a step added at runtime. */
|
|
73
148
|
stepTargetConnected() {
|
|
74
149
|
this.#repaint.schedule();
|
|
75
150
|
}
|
|
76
|
-
/** Re-derives every step's state for a step removed at runtime. */
|
|
77
151
|
stepTargetDisconnected() {
|
|
78
152
|
this.#repaint.schedule();
|
|
79
153
|
}
|
|
80
|
-
/** Advances to the next step (ignored at the last step). */
|
|
81
154
|
next() {
|
|
82
|
-
this.#moveTo(this.#clampIndex(this
|
|
155
|
+
this.#moveTo(this.#clampIndex(this.#safeIndex) + 1);
|
|
83
156
|
}
|
|
84
|
-
/** Returns to the previous step (ignored at the first step). */
|
|
85
157
|
prev() {
|
|
86
|
-
this.#moveTo(this.#clampIndex(this
|
|
158
|
+
this.#moveTo(this.#clampIndex(this.#safeIndex) - 1);
|
|
87
159
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Jumps to the step carried in the action's `index` param. A param that is empty,
|
|
90
|
-
* or that Stimulus parses into anything but a number or a string, is no index,
|
|
91
|
-
* so it jumps nowhere.
|
|
92
|
-
*/
|
|
93
160
|
goto(event) {
|
|
94
161
|
const { index } = event.params;
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
const target = Number(index);
|
|
98
|
-
if (!Number.isFinite(target) || !Number.isInteger(target)) return;
|
|
162
|
+
const target = toFiniteNumber(index);
|
|
163
|
+
if (target === null || !Number.isInteger(target)) return;
|
|
99
164
|
this.#moveTo(target);
|
|
100
165
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Moves the current step to `target` when allowed: in range, not a no-op, and
|
|
103
|
-
* — under `linear` — not skipping more than one step ahead. Re-renders and
|
|
104
|
-
* dispatches `change`.
|
|
105
|
-
*/
|
|
106
166
|
#moveTo(target) {
|
|
107
167
|
const total = this.stepTargets.length;
|
|
108
|
-
if (!Number.isFinite(target) || !Number.isInteger(target)) return;
|
|
109
168
|
if (target < 0 || target >= total) return;
|
|
110
|
-
const current = this.#clampIndex(this
|
|
111
|
-
if (target === current) return;
|
|
169
|
+
const current = this.#clampIndex(this.#safeIndex);
|
|
170
|
+
if (target === current && target === this.#shown) return;
|
|
112
171
|
if (this.linearValue && target > current + 1) return;
|
|
113
|
-
const previous = current;
|
|
172
|
+
const previous = this.#shown ?? current;
|
|
114
173
|
this.indexValue = target;
|
|
115
174
|
this.#shown = target;
|
|
116
175
|
this.#render();
|
|
176
|
+
if (target === previous) return;
|
|
117
177
|
this.dispatch("change", {
|
|
118
178
|
detail: { index: target, previous, total, step: this.stepTargets[target] }
|
|
119
179
|
});
|
|
120
180
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Re-derives one settled batch of Value and target changes, and reports a
|
|
123
|
-
* current position that moved from the one shown before as `reconcile`. With no
|
|
124
|
-
* step present there is nothing to report, and the position shown last stays the
|
|
125
|
-
* one the next move is measured from.
|
|
126
|
-
*/
|
|
127
181
|
#reconcileStep() {
|
|
128
182
|
const previous = this.#shown;
|
|
129
183
|
const index = this.#currentStep;
|
|
@@ -135,25 +189,11 @@ var StepperController = class extends Controller {
|
|
|
135
189
|
detail: { index, previous, total: steps.length, step: steps[index] }
|
|
136
190
|
});
|
|
137
191
|
}
|
|
138
|
-
/** The current position, clamped into the step range, or `null` when there is no step. */
|
|
139
192
|
get #currentStep() {
|
|
140
|
-
return this.stepTargets.length > 0 ? this.#clampIndex(this
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Derives each step's `data-state` and the current button's `aria-current` from
|
|
144
|
-
* `index`, clamped into the step range, without dispatching an action event.
|
|
145
|
-
*
|
|
146
|
-
* The clamp decides only what is shown. This body never writes `index`, so an
|
|
147
|
-
* out-of-range declaration stays in the attribute as the page wrote it.
|
|
148
|
-
*
|
|
149
|
-
* `aria-current="step"` is placed on the step's **first** `<button>`; the markup
|
|
150
|
-
* contract assumes one operable button per step. If a step needs multiple
|
|
151
|
-
* buttons, mark the navigational one first (or this would target the wrong one).
|
|
152
|
-
*
|
|
153
|
-
* @stimeoRenderRoot
|
|
154
|
-
*/
|
|
193
|
+
return this.stepTargets.length > 0 ? this.#clampIndex(this.#safeIndex) : null;
|
|
194
|
+
}
|
|
155
195
|
#render() {
|
|
156
|
-
const current = this.#clampIndex(this
|
|
196
|
+
const current = this.#clampIndex(this.#safeIndex);
|
|
157
197
|
this.stepTargets.forEach((step, index) => {
|
|
158
198
|
step.dataset.state = index < current ? "complete" : index === current ? "current" : "upcoming";
|
|
159
199
|
const button = step.querySelector("button");
|
|
@@ -165,14 +205,21 @@ var StepperController = class extends Controller {
|
|
|
165
205
|
}
|
|
166
206
|
});
|
|
167
207
|
}
|
|
168
|
-
/** Constrains an index to `[0, total-1]` (or `0` when there are no steps). */
|
|
169
208
|
#clampIndex(index) {
|
|
170
209
|
const last = this.stepTargets.length - 1;
|
|
171
|
-
if (last < 0
|
|
210
|
+
if (last < 0) return 0;
|
|
172
211
|
return Math.min(last, Math.max(0, Math.trunc(index)));
|
|
173
212
|
}
|
|
213
|
+
get #safeIndex() {
|
|
214
|
+
return this.#numbers.read(
|
|
215
|
+
this,
|
|
216
|
+
"index",
|
|
217
|
+
this.indexValue,
|
|
218
|
+
_StepperController.values.index.default,
|
|
219
|
+
_StepperController.valueConstraints.index
|
|
220
|
+
);
|
|
221
|
+
}
|
|
174
222
|
};
|
|
175
223
|
|
|
176
224
|
export { StepperController };
|
|
177
|
-
//# sourceMappingURL=stepper_controller.js.map
|
|
178
225
|
//# sourceMappingURL=stepper_controller.js.map
|