stimeo-ui 0.6.0 → 0.7.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 +62 -0
- package/dist/controllers/alert_dialog_controller.d.ts +2 -0
- package/dist/controllers/alert_dialog_controller.js +75 -4
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +2 -0
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +47 -5
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +2 -0
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +2 -0
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -0
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +52 -20
- package/dist/controllers/character_counter_controller.js +338 -63
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +3 -0
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +3 -0
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +2 -0
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +75 -4
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +42 -14
- package/dist/controllers/conditional_fields_controller.js +345 -51
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +2 -0
- package/dist/controllers/confirm_controller.js +75 -4
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +2 -0
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +4 -0
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +2 -0
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +3 -0
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +75 -4
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +56 -24
- package/dist/controllers/direct_upload_controller.js +201 -45
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +14 -6
- package/dist/controllers/dirty_form_controller.js +192 -29
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +2 -0
- package/dist/controllers/dismissible_controller.js +83 -18
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +75 -4
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +2 -0
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +2 -0
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +2 -0
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -0
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -0
- package/dist/controllers/focus_controller.js +75 -4
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.d.ts +50 -10
- package/dist/controllers/form_field_controller.js +280 -62
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +10 -8
- package/dist/controllers/form_validation_controller.js +208 -83
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +2 -0
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +2 -0
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +2 -0
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +2 -0
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +2 -0
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +2 -0
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +2 -0
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +2 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +2 -0
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +2 -0
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +2 -0
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +2 -0
- package/dist/controllers/number_input_controller.js +47 -5
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +3 -0
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +2 -0
- package/dist/controllers/overflow_menu_controller.js +2 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +2 -0
- package/dist/controllers/pagination_controller.js +2 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +2 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +2 -0
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +30 -16
- package/dist/controllers/persist_controller.js +432 -122
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +3 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.js +77 -4
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +2 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +2 -0
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +3 -0
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +3 -0
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +2 -0
- package/dist/controllers/rating_controller.js +9 -5
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +2 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +2 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +2 -0
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +23 -15
- package/dist/controllers/scroll_area_controller.js +462 -162
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +2 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -11
- package/dist/controllers/separator_controller.js +354 -38
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.js +83 -10
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -0
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -0
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +2 -0
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +2 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +2 -0
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +94 -38
- package/dist/controllers/submit_once_controller.js +399 -121
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +2 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +2 -0
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -0
- package/dist/controllers/theme_controller.js +8 -6
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +2 -0
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -0
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +2 -0
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -0
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +2 -0
- package/dist/controllers/tree_view_controller.js +2 -1
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +2387 -861
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +107 -3
- package/dist/inspector/cli.js +192 -14
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +254 -46
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +7 -7
- package/dist/inspector/manifest.json +434 -44
- package/dist/positioning/index.d.ts +2 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
package/dist/inspector/cli.d.ts
CHANGED
|
@@ -48,6 +48,16 @@ interface ControllerManifest {
|
|
|
48
48
|
* {@link ConditionalTargetRule}.
|
|
49
49
|
*/
|
|
50
50
|
readonly conditionalTargets: readonly ConditionalTargetRule[];
|
|
51
|
+
/**
|
|
52
|
+
* Actions required on a particular element in one Value configuration. See
|
|
53
|
+
* {@link RequiredActionRule}.
|
|
54
|
+
*/
|
|
55
|
+
readonly requiredActions: readonly RequiredActionRule[];
|
|
56
|
+
/**
|
|
57
|
+
* States an action opens that the consumer's wiring has to close. See
|
|
58
|
+
* {@link ActionCompletionRule}.
|
|
59
|
+
*/
|
|
60
|
+
readonly actionCompletion: readonly ActionCompletionRule[];
|
|
51
61
|
/**
|
|
52
62
|
* Accessibility requirements the *consumer's markup* must satisfy (stage 3):
|
|
53
63
|
* ARIA attributes the controller does **not** set at runtime and therefore
|
|
@@ -140,6 +150,8 @@ interface ControllerManifest {
|
|
|
140
150
|
interface ValueCondition {
|
|
141
151
|
/** Value name (camelCase, as declared in `static values`). */
|
|
142
152
|
readonly value: string;
|
|
153
|
+
/** Stimulus decoder used before comparing the effective value. */
|
|
154
|
+
readonly type: "boolean" | "string";
|
|
143
155
|
/** Effective values that arm the rule. */
|
|
144
156
|
readonly equals: readonly string[];
|
|
145
157
|
/** The value's declared default, used when the attribute is absent. */
|
|
@@ -608,6 +620,85 @@ interface ConditionalTargetRule {
|
|
|
608
620
|
* controller then refuses to build from.
|
|
609
621
|
*/
|
|
610
622
|
readonly requireInside?: boolean;
|
|
623
|
+
/**
|
|
624
|
+
* Require each {@link require} entry to share its nearest enclosing host —
|
|
625
|
+
* the closest ancestor matching one of these selectors — with the
|
|
626
|
+
* `whenPresent` element.
|
|
627
|
+
*
|
|
628
|
+
* Neither presence nor {@link requireInside} can express a pair that must sit
|
|
629
|
+
* in the *same* container without being nested in one another. A controller
|
|
630
|
+
* that swaps one host's two halves reads them per host, so halves split
|
|
631
|
+
* across two hosts leave each host with an incomplete pair, and the swap
|
|
632
|
+
* silently does nothing on markup where both names are present.
|
|
633
|
+
*
|
|
634
|
+
* Only a pair whose halves both resolve to a host is judged. A template
|
|
635
|
+
* helper that emits the host leaves nothing in the parsed markup, so an
|
|
636
|
+
* element with no host above it is indistinguishable from one whose host is
|
|
637
|
+
* generated — reporting either would be a guess, and the check stays quiet.
|
|
638
|
+
*/
|
|
639
|
+
readonly requireSameHost?: readonly HostSelector[];
|
|
640
|
+
/**
|
|
641
|
+
* What to call the shared host in the diagnostic (e.g. `submit control`).
|
|
642
|
+
* The selectors' tag names read as a disjunction of element names, which
|
|
643
|
+
* names the mechanism rather than the thing the author is looking at.
|
|
644
|
+
*/
|
|
645
|
+
readonly hostLabel?: string;
|
|
646
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
647
|
+
readonly suggestion: string;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* One element shape that can act as a host for {@link
|
|
651
|
+
* ConditionalTargetRule.requireSameHost}.
|
|
652
|
+
*
|
|
653
|
+
* A tag alone is too coarse where the element's `type` decides its role, so an
|
|
654
|
+
* optional attribute filter narrows it. Omitting {@link attr} accepts the tag
|
|
655
|
+
* however it is spelled.
|
|
656
|
+
*/
|
|
657
|
+
interface HostSelector {
|
|
658
|
+
/** Lowercased tag name. */
|
|
659
|
+
readonly tag: string;
|
|
660
|
+
/** Attribute that must carry one of {@link values} for the tag to qualify. */
|
|
661
|
+
readonly attr?: string;
|
|
662
|
+
/** Accepted lowercased values of {@link attr}. */
|
|
663
|
+
readonly values?: readonly string[];
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* An action that puts a controller into a state it cannot leave on its own.
|
|
667
|
+
*
|
|
668
|
+
* A controller whose busy state is opened declaratively has no way to know the
|
|
669
|
+
* consumer never wired the matching exit: nothing throws, the markup is
|
|
670
|
+
* well-formed, and the only symptom is a control that stays disabled for the
|
|
671
|
+
* rest of the page's life. The check reads the wiring the author did declare
|
|
672
|
+
* and says so before it ships.
|
|
673
|
+
*/
|
|
674
|
+
interface ActionCompletionRule {
|
|
675
|
+
/** Action that opens the state. */
|
|
676
|
+
readonly opens: string;
|
|
677
|
+
/** Any one of these actions closes it again. */
|
|
678
|
+
readonly closedBy: readonly string[];
|
|
679
|
+
/** Event types for {@link opens} that the controller cannot complete itself. */
|
|
680
|
+
readonly whenTriggeredBy: readonly string[];
|
|
681
|
+
/** Value whose non-zero number supplies an automatic exit instead. */
|
|
682
|
+
readonly escapeValue?: string;
|
|
683
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
684
|
+
readonly suggestion: string;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* An action binding that must exist for one configured behavior to be usable.
|
|
688
|
+
*
|
|
689
|
+
* Declaring an action method on the controller only proves the method exists;
|
|
690
|
+
* Stimulus still needs the consumer to wire it. Optional interactive variants
|
|
691
|
+
* can otherwise enter the Tab order while every key silently does nothing.
|
|
692
|
+
*/
|
|
693
|
+
interface RequiredActionRule {
|
|
694
|
+
/** Target whose element must carry the action; `""` means the scope element. */
|
|
695
|
+
readonly target: string;
|
|
696
|
+
/** Public controller action method that must be wired. */
|
|
697
|
+
readonly action: string;
|
|
698
|
+
/** Event types that make the binding usable for this contract. */
|
|
699
|
+
readonly eventTypes: readonly string[];
|
|
700
|
+
/** Configuration in which the binding becomes mandatory. */
|
|
701
|
+
readonly when?: ValueCondition;
|
|
611
702
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
612
703
|
readonly suggestion: string;
|
|
613
704
|
}
|
|
@@ -618,10 +709,10 @@ interface ConditionalTargetRule {
|
|
|
618
709
|
* ""))`) before applying these bounds. ERB-generated values are undecidable and
|
|
619
710
|
* skipped rather than guessed.
|
|
620
711
|
*/
|
|
621
|
-
interface
|
|
712
|
+
interface NumericValueConstraint {
|
|
622
713
|
/** Value name (camelCase, as declared in `static values`). */
|
|
623
714
|
readonly value: string;
|
|
624
|
-
/** Decoder family.
|
|
715
|
+
/** Decoder family. */
|
|
625
716
|
readonly type: "number";
|
|
626
717
|
/** Reject `NaN` and infinities when true. */
|
|
627
718
|
readonly finite?: boolean;
|
|
@@ -632,6 +723,19 @@ interface ValueConstraint {
|
|
|
632
723
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
633
724
|
readonly suggestion: string;
|
|
634
725
|
}
|
|
726
|
+
/** A statically checkable finite set of accepted String Value literals. */
|
|
727
|
+
interface StringValueConstraint {
|
|
728
|
+
/** Value name (camelCase, as declared in `static values`). */
|
|
729
|
+
readonly value: string;
|
|
730
|
+
/** Decoder family. */
|
|
731
|
+
readonly type: "string";
|
|
732
|
+
/** Exact authored literals accepted by the public contract. */
|
|
733
|
+
readonly allowedValues: readonly string[];
|
|
734
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
735
|
+
readonly suggestion: string;
|
|
736
|
+
}
|
|
737
|
+
/** A statically checkable semantic contract on one declared Stimulus Value. */
|
|
738
|
+
type ValueConstraint = NumericValueConstraint | StringValueConstraint;
|
|
635
739
|
/**
|
|
636
740
|
* A statically checkable relationship between two numeric Stimulus Values.
|
|
637
741
|
*
|
|
@@ -659,7 +763,7 @@ type DiagnosticSeverity = "error" | "warning";
|
|
|
659
763
|
* for testing, machine-readable output, and `data-stimeo-ignore` suppression
|
|
660
764
|
* lists (which is why the set also exists as a runtime array).
|
|
661
765
|
*/
|
|
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"];
|
|
766
|
+
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-required-action", "missing-action-completion", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "missing-announcer", "unknown-ignore-code"];
|
|
663
767
|
/** See {@link DIAGNOSTIC_CODES}. */
|
|
664
768
|
type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
|
|
665
769
|
/**
|
package/dist/inspector/cli.js
CHANGED
|
@@ -5,6 +5,65 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
|
|
6
6
|
// src/inspector/cli.ts
|
|
7
7
|
|
|
8
|
+
// src/inspector/announcer_rules.ts
|
|
9
|
+
var ANNOUNCER_IDENTIFIER = "stimeo--announcer";
|
|
10
|
+
var ANNOUNCING_CONTROLLERS = [
|
|
11
|
+
"stimeo--auto-submit",
|
|
12
|
+
"stimeo--character-counter",
|
|
13
|
+
"stimeo--countdown",
|
|
14
|
+
"stimeo--direct-upload",
|
|
15
|
+
"stimeo--empty-state",
|
|
16
|
+
"stimeo--flash",
|
|
17
|
+
"stimeo--form-field",
|
|
18
|
+
"stimeo--frame-loading",
|
|
19
|
+
"stimeo--meter",
|
|
20
|
+
"stimeo--multi-select",
|
|
21
|
+
"stimeo--nested-form",
|
|
22
|
+
"stimeo--network-status",
|
|
23
|
+
"stimeo--progress",
|
|
24
|
+
"stimeo--skeleton",
|
|
25
|
+
"stimeo--spinner",
|
|
26
|
+
"stimeo--submit-once",
|
|
27
|
+
"stimeo--tags-input"
|
|
28
|
+
];
|
|
29
|
+
function positionAt(source, index) {
|
|
30
|
+
const before = source.slice(0, index);
|
|
31
|
+
const line = before.split("\n").length;
|
|
32
|
+
const lastBreak = before.lastIndexOf("\n");
|
|
33
|
+
return { line, column: index - lastBreak };
|
|
34
|
+
}
|
|
35
|
+
function firstAnnouncingHit(scanned) {
|
|
36
|
+
let best = null;
|
|
37
|
+
let bestIndex = Number.POSITIVE_INFINITY;
|
|
38
|
+
for (const identifier of ANNOUNCING_CONTROLLERS) {
|
|
39
|
+
const index = scanned.source.indexOf(identifier);
|
|
40
|
+
if (index === -1 || index >= bestIndex) continue;
|
|
41
|
+
bestIndex = index;
|
|
42
|
+
best = { file: scanned.file, identifier, ...positionAt(scanned.source, index) };
|
|
43
|
+
}
|
|
44
|
+
return best;
|
|
45
|
+
}
|
|
46
|
+
function announcerSeatingDiagnostic(sources) {
|
|
47
|
+
let speaker = null;
|
|
48
|
+
for (const scanned of sources) {
|
|
49
|
+
if (scanned.source.includes(ANNOUNCER_IDENTIFIER)) return null;
|
|
50
|
+
if (!speaker) speaker = firstAnnouncingHit(scanned);
|
|
51
|
+
}
|
|
52
|
+
if (!speaker) return null;
|
|
53
|
+
return {
|
|
54
|
+
file: speaker.file,
|
|
55
|
+
diagnostic: {
|
|
56
|
+
code: "missing-announcer",
|
|
57
|
+
severity: "warning",
|
|
58
|
+
message: `"${speaker.identifier}" speaks through the shared announcer, and none of the scanned files seats one.`,
|
|
59
|
+
line: speaker.line,
|
|
60
|
+
column: speaker.column,
|
|
61
|
+
length: speaker.identifier.length,
|
|
62
|
+
suggestion: `Add an element with data-controller="${ANNOUNCER_IDENTIFIER}" (and an assertive/polite target) to the layout, or include the layout in the checked paths.`
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
8
67
|
// src/inspector/html_parser.ts
|
|
9
68
|
var ERB_ELEMENT_TAG = "#erb";
|
|
10
69
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -930,10 +989,16 @@ function actionDescriptors(dataActionValue) {
|
|
|
930
989
|
if (hash === -1) continue;
|
|
931
990
|
let lhs = descriptor.slice(0, hash);
|
|
932
991
|
const arrow = lhs.indexOf("->");
|
|
933
|
-
|
|
992
|
+
let eventType = "";
|
|
993
|
+
if (arrow !== -1) {
|
|
994
|
+
eventType = lhs.slice(0, arrow);
|
|
995
|
+
lhs = lhs.slice(arrow + 2);
|
|
996
|
+
const at = eventType.indexOf("@");
|
|
997
|
+
if (at !== -1) eventType = eventType.slice(0, at);
|
|
998
|
+
}
|
|
934
999
|
if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
|
|
935
1000
|
const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
|
|
936
|
-
descriptors.push({ identifier: lhs, method });
|
|
1001
|
+
descriptors.push({ identifier: lhs, method, eventType });
|
|
937
1002
|
}
|
|
938
1003
|
return descriptors;
|
|
939
1004
|
}
|
|
@@ -952,6 +1017,8 @@ var DIAGNOSTIC_CODES = [
|
|
|
952
1017
|
"orphan-target",
|
|
953
1018
|
"missing-required-target",
|
|
954
1019
|
"missing-conditional-target",
|
|
1020
|
+
"missing-required-action",
|
|
1021
|
+
"missing-action-completion",
|
|
955
1022
|
"missing-aria",
|
|
956
1023
|
"invalid-aria-value",
|
|
957
1024
|
"keyboard-inaccessible",
|
|
@@ -963,6 +1030,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
963
1030
|
"undeclared-target",
|
|
964
1031
|
"cardinality-violation",
|
|
965
1032
|
"forbidden-aria",
|
|
1033
|
+
"missing-announcer",
|
|
966
1034
|
"unknown-ignore-code"
|
|
967
1035
|
];
|
|
968
1036
|
|
|
@@ -973,6 +1041,19 @@ function isInside(node, ancestor) {
|
|
|
973
1041
|
}
|
|
974
1042
|
return false;
|
|
975
1043
|
}
|
|
1044
|
+
function matchesHost(node, selector) {
|
|
1045
|
+
if (node.tag !== selector.tag) return false;
|
|
1046
|
+
if (!selector.attr) return true;
|
|
1047
|
+
const attr = node.attrs.find((candidate) => candidate.name === selector.attr);
|
|
1048
|
+
return attr !== void 0 && (selector.values ?? []).includes(attr.value.trim().toLowerCase());
|
|
1049
|
+
}
|
|
1050
|
+
function nearestHost(node, selectors) {
|
|
1051
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
1052
|
+
if (current.origin !== "markup" || current.opaque) return void 0;
|
|
1053
|
+
if (selectors.some((selector) => matchesHost(current, selector))) return current;
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
976
1057
|
function checkSource(source, manifest) {
|
|
977
1058
|
const diagnostics = [];
|
|
978
1059
|
const tree = parseHtml(neutralizeErb(source), erbElements(source));
|
|
@@ -991,7 +1072,9 @@ function checkSource(source, manifest) {
|
|
|
991
1072
|
(a) => a.name === `data-${identifier}-${dasherize(when.value)}-value`
|
|
992
1073
|
);
|
|
993
1074
|
if (hostAttr && isDynamicValue(hostAttr)) return null;
|
|
994
|
-
|
|
1075
|
+
const raw = hostAttr ? hostAttr.value : when.default;
|
|
1076
|
+
const effective = when.type === "boolean" ? String(!(raw === "0" || raw.toLowerCase() === "false")) : raw;
|
|
1077
|
+
return when.equals.includes(effective);
|
|
995
1078
|
};
|
|
996
1079
|
const isContentEditable2 = (element) => {
|
|
997
1080
|
let current = element;
|
|
@@ -1064,6 +1147,17 @@ function checkSource(source, manifest) {
|
|
|
1064
1147
|
const scopes = [];
|
|
1065
1148
|
const presence = /* @__PURE__ */ new Map();
|
|
1066
1149
|
const targetNodes = /* @__PURE__ */ new Map();
|
|
1150
|
+
const wiredActions = /* @__PURE__ */ new Map();
|
|
1151
|
+
const recordAction = (owner, identifier, element, method, eventType) => {
|
|
1152
|
+
let byId = wiredActions.get(owner);
|
|
1153
|
+
if (!byId) {
|
|
1154
|
+
byId = /* @__PURE__ */ new Map();
|
|
1155
|
+
wiredActions.set(owner, byId);
|
|
1156
|
+
}
|
|
1157
|
+
const found = byId.get(identifier);
|
|
1158
|
+
if (found) found.push({ element, method, eventType });
|
|
1159
|
+
else byId.set(identifier, [{ element, method, eventType }]);
|
|
1160
|
+
};
|
|
1067
1161
|
const recordPresence = (owner, identifier, target) => {
|
|
1068
1162
|
let byId = presence.get(owner);
|
|
1069
1163
|
if (!byId) {
|
|
@@ -1272,11 +1366,12 @@ function checkSource(source, manifest) {
|
|
|
1272
1366
|
(candidate) => dasherize(candidate.value) === parsed.valueToken
|
|
1273
1367
|
);
|
|
1274
1368
|
if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
|
|
1369
|
+
const literal = constraint.type === "string" ? attr.value : attr.value.trim();
|
|
1275
1370
|
report(
|
|
1276
1371
|
node,
|
|
1277
1372
|
"invalid-value",
|
|
1278
1373
|
"error",
|
|
1279
|
-
`Invalid value "${
|
|
1374
|
+
`Invalid value "${literal}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
|
|
1280
1375
|
attr,
|
|
1281
1376
|
constraint.suggestion
|
|
1282
1377
|
);
|
|
@@ -1287,8 +1382,12 @@ function checkSource(source, manifest) {
|
|
|
1287
1382
|
}
|
|
1288
1383
|
}
|
|
1289
1384
|
if (attr.name === "data-action") {
|
|
1290
|
-
for (const { identifier, method } of actionDescriptors(attr.value)) {
|
|
1385
|
+
for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
|
|
1291
1386
|
const controller = known[identifier];
|
|
1387
|
+
if (controller && method.length > 0) {
|
|
1388
|
+
const owner = findOwner(node, identifier);
|
|
1389
|
+
if (owner) recordAction(owner, identifier, node, method, eventType);
|
|
1390
|
+
}
|
|
1292
1391
|
if (!controller) {
|
|
1293
1392
|
const best = nearestName(identifier, knownIdentifiers);
|
|
1294
1393
|
report(
|
|
@@ -1372,19 +1471,87 @@ function checkSource(source, manifest) {
|
|
|
1372
1471
|
if (!present.has(rule.whenPresent)) continue;
|
|
1373
1472
|
const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
|
|
1374
1473
|
const owners = nodesOf(rule.whenPresent);
|
|
1375
|
-
const
|
|
1474
|
+
const hosts = rule.requireSameHost;
|
|
1475
|
+
const sharesHost = (target, selectors) => {
|
|
1476
|
+
const ownerHosts = owners.map((owner) => nearestHost(owner, selectors));
|
|
1477
|
+
if (ownerHosts.some((host) => !host)) return true;
|
|
1478
|
+
const candidates = nodesOf(target);
|
|
1479
|
+
if (candidates.length === 0) return false;
|
|
1480
|
+
return candidates.some((el) => {
|
|
1481
|
+
const host = nearestHost(el, selectors);
|
|
1482
|
+
return host ? ownerHosts.includes(host) : true;
|
|
1483
|
+
});
|
|
1484
|
+
};
|
|
1485
|
+
const satisfied = (target) => {
|
|
1486
|
+
if (hosts) return sharesHost(target, hosts);
|
|
1487
|
+
if (rule.requireInside)
|
|
1488
|
+
return nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner)));
|
|
1489
|
+
return present.has(target);
|
|
1490
|
+
};
|
|
1376
1491
|
const missing = rule.require.filter((target) => !satisfied(target));
|
|
1377
1492
|
if (missing.length === 0) continue;
|
|
1378
1493
|
report(
|
|
1379
1494
|
node,
|
|
1380
1495
|
"missing-conditional-target",
|
|
1381
1496
|
complete ? "error" : "warning",
|
|
1382
|
-
rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1497
|
+
hosts ? `"${identifier}" requires ${list(missing)} in the same ${rule.hostLabel ?? "element"} as its "${rule.whenPresent}".` : rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1383
1498
|
node,
|
|
1384
1499
|
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
1385
1500
|
);
|
|
1386
1501
|
}
|
|
1387
1502
|
}
|
|
1503
|
+
for (const { node, identifier } of scopes) {
|
|
1504
|
+
const controller = known[identifier];
|
|
1505
|
+
if (!controller) continue;
|
|
1506
|
+
const wired = wiredActions.get(node)?.get(identifier) ?? [];
|
|
1507
|
+
for (const rule of controller.requiredActions) {
|
|
1508
|
+
if (conditionHolds(node, identifier, rule.when) !== true) continue;
|
|
1509
|
+
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
1510
|
+
for (const element of elements) {
|
|
1511
|
+
const actionAttr = element.attrs.find((attr) => attr.name === "data-action");
|
|
1512
|
+
if (element.opaque || actionAttr && isDynamicValue(actionAttr)) continue;
|
|
1513
|
+
const satisfied = wired.some(
|
|
1514
|
+
(action) => action.element === element && action.method === rule.action && rule.eventTypes.includes(action.eventType)
|
|
1515
|
+
);
|
|
1516
|
+
if (satisfied) continue;
|
|
1517
|
+
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
1518
|
+
report(
|
|
1519
|
+
element,
|
|
1520
|
+
"missing-required-action",
|
|
1521
|
+
"error",
|
|
1522
|
+
`The ${where} of "${identifier}" requires action "${rule.action}" on ${list(rule.eventTypes)} in this configuration.`,
|
|
1523
|
+
element,
|
|
1524
|
+
rule.suggestion
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
for (const { node, identifier } of scopes) {
|
|
1530
|
+
const controller = known[identifier];
|
|
1531
|
+
if (!controller) continue;
|
|
1532
|
+
const wired = wiredActions.get(node)?.get(identifier) ?? [];
|
|
1533
|
+
if (wired.length === 0) continue;
|
|
1534
|
+
for (const rule of controller.actionCompletion) {
|
|
1535
|
+
const opens = wired.some(
|
|
1536
|
+
(action) => action.method === rule.opens && rule.whenTriggeredBy.includes(action.eventType)
|
|
1537
|
+
);
|
|
1538
|
+
if (!opens) continue;
|
|
1539
|
+
if (wired.some((action) => rule.closedBy.includes(action.method))) continue;
|
|
1540
|
+
if (rule.escapeValue) {
|
|
1541
|
+
const attrName = `data-${identifier}-${dasherize(rule.escapeValue)}-value`;
|
|
1542
|
+
const authored = node.attrs.find((attr) => attr.name === attrName);
|
|
1543
|
+
if (authored && (isDynamicValue(authored) || Number(authored.value.trim()) !== 0)) continue;
|
|
1544
|
+
}
|
|
1545
|
+
report(
|
|
1546
|
+
node,
|
|
1547
|
+
"missing-action-completion",
|
|
1548
|
+
"warning",
|
|
1549
|
+
`"${identifier}" opens a state with "${rule.opens}" here, and nothing in this scope closes it.`,
|
|
1550
|
+
node,
|
|
1551
|
+
rule.suggestion
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1388
1555
|
for (const { node, identifier } of scopes) {
|
|
1389
1556
|
const controller = known[identifier];
|
|
1390
1557
|
if (!controller) continue;
|
|
@@ -1687,6 +1854,7 @@ function checkSource(source, manifest) {
|
|
|
1687
1854
|
return diagnostics;
|
|
1688
1855
|
}
|
|
1689
1856
|
function valueSatisfiesConstraint(raw, constraint) {
|
|
1857
|
+
if (constraint.type === "string") return constraint.allowedValues.includes(raw);
|
|
1690
1858
|
const value = decodeNumericValue(raw);
|
|
1691
1859
|
if (Number.isNaN(value)) return false;
|
|
1692
1860
|
if (constraint.finite && !Number.isFinite(value)) return false;
|
|
@@ -1698,6 +1866,7 @@ function decodeNumericValue(raw) {
|
|
|
1698
1866
|
return Number(raw.replace(/_/g, ""));
|
|
1699
1867
|
}
|
|
1700
1868
|
function describeValueConstraint(constraint) {
|
|
1869
|
+
if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
|
|
1701
1870
|
const parts = [constraint.finite ? "a finite number" : "a number"];
|
|
1702
1871
|
if (constraint.greaterThan !== void 0) {
|
|
1703
1872
|
parts.push(`greater than ${constraint.greaterThan}`);
|
|
@@ -1952,6 +2121,8 @@ var CONTROLLER_ARRAY_FIELDS = [
|
|
|
1952
2121
|
"events",
|
|
1953
2122
|
"requiredTargets",
|
|
1954
2123
|
"conditionalTargets",
|
|
2124
|
+
"requiredActions",
|
|
2125
|
+
"actionCompletion",
|
|
1955
2126
|
"a11y",
|
|
1956
2127
|
"keyboard",
|
|
1957
2128
|
"hosts",
|
|
@@ -2014,10 +2185,16 @@ function collectFiles(target, out) {
|
|
|
2014
2185
|
}
|
|
2015
2186
|
if (isCheckableFile(target)) out.push(target);
|
|
2016
2187
|
}
|
|
2017
|
-
function checkFile(file, manifest) {
|
|
2018
|
-
const source = readFileSync(file, "utf8");
|
|
2188
|
+
function checkFile(file, source, manifest) {
|
|
2019
2189
|
return { file, diagnostics: checkSource(source, manifest) };
|
|
2020
2190
|
}
|
|
2191
|
+
function withAnnouncerSeating(reports, sources) {
|
|
2192
|
+
const seating = announcerSeatingDiagnostic(sources);
|
|
2193
|
+
if (!seating) return [...reports];
|
|
2194
|
+
return reports.map(
|
|
2195
|
+
(report) => report.file === seating.file ? { file: report.file, diagnostics: [...report.diagnostics, seating.diagnostic] } : report
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2021
2198
|
function formatDiagnostic(d) {
|
|
2022
2199
|
const where = `${d.line}:${d.column}`.padEnd(7);
|
|
2023
2200
|
const head = ` ${where} ${d.severity.padEnd(7)} ${d.message} [${d.code}]`;
|
|
@@ -2150,13 +2327,14 @@ ${USAGE}`);
|
|
|
2150
2327
|
const manifest = load();
|
|
2151
2328
|
const cwd = process.cwd();
|
|
2152
2329
|
const reports = [];
|
|
2330
|
+
const sources = [];
|
|
2153
2331
|
for (const file of files.sort()) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2332
|
+
const path = relative(cwd, file) || file;
|
|
2333
|
+
const source = readFileSync(file, "utf8");
|
|
2334
|
+
sources.push({ file: path, source });
|
|
2335
|
+
reports.push(checkFile(path, source, manifest));
|
|
2158
2336
|
}
|
|
2159
|
-
const summary = buildCheckReport(reports, files.length);
|
|
2337
|
+
const summary = buildCheckReport(withAnnouncerSeating(reports, sources), files.length);
|
|
2160
2338
|
if (json) {
|
|
2161
2339
|
write(JSON.stringify(summary, null, 2));
|
|
2162
2340
|
return summary.errorCount > 0 ? 1 : 0;
|