stimeo-ui 0.4.0 → 0.5.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 +68 -0
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +2 -2
- package/dist/controllers/aspect_ratio_controller.js +1 -1
- package/dist/controllers/aspect_ratio_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.js +5 -1
- package/dist/controllers/carousel_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 +2 -2
- package/dist/controllers/color_picker_controller.js +6 -2
- 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.js +5 -1
- 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/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/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/idle_controller.d.ts +2 -1
- package/dist/controllers/idle_controller.js +13 -2
- 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/meter_controller.js +3 -1
- package/dist/controllers/meter_controller.js.map +1 -1
- 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 +8 -0
- package/dist/controllers/number_input_controller.js +191 -24
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +1 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +5 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +1 -1
- package/dist/controllers/password_strength_controller.js +1 -1
- package/dist/controllers/password_strength_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/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/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.js +2 -0
- 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/scroll_area_controller.d.ts +1 -1
- package/dist/controllers/scroll_area_controller.js +1 -1
- package/dist/controllers/scroll_area_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/textarea_autosize_controller.js +1 -1
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +3 -0
- package/dist/controllers/time_picker_controller.js +6 -3
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +1 -2
- package/dist/controllers/tree_view_controller.js +19 -1
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +1214 -307
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +76 -1
- package/dist/inspector/cli.js +152 -0
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +212 -2
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +17 -17
- package/dist/inspector/manifest.json +408 -26
- 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
|
|
@@ -328,6 +343,27 @@ interface KeyboardRequirement {
|
|
|
328
343
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
329
344
|
readonly suggestion: string;
|
|
330
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* A host-element contract (stage 3): the element carrying a controller or one
|
|
348
|
+
* of its targets must use semantics the controller can own without competing
|
|
349
|
+
* with another native interaction.
|
|
350
|
+
*
|
|
351
|
+
* `"non-interactive"` admits only generic, non-interactive elements.
|
|
352
|
+
* `"non-interactive-or-button"` additionally admits a `<button>` whose
|
|
353
|
+
* literal `type` is listed in {@link buttonTypes}. The explicit type matters:
|
|
354
|
+
* a missing or invalid button type defaults to `submit`, so treating it as an
|
|
355
|
+
* ordinary action button would silently submit an enclosing form.
|
|
356
|
+
*/
|
|
357
|
+
interface HostRequirement {
|
|
358
|
+
/** Target name; the empty string `""` means the controller scope element. */
|
|
359
|
+
readonly target: string;
|
|
360
|
+
/** Static host shapes the controller supports. */
|
|
361
|
+
readonly mode: "non-interactive" | "non-interactive-or-button";
|
|
362
|
+
/** Literal button types admitted by `"non-interactive-or-button"`. */
|
|
363
|
+
readonly buttonTypes?: readonly string[];
|
|
364
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
365
|
+
readonly suggestion: string;
|
|
366
|
+
}
|
|
331
367
|
/**
|
|
332
368
|
* An author-futile attribute rule (stage 3): authoring any of {@link attrs} on
|
|
333
369
|
* the {@link target} element draws a `managed-aria` **warning** — the
|
|
@@ -564,6 +600,45 @@ interface ConditionalTargetRule {
|
|
|
564
600
|
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
565
601
|
readonly suggestion: string;
|
|
566
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* A statically checkable numeric contract on one declared Stimulus Value.
|
|
605
|
+
*
|
|
606
|
+
* The Inspector decodes literals like Stimulus (`Number(raw.replace(/_/g,
|
|
607
|
+
* ""))`) before applying these bounds. ERB-generated values are undecidable and
|
|
608
|
+
* skipped rather than guessed.
|
|
609
|
+
*/
|
|
610
|
+
interface ValueConstraint {
|
|
611
|
+
/** Value name (camelCase, as declared in `static values`). */
|
|
612
|
+
readonly value: string;
|
|
613
|
+
/** Decoder family. Numeric bounds are the first supported semantic family. */
|
|
614
|
+
readonly type: "number";
|
|
615
|
+
/** Reject `NaN` and infinities when true. */
|
|
616
|
+
readonly finite?: boolean;
|
|
617
|
+
/** Require a decoded number strictly greater than this bound. */
|
|
618
|
+
readonly greaterThan?: number;
|
|
619
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
620
|
+
readonly suggestion: string;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* A statically checkable relationship between two numeric Stimulus Values.
|
|
624
|
+
*
|
|
625
|
+
* Missing attributes use the controller's public defaults. If either authored
|
|
626
|
+
* value is dynamic, the Inspector skips the relation rather than guessing.
|
|
627
|
+
*/
|
|
628
|
+
interface ValueRelation {
|
|
629
|
+
/** Value on the left side of the comparison (camelCase). */
|
|
630
|
+
readonly left: string;
|
|
631
|
+
/** Supported numeric comparison. */
|
|
632
|
+
readonly operator: "less-than-or-equal";
|
|
633
|
+
/** Value on the right side of the comparison (camelCase). */
|
|
634
|
+
readonly right: string;
|
|
635
|
+
/** Effective left value when its attribute is absent. */
|
|
636
|
+
readonly leftDefault: number;
|
|
637
|
+
/** Effective right value when its attribute is absent. */
|
|
638
|
+
readonly rightDefault: number;
|
|
639
|
+
/** Human-readable fix suggestion shown by the CLI (stage 4). */
|
|
640
|
+
readonly suggestion: string;
|
|
641
|
+
}
|
|
567
642
|
/** Severity of a diagnostic. Only `error` affects the process exit code. */
|
|
568
643
|
type DiagnosticSeverity = "error" | "warning";
|
|
569
644
|
/**
|
|
@@ -571,7 +646,7 @@ type DiagnosticSeverity = "error" | "warning";
|
|
|
571
646
|
* for testing, machine-readable output, and `data-stimeo-ignore` suppression
|
|
572
647
|
* lists (which is why the set also exists as a runtime array).
|
|
573
648
|
*/
|
|
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"];
|
|
649
|
+
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
650
|
/** See {@link DIAGNOSTIC_CODES}. */
|
|
576
651
|
type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
|
|
577
652
|
/**
|
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",
|
|
@@ -965,6 +987,36 @@ function checkSource(source, manifest) {
|
|
|
965
987
|
if (hostAttr && isDynamicValue(hostAttr)) return null;
|
|
966
988
|
return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
|
|
967
989
|
};
|
|
990
|
+
const isContentEditable2 = (element) => {
|
|
991
|
+
let current = element;
|
|
992
|
+
while (current && current.tag !== "#root") {
|
|
993
|
+
const attr = current.attrs.find((candidate) => candidate.name === "contenteditable");
|
|
994
|
+
if (attr) {
|
|
995
|
+
if (isDynamicValue(attr)) return null;
|
|
996
|
+
const value = attr.value.trim().toLowerCase();
|
|
997
|
+
if (value === "false") return false;
|
|
998
|
+
if (value === "" || value === "true" || value === "plaintext-only") return true;
|
|
999
|
+
}
|
|
1000
|
+
current = current.parent;
|
|
1001
|
+
}
|
|
1002
|
+
return false;
|
|
1003
|
+
};
|
|
1004
|
+
const hostIsAllowed = (element, rule) => {
|
|
1005
|
+
if (element.tag === "button") {
|
|
1006
|
+
if (rule.mode === "non-interactive") return false;
|
|
1007
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1008
|
+
if (type && isDynamicValue(type)) return null;
|
|
1009
|
+
const effectiveType = type?.value.trim().toLowerCase() || "submit";
|
|
1010
|
+
return (rule.buttonTypes ?? []).includes(effectiveType);
|
|
1011
|
+
}
|
|
1012
|
+
if (ALWAYS_INTERACTIVE_HOSTS.has(element.tag)) return false;
|
|
1013
|
+
if ((element.tag === "a" || element.tag === "area") && hasAttr(element, "href")) return false;
|
|
1014
|
+
if ((element.tag === "audio" || element.tag === "video") && hasAttr(element, "controls")) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
const editable = isContentEditable2(element);
|
|
1018
|
+
return editable === null ? null : !editable;
|
|
1019
|
+
};
|
|
968
1020
|
const suppressed = (node, code) => {
|
|
969
1021
|
if (code === "unknown-ignore-code") return false;
|
|
970
1022
|
let current = node;
|
|
@@ -1072,6 +1124,7 @@ function checkSource(source, manifest) {
|
|
|
1072
1124
|
focusableRoleTally.set(role, (focusableRoleTally.get(role) ?? 0) + 1);
|
|
1073
1125
|
}
|
|
1074
1126
|
}
|
|
1127
|
+
const authoredValueIdentifiers = /* @__PURE__ */ new Set();
|
|
1075
1128
|
for (const attr of node.attrs) {
|
|
1076
1129
|
if (attr.name === IGNORE_ATTR) {
|
|
1077
1130
|
for (const token of attr.value.split(/\s+/).filter((t) => t.length > 0)) {
|
|
@@ -1196,6 +1249,7 @@ function checkSource(source, manifest) {
|
|
|
1196
1249
|
attr
|
|
1197
1250
|
);
|
|
1198
1251
|
} else {
|
|
1252
|
+
authoredValueIdentifiers.add(parsed.identifier);
|
|
1199
1253
|
const controller = known[parsed.identifier];
|
|
1200
1254
|
const valid = controller?.values.some((v) => dasherize(v) === parsed.valueToken) ?? false;
|
|
1201
1255
|
if (!valid) {
|
|
@@ -1207,6 +1261,20 @@ function checkSource(source, manifest) {
|
|
|
1207
1261
|
attr,
|
|
1208
1262
|
didYouMean(parsed.valueToken, (controller?.values ?? []).map(dasherize))
|
|
1209
1263
|
);
|
|
1264
|
+
} else if (!isDynamicValue(attr)) {
|
|
1265
|
+
const constraint = controller?.valueConstraints.find(
|
|
1266
|
+
(candidate) => dasherize(candidate.value) === parsed.valueToken
|
|
1267
|
+
);
|
|
1268
|
+
if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
|
|
1269
|
+
report(
|
|
1270
|
+
node,
|
|
1271
|
+
"invalid-value",
|
|
1272
|
+
"error",
|
|
1273
|
+
`Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
|
|
1274
|
+
attr,
|
|
1275
|
+
constraint.suggestion
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1210
1278
|
}
|
|
1211
1279
|
}
|
|
1212
1280
|
continue;
|
|
@@ -1241,6 +1309,34 @@ function checkSource(source, manifest) {
|
|
|
1241
1309
|
}
|
|
1242
1310
|
}
|
|
1243
1311
|
}
|
|
1312
|
+
for (const identifier of authoredValueIdentifiers) {
|
|
1313
|
+
const controller = known[identifier];
|
|
1314
|
+
if (!controller) continue;
|
|
1315
|
+
for (const relation of controller.valueRelations) {
|
|
1316
|
+
const leftAttr = node.attrs.find(
|
|
1317
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.left)}-value`
|
|
1318
|
+
);
|
|
1319
|
+
const rightAttr = node.attrs.find(
|
|
1320
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.right)}-value`
|
|
1321
|
+
);
|
|
1322
|
+
const at = rightAttr ?? leftAttr;
|
|
1323
|
+
if (!at || leftAttr && isDynamicValue(leftAttr) || rightAttr && isDynamicValue(rightAttr)) {
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const left = leftAttr ? decodeNumericValue(leftAttr.value) : relation.leftDefault;
|
|
1327
|
+
const right = rightAttr ? decodeNumericValue(rightAttr.value) : relation.rightDefault;
|
|
1328
|
+
if (!Number.isFinite(left) || !Number.isFinite(right)) continue;
|
|
1329
|
+
if (relation.operator === "less-than-or-equal" && left <= right) continue;
|
|
1330
|
+
report(
|
|
1331
|
+
node,
|
|
1332
|
+
"invalid-value",
|
|
1333
|
+
"error",
|
|
1334
|
+
`Invalid values for "${identifier}": "${relation.left}" (${left}) must be less than or equal to "${relation.right}" (${right}).`,
|
|
1335
|
+
at,
|
|
1336
|
+
relation.suggestion
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1244
1340
|
});
|
|
1245
1341
|
const scopeIsComplete = (container, scope, identifier) => container.origin === "markup" && !targetsUnreadable(container, scope, identifier);
|
|
1246
1342
|
for (const { node, identifier } of scopes) {
|
|
@@ -1318,6 +1414,28 @@ function checkSource(source, manifest) {
|
|
|
1318
1414
|
}
|
|
1319
1415
|
}
|
|
1320
1416
|
}
|
|
1417
|
+
for (const { node, identifier } of scopes) {
|
|
1418
|
+
const controller = known[identifier];
|
|
1419
|
+
if (!controller) continue;
|
|
1420
|
+
for (const rule of controller.hosts) {
|
|
1421
|
+
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
1422
|
+
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
1423
|
+
for (const element of elements) {
|
|
1424
|
+
if (!hasReadableMarkup(element)) continue;
|
|
1425
|
+
if (hostIsAllowed(element, rule) !== false) continue;
|
|
1426
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1427
|
+
const detail = element.tag === "button" ? `the unsupported ${type ? `type="${type.value}"` : 'implicit type="submit"'}` : `an unsupported interactive <${element.tag}>`;
|
|
1428
|
+
report(
|
|
1429
|
+
element,
|
|
1430
|
+
"invalid-host",
|
|
1431
|
+
"error",
|
|
1432
|
+
`The ${where} of "${identifier}" uses ${detail} host.`,
|
|
1433
|
+
type ?? element,
|
|
1434
|
+
rule.suggestion
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1321
1439
|
for (const { node, identifier } of scopes) {
|
|
1322
1440
|
const controller = known[identifier];
|
|
1323
1441
|
if (!controller) continue;
|
|
@@ -1559,6 +1677,23 @@ function checkSource(source, manifest) {
|
|
|
1559
1677
|
diagnostics.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
1560
1678
|
return diagnostics;
|
|
1561
1679
|
}
|
|
1680
|
+
function valueSatisfiesConstraint(raw, constraint) {
|
|
1681
|
+
const value = decodeNumericValue(raw);
|
|
1682
|
+
if (Number.isNaN(value)) return false;
|
|
1683
|
+
if (constraint.finite && !Number.isFinite(value)) return false;
|
|
1684
|
+
if (constraint.greaterThan !== void 0 && !(value > constraint.greaterThan)) return false;
|
|
1685
|
+
return true;
|
|
1686
|
+
}
|
|
1687
|
+
function decodeNumericValue(raw) {
|
|
1688
|
+
return Number(raw.replace(/_/g, ""));
|
|
1689
|
+
}
|
|
1690
|
+
function describeValueConstraint(constraint) {
|
|
1691
|
+
const parts = [constraint.finite ? "a finite number" : "a number"];
|
|
1692
|
+
if (constraint.greaterThan !== void 0) {
|
|
1693
|
+
parts.push(`greater than ${constraint.greaterThan}`);
|
|
1694
|
+
}
|
|
1695
|
+
return parts.join(" ");
|
|
1696
|
+
}
|
|
1562
1697
|
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
1698
|
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
1699
|
function hasReadableMarkup(el) {
|
|
@@ -1631,6 +1766,20 @@ function describeCounted(target, attr, values) {
|
|
|
1631
1766
|
return `"${target}" targets with ${attr}${constrained}`;
|
|
1632
1767
|
}
|
|
1633
1768
|
var NATIVE_FOCUSABLE_TAGS = /* @__PURE__ */ new Set(["button", "select", "textarea", "summary"]);
|
|
1769
|
+
var ALWAYS_INTERACTIVE_HOSTS = /* @__PURE__ */ new Set([
|
|
1770
|
+
"input",
|
|
1771
|
+
"select",
|
|
1772
|
+
"textarea",
|
|
1773
|
+
"label",
|
|
1774
|
+
"summary",
|
|
1775
|
+
"details",
|
|
1776
|
+
"iframe",
|
|
1777
|
+
"object",
|
|
1778
|
+
"embed"
|
|
1779
|
+
]);
|
|
1780
|
+
function hasAttr(element, name) {
|
|
1781
|
+
return element.attrs.some((attr) => attr.name === name);
|
|
1782
|
+
}
|
|
1634
1783
|
function isNativelyFocusable(el) {
|
|
1635
1784
|
if (NATIVE_FOCUSABLE_TAGS.has(el.tag)) return true;
|
|
1636
1785
|
if (el.tag === "input") {
|
|
@@ -1781,12 +1930,15 @@ function isCheckableFile(path) {
|
|
|
1781
1930
|
var CONTROLLER_ARRAY_FIELDS = [
|
|
1782
1931
|
"targets",
|
|
1783
1932
|
"values",
|
|
1933
|
+
"valueConstraints",
|
|
1934
|
+
"valueRelations",
|
|
1784
1935
|
"actions",
|
|
1785
1936
|
"events",
|
|
1786
1937
|
"requiredTargets",
|
|
1787
1938
|
"conditionalTargets",
|
|
1788
1939
|
"a11y",
|
|
1789
1940
|
"keyboard",
|
|
1941
|
+
"hosts",
|
|
1790
1942
|
"managedAria",
|
|
1791
1943
|
"compositions",
|
|
1792
1944
|
"companions",
|