stimeo-ui 0.4.0 → 0.6.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.
- package/CHANGELOG.md +184 -0
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +3 -5
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +195 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +5 -1
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +90 -10
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +31 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.js +8 -3
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js +4 -1
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +9 -3
- package/dist/controllers/color_picker_controller.js +41 -11
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +1 -1
- package/dist/controllers/context_menu_controller.js +2 -2
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +5 -3
- package/dist/controllers/countdown_controller.js +5 -1
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +162 -31
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +1 -1
- package/dist/controllers/direct_upload_controller.js +3 -3
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +27 -7
- package/dist/controllers/empty_state_controller.js +107 -16
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +7 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +25 -5
- package/dist/controllers/flash_controller.js +161 -21
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -3
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +8 -2
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +19 -2
- package/dist/controllers/frame_loading_controller.js +94 -22
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +8 -4
- package/dist/controllers/highlight_controller.js +38 -1
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -4
- package/dist/controllers/idle_controller.js +39 -6
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.js +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +1 -1
- package/dist/controllers/masonry_controller.js +1 -1
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +3 -1
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +46 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +15 -13
- package/dist/controllers/network_status_controller.js +1 -3
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +29 -3
- package/dist/controllers/number_input_controller.js +455 -64
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +5 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +38 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +4 -4
- package/dist/controllers/password_strength_controller.js +21 -3
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +4 -4
- package/dist/controllers/persist_controller.js +24 -5
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +10 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -2
- package/dist/controllers/portal_controller.js +10 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +1 -1
- package/dist/controllers/progress_controller.js +7 -3
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +41 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +23 -0
- package/dist/controllers/range_slider_controller.js +385 -94
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +36 -31
- package/dist/controllers/rating_controller.js +274 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +2 -0
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +4 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -1
- package/dist/controllers/scroll_area_controller.js +155 -23
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/separator_controller.js +13 -17
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -2
- package/dist/controllers/skeleton_controller.js +71 -3
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +17 -1
- package/dist/controllers/slider_controller.js +325 -48
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +15 -10
- package/dist/controllers/spinner_controller.js +18 -3
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +1 -1
- package/dist/controllers/step_indicator_controller.js +3 -1
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.js +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -8
- package/dist/controllers/switch_controller.js +162 -18
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +41 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.js +1 -1
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +42 -10
- package/dist/controllers/time_picker_controller.js +296 -104
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +39 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +1 -2
- package/dist/controllers/tree_view_controller.js +24 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +3811 -1048
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +92 -4
- package/dist/inspector/cli.js +174 -6
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +234 -8
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +24 -24
- package/dist/inspector/manifest.json +573 -59
- package/dist/positioning/index.d.ts +2 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +3 -3
package/dist/inspector/cli.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ interface ControllerManifest {
|
|
|
23
23
|
readonly targets: readonly string[];
|
|
24
24
|
/** Value names (camelCase) declared via `static values`. */
|
|
25
25
|
readonly values: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Authoring constraints for literal Stimulus Values (stage 1). Reflection can
|
|
28
|
+
* expose a Value's name and decoder, but not semantic bounds such as a
|
|
29
|
+
* strictly positive step, so those contracts stay explicit and reviewable.
|
|
30
|
+
*/
|
|
31
|
+
readonly valueConstraints: readonly ValueConstraint[];
|
|
32
|
+
/** Statically decidable relationships between literal Stimulus Values. */
|
|
33
|
+
readonly valueRelations: readonly ValueRelation[];
|
|
26
34
|
/**
|
|
27
35
|
* Public action method names declared via `static actions`, wired by
|
|
28
36
|
* consumers as `data-action="…-><identifier>#<action>"`.
|
|
@@ -57,6 +65,13 @@ interface ControllerManifest {
|
|
|
57
65
|
* toolbar, radio group, …) are deliberately excluded.
|
|
58
66
|
*/
|
|
59
67
|
readonly keyboard: readonly KeyboardRequirement[];
|
|
68
|
+
/**
|
|
69
|
+
* Host-element contracts (stage 3): scope or target elements that must avoid
|
|
70
|
+
* native interactive semantics the controller cannot safely compose with.
|
|
71
|
+
* A rule may explicitly admit non-submitting buttons when the controller
|
|
72
|
+
* delegates their native activation to the browser.
|
|
73
|
+
*/
|
|
74
|
+
readonly hosts: readonly HostRequirement[];
|
|
60
75
|
/**
|
|
61
76
|
* Author-futile attributes (stage 3): ARIA the controller recomputes
|
|
62
77
|
* wholesale at runtime, where an authored value can neither
|
|
@@ -212,9 +227,11 @@ interface DocumentCondition {
|
|
|
212
227
|
* A single accessibility requirement on a controller's markup (Inspector
|
|
213
228
|
* stage 3). At least one of {@link attrs} must be present on the
|
|
214
229
|
* {@link target} element; when {@link values} is given, the present
|
|
215
|
-
* attribute's value must be one of them
|
|
216
|
-
*
|
|
217
|
-
*
|
|
230
|
+
* attribute's value must be one of them, and when it is not, the value must be
|
|
231
|
+
* non-empty — ARIA derives no name from `aria-label=""`, so an empty value
|
|
232
|
+
* leaves the requirement unmet. {@link or} widens the requirement with
|
|
233
|
+
* alternative attribute/value groups — the requirement is satisfied when *any*
|
|
234
|
+
* group is (e.g. `role="status"` **or** `aria-live`).
|
|
218
235
|
*/
|
|
219
236
|
interface A11yRequirement {
|
|
220
237
|
/**
|
|
@@ -328,6 +345,27 @@ interface KeyboardRequirement {
|
|
|
328
345
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
329
346
|
readonly suggestion: string;
|
|
330
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* A host-element contract (stage 3): the element carrying a controller or one
|
|
350
|
+
* of its targets must use semantics the controller can own without competing
|
|
351
|
+
* with another native interaction.
|
|
352
|
+
*
|
|
353
|
+
* `"non-interactive"` admits only generic, non-interactive elements.
|
|
354
|
+
* `"non-interactive-or-button"` additionally admits a `<button>` whose
|
|
355
|
+
* literal `type` is listed in {@link buttonTypes}. The explicit type matters:
|
|
356
|
+
* a missing or invalid button type defaults to `submit`, so treating it as an
|
|
357
|
+
* ordinary action button would silently submit an enclosing form.
|
|
358
|
+
*/
|
|
359
|
+
interface HostRequirement {
|
|
360
|
+
/** Target name; the empty string `""` means the controller scope element. */
|
|
361
|
+
readonly target: string;
|
|
362
|
+
/** Static host shapes the controller supports. */
|
|
363
|
+
readonly mode: "non-interactive" | "non-interactive-or-button";
|
|
364
|
+
/** Literal button types admitted by `"non-interactive-or-button"`. */
|
|
365
|
+
readonly buttonTypes?: readonly string[];
|
|
366
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
367
|
+
readonly suggestion: string;
|
|
368
|
+
}
|
|
331
369
|
/**
|
|
332
370
|
* An author-futile attribute rule (stage 3): authoring any of {@link attrs} on
|
|
333
371
|
* the {@link target} element draws a `managed-aria` **warning** — the
|
|
@@ -561,6 +599,56 @@ interface ConditionalTargetRule {
|
|
|
561
599
|
readonly whenPresent: string;
|
|
562
600
|
/** Targets that become required once {@link whenPresent} appears. */
|
|
563
601
|
readonly require: readonly string[];
|
|
602
|
+
/**
|
|
603
|
+
* Require each {@link require} entry to sit **inside** the `whenPresent`
|
|
604
|
+
* element rather than anywhere in the scope.
|
|
605
|
+
*
|
|
606
|
+
* A `<template>` is cloned at runtime, so a part declared outside it never
|
|
607
|
+
* reaches the clone. Without this the presence check passes on markup the
|
|
608
|
+
* controller then refuses to build from.
|
|
609
|
+
*/
|
|
610
|
+
readonly requireInside?: boolean;
|
|
611
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
612
|
+
readonly suggestion: string;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* A statically checkable numeric contract on one declared Stimulus Value.
|
|
616
|
+
*
|
|
617
|
+
* The Inspector decodes literals like Stimulus (`Number(raw.replace(/_/g,
|
|
618
|
+
* ""))`) before applying these bounds. ERB-generated values are undecidable and
|
|
619
|
+
* skipped rather than guessed.
|
|
620
|
+
*/
|
|
621
|
+
interface ValueConstraint {
|
|
622
|
+
/** Value name (camelCase, as declared in `static values`). */
|
|
623
|
+
readonly value: string;
|
|
624
|
+
/** Decoder family. Numeric bounds are the first supported semantic family. */
|
|
625
|
+
readonly type: "number";
|
|
626
|
+
/** Reject `NaN` and infinities when true. */
|
|
627
|
+
readonly finite?: boolean;
|
|
628
|
+
/** Require a decoded number strictly greater than this bound. */
|
|
629
|
+
readonly greaterThan?: number;
|
|
630
|
+
/** Require a decoded number with no fractional part. */
|
|
631
|
+
readonly integer?: boolean;
|
|
632
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
633
|
+
readonly suggestion: string;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* A statically checkable relationship between two numeric Stimulus Values.
|
|
637
|
+
*
|
|
638
|
+
* Missing attributes use the controller's public defaults. If either authored
|
|
639
|
+
* value is dynamic, the Inspector skips the relation rather than guessing.
|
|
640
|
+
*/
|
|
641
|
+
interface ValueRelation {
|
|
642
|
+
/** Value on the left side of the comparison (camelCase). */
|
|
643
|
+
readonly left: string;
|
|
644
|
+
/** Supported numeric comparison. */
|
|
645
|
+
readonly operator: "less-than-or-equal";
|
|
646
|
+
/** Value on the right side of the comparison (camelCase). */
|
|
647
|
+
readonly right: string;
|
|
648
|
+
/** Effective left value when its attribute is absent. */
|
|
649
|
+
readonly leftDefault: number;
|
|
650
|
+
/** Effective right value when its attribute is absent. */
|
|
651
|
+
readonly rightDefault: number;
|
|
564
652
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
565
653
|
readonly suggestion: string;
|
|
566
654
|
}
|
|
@@ -571,7 +659,7 @@ type DiagnosticSeverity = "error" | "warning";
|
|
|
571
659
|
* for testing, machine-readable output, and `data-stimeo-ignore` suppression
|
|
572
660
|
* lists (which is why the set also exists as a runtime array).
|
|
573
661
|
*/
|
|
574
|
-
declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "unknown-ignore-code"];
|
|
662
|
+
declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "invalid-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "unknown-ignore-code"];
|
|
575
663
|
/** See {@link DIAGNOSTIC_CODES}. */
|
|
576
664
|
type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
|
|
577
665
|
/**
|
package/dist/inspector/cli.js
CHANGED
|
@@ -610,6 +610,26 @@ function readEntry(code, start, base) {
|
|
|
610
610
|
};
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
|
+
const number = /^[+-]?\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?/.exec(
|
|
614
|
+
code.slice(valueStart)
|
|
615
|
+
)?.[0];
|
|
616
|
+
if (number !== void 0) {
|
|
617
|
+
const end = valueStart + number.length;
|
|
618
|
+
const after = skipTrivia(code, end);
|
|
619
|
+
const whole = after >= code.length || code[after] === "," || code[after] === "}";
|
|
620
|
+
if (whole) {
|
|
621
|
+
return {
|
|
622
|
+
attr: {
|
|
623
|
+
name,
|
|
624
|
+
value: number,
|
|
625
|
+
...anchor,
|
|
626
|
+
valueStart: base + valueStart,
|
|
627
|
+
valueEnd: base + end
|
|
628
|
+
},
|
|
629
|
+
end
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
}
|
|
613
633
|
return { attr: { name, value: null, ...anchor }, end: skipValue(code, valueStart) };
|
|
614
634
|
}
|
|
615
635
|
function readKey(code, start) {
|
|
@@ -926,6 +946,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
926
946
|
"unknown-controller",
|
|
927
947
|
"unknown-target",
|
|
928
948
|
"unknown-value",
|
|
949
|
+
"invalid-value",
|
|
929
950
|
"unknown-action-controller",
|
|
930
951
|
"unknown-action-method",
|
|
931
952
|
"orphan-target",
|
|
@@ -934,6 +955,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
934
955
|
"missing-aria",
|
|
935
956
|
"invalid-aria-value",
|
|
936
957
|
"keyboard-inaccessible",
|
|
958
|
+
"invalid-host",
|
|
937
959
|
"unresolved-idref",
|
|
938
960
|
"managed-aria",
|
|
939
961
|
"composition-mismatch",
|
|
@@ -945,6 +967,12 @@ var DIAGNOSTIC_CODES = [
|
|
|
945
967
|
];
|
|
946
968
|
|
|
947
969
|
// src/inspector/check.ts
|
|
970
|
+
function isInside(node, ancestor) {
|
|
971
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
972
|
+
if (current === ancestor) return true;
|
|
973
|
+
}
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
948
976
|
function checkSource(source, manifest) {
|
|
949
977
|
const diagnostics = [];
|
|
950
978
|
const tree = parseHtml(neutralizeErb(source), erbElements(source));
|
|
@@ -965,6 +993,36 @@ function checkSource(source, manifest) {
|
|
|
965
993
|
if (hostAttr && isDynamicValue(hostAttr)) return null;
|
|
966
994
|
return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
|
|
967
995
|
};
|
|
996
|
+
const isContentEditable2 = (element) => {
|
|
997
|
+
let current = element;
|
|
998
|
+
while (current && current.tag !== "#root") {
|
|
999
|
+
const attr = current.attrs.find((candidate) => candidate.name === "contenteditable");
|
|
1000
|
+
if (attr) {
|
|
1001
|
+
if (isDynamicValue(attr)) return null;
|
|
1002
|
+
const value = attr.value.trim().toLowerCase();
|
|
1003
|
+
if (value === "false") return false;
|
|
1004
|
+
if (value === "" || value === "true" || value === "plaintext-only") return true;
|
|
1005
|
+
}
|
|
1006
|
+
current = current.parent;
|
|
1007
|
+
}
|
|
1008
|
+
return false;
|
|
1009
|
+
};
|
|
1010
|
+
const hostIsAllowed = (element, rule) => {
|
|
1011
|
+
if (element.tag === "button") {
|
|
1012
|
+
if (rule.mode === "non-interactive") return false;
|
|
1013
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1014
|
+
if (type && isDynamicValue(type)) return null;
|
|
1015
|
+
const effectiveType = type?.value.trim().toLowerCase() || "submit";
|
|
1016
|
+
return (rule.buttonTypes ?? []).includes(effectiveType);
|
|
1017
|
+
}
|
|
1018
|
+
if (ALWAYS_INTERACTIVE_HOSTS.has(element.tag)) return false;
|
|
1019
|
+
if ((element.tag === "a" || element.tag === "area") && hasAttr(element, "href")) return false;
|
|
1020
|
+
if ((element.tag === "audio" || element.tag === "video") && hasAttr(element, "controls")) {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
const editable = isContentEditable2(element);
|
|
1024
|
+
return editable === null ? null : !editable;
|
|
1025
|
+
};
|
|
968
1026
|
const suppressed = (node, code) => {
|
|
969
1027
|
if (code === "unknown-ignore-code") return false;
|
|
970
1028
|
let current = node;
|
|
@@ -1072,6 +1130,7 @@ function checkSource(source, manifest) {
|
|
|
1072
1130
|
focusableRoleTally.set(role, (focusableRoleTally.get(role) ?? 0) + 1);
|
|
1073
1131
|
}
|
|
1074
1132
|
}
|
|
1133
|
+
const authoredValueIdentifiers = /* @__PURE__ */ new Set();
|
|
1075
1134
|
for (const attr of node.attrs) {
|
|
1076
1135
|
if (attr.name === IGNORE_ATTR) {
|
|
1077
1136
|
for (const token of attr.value.split(/\s+/).filter((t) => t.length > 0)) {
|
|
@@ -1196,6 +1255,7 @@ function checkSource(source, manifest) {
|
|
|
1196
1255
|
attr
|
|
1197
1256
|
);
|
|
1198
1257
|
} else {
|
|
1258
|
+
authoredValueIdentifiers.add(parsed.identifier);
|
|
1199
1259
|
const controller = known[parsed.identifier];
|
|
1200
1260
|
const valid = controller?.values.some((v) => dasherize(v) === parsed.valueToken) ?? false;
|
|
1201
1261
|
if (!valid) {
|
|
@@ -1207,6 +1267,20 @@ function checkSource(source, manifest) {
|
|
|
1207
1267
|
attr,
|
|
1208
1268
|
didYouMean(parsed.valueToken, (controller?.values ?? []).map(dasherize))
|
|
1209
1269
|
);
|
|
1270
|
+
} else if (!isDynamicValue(attr)) {
|
|
1271
|
+
const constraint = controller?.valueConstraints.find(
|
|
1272
|
+
(candidate) => dasherize(candidate.value) === parsed.valueToken
|
|
1273
|
+
);
|
|
1274
|
+
if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
|
|
1275
|
+
report(
|
|
1276
|
+
node,
|
|
1277
|
+
"invalid-value",
|
|
1278
|
+
"error",
|
|
1279
|
+
`Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
|
|
1280
|
+
attr,
|
|
1281
|
+
constraint.suggestion
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1210
1284
|
}
|
|
1211
1285
|
}
|
|
1212
1286
|
continue;
|
|
@@ -1241,6 +1315,34 @@ function checkSource(source, manifest) {
|
|
|
1241
1315
|
}
|
|
1242
1316
|
}
|
|
1243
1317
|
}
|
|
1318
|
+
for (const identifier of authoredValueIdentifiers) {
|
|
1319
|
+
const controller = known[identifier];
|
|
1320
|
+
if (!controller) continue;
|
|
1321
|
+
for (const relation of controller.valueRelations) {
|
|
1322
|
+
const leftAttr = node.attrs.find(
|
|
1323
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.left)}-value`
|
|
1324
|
+
);
|
|
1325
|
+
const rightAttr = node.attrs.find(
|
|
1326
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.right)}-value`
|
|
1327
|
+
);
|
|
1328
|
+
const at = rightAttr ?? leftAttr;
|
|
1329
|
+
if (!at || leftAttr && isDynamicValue(leftAttr) || rightAttr && isDynamicValue(rightAttr)) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const left = leftAttr ? decodeNumericValue(leftAttr.value) : relation.leftDefault;
|
|
1333
|
+
const right = rightAttr ? decodeNumericValue(rightAttr.value) : relation.rightDefault;
|
|
1334
|
+
if (!Number.isFinite(left) || !Number.isFinite(right)) continue;
|
|
1335
|
+
if (relation.operator === "less-than-or-equal" && left <= right) continue;
|
|
1336
|
+
report(
|
|
1337
|
+
node,
|
|
1338
|
+
"invalid-value",
|
|
1339
|
+
"error",
|
|
1340
|
+
`Invalid values for "${identifier}": "${relation.left}" (${left}) must be less than or equal to "${relation.right}" (${right}).`,
|
|
1341
|
+
at,
|
|
1342
|
+
relation.suggestion
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1244
1346
|
});
|
|
1245
1347
|
const scopeIsComplete = (container, scope, identifier) => container.origin === "markup" && !targetsUnreadable(container, scope, identifier);
|
|
1246
1348
|
for (const { node, identifier } of scopes) {
|
|
@@ -1268,13 +1370,16 @@ function checkSource(source, manifest) {
|
|
|
1268
1370
|
const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
|
|
1269
1371
|
for (const rule of controller.conditionalTargets) {
|
|
1270
1372
|
if (!present.has(rule.whenPresent)) continue;
|
|
1271
|
-
const
|
|
1373
|
+
const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
|
|
1374
|
+
const owners = nodesOf(rule.whenPresent);
|
|
1375
|
+
const satisfied = (target) => rule.requireInside ? nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner))) : present.has(target);
|
|
1376
|
+
const missing = rule.require.filter((target) => !satisfied(target));
|
|
1272
1377
|
if (missing.length === 0) continue;
|
|
1273
1378
|
report(
|
|
1274
1379
|
node,
|
|
1275
1380
|
"missing-conditional-target",
|
|
1276
1381
|
complete ? "error" : "warning",
|
|
1277
|
-
`"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1382
|
+
rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1278
1383
|
node,
|
|
1279
1384
|
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
1280
1385
|
);
|
|
@@ -1292,7 +1397,7 @@ function checkSource(source, manifest) {
|
|
|
1292
1397
|
if (!hasReadableMarkup(el)) continue;
|
|
1293
1398
|
if (!contentHolds(node, identifier, el, req.whenContains)) continue;
|
|
1294
1399
|
if (!elementHolds(el, req.whenElement)) continue;
|
|
1295
|
-
checkA11y(report, el, identifier, req, severity);
|
|
1400
|
+
checkA11y(report, el, identifier, req, severity, isDynamicValue);
|
|
1296
1401
|
}
|
|
1297
1402
|
}
|
|
1298
1403
|
}
|
|
@@ -1318,6 +1423,28 @@ function checkSource(source, manifest) {
|
|
|
1318
1423
|
}
|
|
1319
1424
|
}
|
|
1320
1425
|
}
|
|
1426
|
+
for (const { node, identifier } of scopes) {
|
|
1427
|
+
const controller = known[identifier];
|
|
1428
|
+
if (!controller) continue;
|
|
1429
|
+
for (const rule of controller.hosts) {
|
|
1430
|
+
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
1431
|
+
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
1432
|
+
for (const element of elements) {
|
|
1433
|
+
if (!hasReadableMarkup(element)) continue;
|
|
1434
|
+
if (hostIsAllowed(element, rule) !== false) continue;
|
|
1435
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1436
|
+
const detail = element.tag === "button" ? `the unsupported ${type ? `type="${type.value}"` : 'implicit type="submit"'}` : `an unsupported interactive <${element.tag}>`;
|
|
1437
|
+
report(
|
|
1438
|
+
element,
|
|
1439
|
+
"invalid-host",
|
|
1440
|
+
"error",
|
|
1441
|
+
`The ${where} of "${identifier}" uses ${detail} host.`,
|
|
1442
|
+
type ?? element,
|
|
1443
|
+
rule.suggestion
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1321
1448
|
for (const { node, identifier } of scopes) {
|
|
1322
1449
|
const controller = known[identifier];
|
|
1323
1450
|
if (!controller) continue;
|
|
@@ -1559,12 +1686,31 @@ function checkSource(source, manifest) {
|
|
|
1559
1686
|
diagnostics.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
1560
1687
|
return diagnostics;
|
|
1561
1688
|
}
|
|
1689
|
+
function valueSatisfiesConstraint(raw, constraint) {
|
|
1690
|
+
const value = decodeNumericValue(raw);
|
|
1691
|
+
if (Number.isNaN(value)) return false;
|
|
1692
|
+
if (constraint.finite && !Number.isFinite(value)) return false;
|
|
1693
|
+
if (constraint.greaterThan !== void 0 && !(value > constraint.greaterThan)) return false;
|
|
1694
|
+
if (constraint.integer && !Number.isInteger(value)) return false;
|
|
1695
|
+
return true;
|
|
1696
|
+
}
|
|
1697
|
+
function decodeNumericValue(raw) {
|
|
1698
|
+
return Number(raw.replace(/_/g, ""));
|
|
1699
|
+
}
|
|
1700
|
+
function describeValueConstraint(constraint) {
|
|
1701
|
+
const parts = [constraint.finite ? "a finite number" : "a number"];
|
|
1702
|
+
if (constraint.greaterThan !== void 0) {
|
|
1703
|
+
parts.push(`greater than ${constraint.greaterThan}`);
|
|
1704
|
+
}
|
|
1705
|
+
if (constraint.integer) parts.push("with no fractional part");
|
|
1706
|
+
return parts.join(" ");
|
|
1707
|
+
}
|
|
1562
1708
|
var UNRESOLVED_TARGET_HINT = "Part of this scope is rendered by a helper or a non-literal data: hash, so the target may exist at runtime.";
|
|
1563
1709
|
var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal hash, so the host controller may be declared at runtime.";
|
|
1564
1710
|
function hasReadableMarkup(el) {
|
|
1565
1711
|
return el.origin === "markup";
|
|
1566
1712
|
}
|
|
1567
|
-
function checkA11y(report, el, identifier, req, severity) {
|
|
1713
|
+
function checkA11y(report, el, identifier, req, severity, isDynamicValue) {
|
|
1568
1714
|
const groups = [
|
|
1569
1715
|
{ attrs: req.attrs, values: req.values },
|
|
1570
1716
|
...req.or ?? []
|
|
@@ -1584,9 +1730,14 @@ function checkA11y(report, el, identifier, req, severity) {
|
|
|
1584
1730
|
}
|
|
1585
1731
|
const invalid = [];
|
|
1586
1732
|
let anyPresent = false;
|
|
1733
|
+
let blank;
|
|
1587
1734
|
for (const [name, allowed] of allowedByAttr) {
|
|
1588
1735
|
const attr = el.attrs.find((a) => a.name === name);
|
|
1589
1736
|
if (!attr) continue;
|
|
1737
|
+
if (allowed.anyValue && attr.value.trim() === "" && !isDynamicValue(attr)) {
|
|
1738
|
+
blank ??= attr;
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1590
1741
|
anyPresent = true;
|
|
1591
1742
|
if (!allowed.anyValue && !allowed.values.has(attr.value.trim())) {
|
|
1592
1743
|
invalid.push({ attr, values: [...allowed.values] });
|
|
@@ -1598,8 +1749,8 @@ function checkA11y(report, el, identifier, req, severity) {
|
|
|
1598
1749
|
el,
|
|
1599
1750
|
"missing-aria",
|
|
1600
1751
|
severity,
|
|
1601
|
-
`"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
|
|
1602
|
-
el,
|
|
1752
|
+
blank ? `${blank.name} is empty on the ${where} of "${identifier}", which ${verb} a value there.` : `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
|
|
1753
|
+
blank ?? el,
|
|
1603
1754
|
req.suggestion
|
|
1604
1755
|
);
|
|
1605
1756
|
return;
|
|
@@ -1631,6 +1782,20 @@ function describeCounted(target, attr, values) {
|
|
|
1631
1782
|
return `"${target}" targets with ${attr}${constrained}`;
|
|
1632
1783
|
}
|
|
1633
1784
|
var NATIVE_FOCUSABLE_TAGS = /* @__PURE__ */ new Set(["button", "select", "textarea", "summary"]);
|
|
1785
|
+
var ALWAYS_INTERACTIVE_HOSTS = /* @__PURE__ */ new Set([
|
|
1786
|
+
"input",
|
|
1787
|
+
"select",
|
|
1788
|
+
"textarea",
|
|
1789
|
+
"label",
|
|
1790
|
+
"summary",
|
|
1791
|
+
"details",
|
|
1792
|
+
"iframe",
|
|
1793
|
+
"object",
|
|
1794
|
+
"embed"
|
|
1795
|
+
]);
|
|
1796
|
+
function hasAttr(element, name) {
|
|
1797
|
+
return element.attrs.some((attr) => attr.name === name);
|
|
1798
|
+
}
|
|
1634
1799
|
function isNativelyFocusable(el) {
|
|
1635
1800
|
if (NATIVE_FOCUSABLE_TAGS.has(el.tag)) return true;
|
|
1636
1801
|
if (el.tag === "input") {
|
|
@@ -1781,12 +1946,15 @@ function isCheckableFile(path) {
|
|
|
1781
1946
|
var CONTROLLER_ARRAY_FIELDS = [
|
|
1782
1947
|
"targets",
|
|
1783
1948
|
"values",
|
|
1949
|
+
"valueConstraints",
|
|
1950
|
+
"valueRelations",
|
|
1784
1951
|
"actions",
|
|
1785
1952
|
"events",
|
|
1786
1953
|
"requiredTargets",
|
|
1787
1954
|
"conditionalTargets",
|
|
1788
1955
|
"a11y",
|
|
1789
1956
|
"keyboard",
|
|
1957
|
+
"hosts",
|
|
1790
1958
|
"managedAria",
|
|
1791
1959
|
"compositions",
|
|
1792
1960
|
"companions",
|