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,15 +1,11 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/form_validation_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/default_attribute.ts
|
|
6
3
|
function setDefaultAttribute(element, name, value) {
|
|
7
4
|
if (element.hasAttribute(name)) return false;
|
|
8
5
|
element.setAttribute(name, value);
|
|
9
6
|
return true;
|
|
10
7
|
}
|
|
11
8
|
|
|
12
|
-
// src/utils/focus_candidate.ts
|
|
13
9
|
function inheritsFieldsetDisabled(control) {
|
|
14
10
|
let fieldset = control.closest("fieldset[disabled]");
|
|
15
11
|
while (fieldset) {
|
|
@@ -19,8 +15,19 @@ function inheritsFieldsetDisabled(control) {
|
|
|
19
15
|
}
|
|
20
16
|
return false;
|
|
21
17
|
}
|
|
18
|
+
function unslotted(element) {
|
|
19
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
20
|
+
}
|
|
21
|
+
function flatTreeParent(element) {
|
|
22
|
+
if (unslotted(element)) return null;
|
|
23
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
24
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
25
|
+
return parent instanceof Element ? parent : null;
|
|
26
|
+
}
|
|
22
27
|
function canTakeFocus(element) {
|
|
23
|
-
|
|
28
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
29
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
30
|
+
}
|
|
24
31
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
25
32
|
if (!("disabled" in element)) return true;
|
|
26
33
|
if (element.disabled) return false;
|
|
@@ -84,7 +91,6 @@ function firstTabStop(root) {
|
|
|
84
91
|
return null;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
// src/controllers/form_validation_controller.ts
|
|
88
94
|
var CONSTRAINT_MESSAGE_KEYS = [
|
|
89
95
|
["valueMissing", "value-missing"],
|
|
90
96
|
["typeMismatch", "type-mismatch"],
|
|
@@ -101,11 +107,9 @@ var DISALLOW_PART = "disallow";
|
|
|
101
107
|
var DISALLOW_WHITESPACE_DEFAULT = "Please enter a value that is not only whitespace.";
|
|
102
108
|
var FORM_FIELD_INVALID_ATTR = "data-stimeo--form-field-invalid";
|
|
103
109
|
var FormValidationController = class _FormValidationController extends Controller {
|
|
104
|
-
/** The generic message override, in the namespace this controller is registered under. */
|
|
105
110
|
get #messageAttribute() {
|
|
106
111
|
return `data-${this.identifier}-${MESSAGE_PART}`;
|
|
107
112
|
}
|
|
108
|
-
/** Marks the `novalidate` this controller applied, so an authored one is left alone. */
|
|
109
113
|
get #novalidateMarker() {
|
|
110
114
|
return `data-${this.identifier}-${_FormValidationController.#NOVALIDATE_PART}`;
|
|
111
115
|
}
|
|
@@ -118,22 +122,11 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
118
122
|
};
|
|
119
123
|
static actions = ["validate"];
|
|
120
124
|
static events = ["valid", "invalid"];
|
|
121
|
-
/** Suffix of the marker recording that we added `novalidate`, so we only remove our own. */
|
|
122
125
|
static #NOVALIDATE_PART = "novalidate";
|
|
123
|
-
/** Object-backed groups already interacted with — the input revalidation gate. */
|
|
124
126
|
#touchedGroups = /* @__PURE__ */ new WeakSet();
|
|
125
|
-
/** String-backed fallback radio groups already interacted with. */
|
|
126
127
|
#touchedRadioGroups = /* @__PURE__ */ new Set();
|
|
127
|
-
/**
|
|
128
|
-
* Last message this controller wrote through `setCustomValidity`, kept as a
|
|
129
|
-
* two-layer ownership ledger: a value is cleared only while the live message
|
|
130
|
-
* still equals the recorded write. Iterable so disconnect can release every
|
|
131
|
-
* surviving loan without touching a consumer's later custom error.
|
|
132
|
-
*/
|
|
133
128
|
#ownedCustomErrors = /* @__PURE__ */ new Map();
|
|
134
|
-
/** Last invalid message routed to each field, suppressing duplicate reports. */
|
|
135
129
|
#reportedErrors = /* @__PURE__ */ new WeakMap();
|
|
136
|
-
/** Exact document that owns the delegated listeners for this connection. */
|
|
137
130
|
#listenerDocument = null;
|
|
138
131
|
#onSubmit = (event) => {
|
|
139
132
|
if (event.target !== this.element) return;
|
|
@@ -172,7 +165,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
172
165
|
this.#markTouched(group.key);
|
|
173
166
|
this.#applyGroup(group);
|
|
174
167
|
};
|
|
175
|
-
/** Suppresses native bubbles and binds the submit / blur / input listeners. */
|
|
176
168
|
connect() {
|
|
177
169
|
if (setDefaultAttribute(this.element, "novalidate", "")) {
|
|
178
170
|
this.element.setAttribute(this.#novalidateMarker, "");
|
|
@@ -183,7 +175,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
183
175
|
this.#listenerDocument.addEventListener("input", this.#onInput);
|
|
184
176
|
this.#listenerDocument.addEventListener("change", this.#onChange);
|
|
185
177
|
}
|
|
186
|
-
/** Tears down listeners and restores `novalidate` if we added it. */
|
|
187
178
|
disconnect() {
|
|
188
179
|
this.#listenerDocument?.removeEventListener("submit", this.#onSubmit, true);
|
|
189
180
|
this.#listenerDocument?.removeEventListener("focusout", this.#onFocusOut);
|
|
@@ -194,28 +185,14 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
194
185
|
if (control.validationMessage === message) control.setCustomValidity("");
|
|
195
186
|
}
|
|
196
187
|
this.#ownedCustomErrors.clear();
|
|
197
|
-
this.#touchedRadioGroups.clear();
|
|
198
188
|
if (this.element.hasAttribute(this.#novalidateMarker)) {
|
|
199
189
|
this.element.removeAttribute("novalidate");
|
|
200
190
|
this.element.removeAttribute(this.#novalidateMarker);
|
|
201
191
|
}
|
|
202
192
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Validates every control now, rendering or clearing each field's message, and
|
|
205
|
-
* returns whether the whole form is valid. Marks every field/group touched so
|
|
206
|
-
* a later input from any sibling re-validates it. Bound via `data-action`
|
|
207
|
-
* (`#validate`) or callable directly (e.g. before a programmatic submit).
|
|
208
|
-
*/
|
|
209
193
|
validate() {
|
|
210
194
|
return this.#validateAll().length === 0;
|
|
211
195
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Validates every control and returns one invalid control per field. Controls
|
|
214
|
-
* are grouped by field first (see {@link #keyFor}) so a field with several
|
|
215
|
-
* controls — a radio group, or a mirror plus its visible control — reflects
|
|
216
|
-
* *all* of them: a valid sibling must never clear an invalid one's message.
|
|
217
|
-
* Each group's first invalid control supplies the message and the focus target.
|
|
218
|
-
*/
|
|
219
196
|
#validateAll() {
|
|
220
197
|
const invalid = [];
|
|
221
198
|
for (const group of this.#snapshot().groups.values()) {
|
|
@@ -225,7 +202,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
225
202
|
}
|
|
226
203
|
return invalid;
|
|
227
204
|
}
|
|
228
|
-
/** Builds the current field table in one pass over form controls and DOM depth. */
|
|
229
205
|
#snapshot() {
|
|
230
206
|
const fields = this.#fieldOutlets();
|
|
231
207
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -243,7 +219,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
243
219
|
}
|
|
244
220
|
return { groups, groupByControl };
|
|
245
221
|
}
|
|
246
|
-
/** Records that a whole field/group, rather than one sibling control, was visited. */
|
|
247
222
|
#markTouched(key) {
|
|
248
223
|
if (typeof key === "string") {
|
|
249
224
|
this.#touchedRadioGroups.add(key);
|
|
@@ -254,13 +229,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
254
229
|
#isTouched(key) {
|
|
255
230
|
return typeof key === "string" ? this.#touchedRadioGroups.has(key) : this.#touchedGroups.has(key);
|
|
256
231
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Runs native constraint validation across a field's controls and routes the
|
|
259
|
-
* result to its `stimeo--form-field` outlet: the first invalid control's
|
|
260
|
-
* `validationMessage` is shown, an all-valid field is cleared. Returns the
|
|
261
|
-
* first invalid control (for the invalid list / focus), or `null` when valid.
|
|
262
|
-
* Routing goes through the outlet, so the ARIA wiring is never duplicated here.
|
|
263
|
-
*/
|
|
264
232
|
#applyGroup(group) {
|
|
265
233
|
for (const control of group.controls) this.#syncCustomValidity(control);
|
|
266
234
|
const firstInvalid = group.controls.find((control) => !control.checkValidity()) ?? null;
|
|
@@ -277,14 +245,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
277
245
|
}
|
|
278
246
|
return firstInvalid;
|
|
279
247
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Resolves the message to show for an invalid control: a per-constraint
|
|
282
|
-
* override (`data-stimeo--form-validation-message-<constraint>`) for the first failing
|
|
283
|
-
* `ValidityState` flag, then a generic `data-stimeo--form-validation-message`
|
|
284
|
-
* override, then the browser's native `validationMessage`. Authoring an override
|
|
285
|
-
* gives controlled, localizable, theme-able wording with **no consumer JS** —
|
|
286
|
-
* and sidesteps headless browsers that return an empty native message.
|
|
287
|
-
*/
|
|
288
248
|
#messageFor(control) {
|
|
289
249
|
if (control.validity.customError) return control.validationMessage;
|
|
290
250
|
for (const [flag, key] of CONSTRAINT_MESSAGE_KEYS) {
|
|
@@ -294,17 +254,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
294
254
|
}
|
|
295
255
|
return control.validationMessage || this.#authoredMessage(control, this.#messageAttribute) || "";
|
|
296
256
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Applies (or clears) a declarative custom constraint via `setCustomValidity`,
|
|
299
|
-
* for controls that opt in with `data-stimeo--form-validation-disallow`. The supported
|
|
300
|
-
* rule is `"whitespace"` — a value that is non-empty but blank
|
|
301
|
-
* after trimming (which slips past `required` / `minlength`); its message follows
|
|
302
|
-
* the whitespace-specific → generic → default chain.
|
|
303
|
-
*
|
|
304
|
-
* Don't-clobber-authored-state: an unknown/absent rule is never written, an
|
|
305
|
-
* existing consumer custom error wins, and a controller error is only cleared
|
|
306
|
-
* while the live message still equals the recorded controller write.
|
|
307
|
-
*/
|
|
308
257
|
#syncCustomValidity(control) {
|
|
309
258
|
const recorded = this.#ownedCustomErrors.get(control);
|
|
310
259
|
const ownsCurrent = recorded !== void 0 && control.validationMessage === recorded;
|
|
@@ -319,16 +268,10 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
319
268
|
if (!ownsCurrent || recorded !== message) control.setCustomValidity(message);
|
|
320
269
|
this.#ownedCustomErrors.set(control, message);
|
|
321
270
|
}
|
|
322
|
-
/** Returns a non-blank authored message, otherwise falls through to a fallback. */
|
|
323
271
|
#authoredMessage(control, attribute) {
|
|
324
272
|
const message = control.getAttribute(attribute);
|
|
325
273
|
return message && message.trim().length > 0 ? message : null;
|
|
326
274
|
}
|
|
327
|
-
/**
|
|
328
|
-
* A grouping key that collects controls belonging to the same field: the owning
|
|
329
|
-
* `stimeo--form-field` when present, else a radio group's shared `name`, else
|
|
330
|
-
* the control itself (always distinct).
|
|
331
|
-
*/
|
|
332
275
|
#keyFor(control, field) {
|
|
333
276
|
if (field) return field;
|
|
334
277
|
if (control instanceof HTMLInputElement && control.type === "radio" && control.name) {
|
|
@@ -336,15 +279,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
336
279
|
}
|
|
337
280
|
return control;
|
|
338
281
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Where focus should land for an invalid control. A visible control is focused
|
|
341
|
-
* directly — the case for native fields and radios. A validatable mirror
|
|
342
|
-
* (the `hidden` attribute) cannot receive focus, so focus is delegated to the
|
|
343
|
-
* visible widget: the owning field's `control` target when it is itself
|
|
344
|
-
* focusable, else its first focusable descendant (e.g. a roving-tabindex
|
|
345
|
-
* member). Resolved structurally — never by probing `focus()` — so behavior
|
|
346
|
-
* is deterministic and CSS-independent.
|
|
347
|
-
*/
|
|
348
282
|
#focusTargetFor(control) {
|
|
349
283
|
if (canTakeFocus(control)) return control;
|
|
350
284
|
const field = this.#fieldFor(control, this.#fieldOutlets());
|
|
@@ -353,7 +287,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
353
287
|
if (isTabStop(root)) return root;
|
|
354
288
|
return firstTabStop(root);
|
|
355
289
|
}
|
|
356
|
-
/** Pairs each live outlet element with its controller once per operation. */
|
|
357
290
|
#fieldOutlets() {
|
|
358
291
|
const fields = /* @__PURE__ */ new Map();
|
|
359
292
|
const elements = this.stimeoFormFieldOutletElements;
|
|
@@ -365,7 +298,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
365
298
|
}
|
|
366
299
|
return fields;
|
|
367
300
|
}
|
|
368
|
-
/** The nearest configured field ancestor of `control`, if any. */
|
|
369
301
|
#fieldFor(control, fields) {
|
|
370
302
|
let ancestor = control;
|
|
371
303
|
while (ancestor) {
|
|
@@ -375,7 +307,6 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
375
307
|
}
|
|
376
308
|
return void 0;
|
|
377
309
|
}
|
|
378
|
-
/** This form's native controls that participate in constraint validation. */
|
|
379
310
|
get #controls() {
|
|
380
311
|
const controls = [];
|
|
381
312
|
for (const element of Array.from(this.element.elements)) {
|
|
@@ -383,18 +314,14 @@ var FormValidationController = class _FormValidationController extends Controlle
|
|
|
383
314
|
}
|
|
384
315
|
return controls;
|
|
385
316
|
}
|
|
386
|
-
/** Narrows an event target to a validatable control. */
|
|
387
317
|
#controlFrom(target) {
|
|
388
318
|
return target instanceof Element && this.#isValidatable(target) && target.form === this.element ? target : null;
|
|
389
319
|
}
|
|
390
320
|
#isValidatable(element) {
|
|
391
|
-
return (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) &&
|
|
392
|
-
// and button-type controls. The `hidden` attribute intentionally does not
|
|
393
|
-
// bar a text mirror from constraint validation.
|
|
321
|
+
return (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) &&
|
|
394
322
|
element.willValidate;
|
|
395
323
|
}
|
|
396
324
|
};
|
|
397
325
|
|
|
398
326
|
export { FormValidationController };
|
|
399
|
-
//# sourceMappingURL=form_validation_controller.js.map
|
|
400
327
|
//# sourceMappingURL=form_validation_controller.js.map
|