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
|
@@ -1,12 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process2 from 'process';
|
|
3
|
-
import { statSync, readdirSync
|
|
3
|
+
import { readFileSync, statSync, readdirSync } from 'fs';
|
|
4
4
|
import { relative, join, dirname, extname } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
7
|
|
|
8
8
|
// src/inspector/cli_bin.ts
|
|
9
9
|
|
|
10
|
+
// src/inspector/announcer_rules.ts
|
|
11
|
+
var ANNOUNCER_IDENTIFIER = "stimeo--announcer";
|
|
12
|
+
var ANNOUNCING_CONTROLLERS = [
|
|
13
|
+
"stimeo--auto-submit",
|
|
14
|
+
"stimeo--character-counter",
|
|
15
|
+
"stimeo--countdown",
|
|
16
|
+
"stimeo--direct-upload",
|
|
17
|
+
"stimeo--empty-state",
|
|
18
|
+
"stimeo--flash",
|
|
19
|
+
"stimeo--form-field",
|
|
20
|
+
"stimeo--frame-loading",
|
|
21
|
+
"stimeo--meter",
|
|
22
|
+
"stimeo--multi-select",
|
|
23
|
+
"stimeo--nested-form",
|
|
24
|
+
"stimeo--network-status",
|
|
25
|
+
"stimeo--progress",
|
|
26
|
+
"stimeo--skeleton",
|
|
27
|
+
"stimeo--spinner",
|
|
28
|
+
"stimeo--submit-once",
|
|
29
|
+
"stimeo--tags-input"
|
|
30
|
+
];
|
|
31
|
+
function positionAt(source, index) {
|
|
32
|
+
const before = source.slice(0, index);
|
|
33
|
+
const line = before.split("\n").length;
|
|
34
|
+
const lastBreak = before.lastIndexOf("\n");
|
|
35
|
+
return { line, column: index - lastBreak };
|
|
36
|
+
}
|
|
37
|
+
function firstAnnouncingHit(scanned) {
|
|
38
|
+
let best = null;
|
|
39
|
+
let bestIndex = Number.POSITIVE_INFINITY;
|
|
40
|
+
for (const identifier of ANNOUNCING_CONTROLLERS) {
|
|
41
|
+
const index = scanned.source.indexOf(identifier);
|
|
42
|
+
if (index === -1 || index >= bestIndex) continue;
|
|
43
|
+
bestIndex = index;
|
|
44
|
+
best = { file: scanned.file, identifier, ...positionAt(scanned.source, index) };
|
|
45
|
+
}
|
|
46
|
+
return best;
|
|
47
|
+
}
|
|
48
|
+
function announcerSeatingDiagnostic(sources) {
|
|
49
|
+
let speaker = null;
|
|
50
|
+
for (const scanned of sources) {
|
|
51
|
+
if (scanned.source.includes(ANNOUNCER_IDENTIFIER)) return null;
|
|
52
|
+
if (!speaker) speaker = firstAnnouncingHit(scanned);
|
|
53
|
+
}
|
|
54
|
+
if (!speaker) return null;
|
|
55
|
+
return {
|
|
56
|
+
file: speaker.file,
|
|
57
|
+
diagnostic: {
|
|
58
|
+
code: "missing-announcer",
|
|
59
|
+
severity: "warning",
|
|
60
|
+
message: `"${speaker.identifier}" speaks through the shared announcer, and none of the scanned files seats one.`,
|
|
61
|
+
line: speaker.line,
|
|
62
|
+
column: speaker.column,
|
|
63
|
+
length: speaker.identifier.length,
|
|
64
|
+
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.`
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
10
69
|
// src/inspector/html_parser.ts
|
|
11
70
|
var ERB_ELEMENT_TAG = "#erb";
|
|
12
71
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -932,10 +991,16 @@ function actionDescriptors(dataActionValue) {
|
|
|
932
991
|
if (hash === -1) continue;
|
|
933
992
|
let lhs = descriptor.slice(0, hash);
|
|
934
993
|
const arrow = lhs.indexOf("->");
|
|
935
|
-
|
|
994
|
+
let eventType = "";
|
|
995
|
+
if (arrow !== -1) {
|
|
996
|
+
eventType = lhs.slice(0, arrow);
|
|
997
|
+
lhs = lhs.slice(arrow + 2);
|
|
998
|
+
const at = eventType.indexOf("@");
|
|
999
|
+
if (at !== -1) eventType = eventType.slice(0, at);
|
|
1000
|
+
}
|
|
936
1001
|
if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
|
|
937
1002
|
const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
|
|
938
|
-
descriptors.push({ identifier: lhs, method });
|
|
1003
|
+
descriptors.push({ identifier: lhs, method, eventType });
|
|
939
1004
|
}
|
|
940
1005
|
return descriptors;
|
|
941
1006
|
}
|
|
@@ -954,6 +1019,8 @@ var DIAGNOSTIC_CODES = [
|
|
|
954
1019
|
"orphan-target",
|
|
955
1020
|
"missing-required-target",
|
|
956
1021
|
"missing-conditional-target",
|
|
1022
|
+
"missing-required-action",
|
|
1023
|
+
"missing-action-completion",
|
|
957
1024
|
"missing-aria",
|
|
958
1025
|
"invalid-aria-value",
|
|
959
1026
|
"keyboard-inaccessible",
|
|
@@ -965,6 +1032,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
965
1032
|
"undeclared-target",
|
|
966
1033
|
"cardinality-violation",
|
|
967
1034
|
"forbidden-aria",
|
|
1035
|
+
"missing-announcer",
|
|
968
1036
|
"unknown-ignore-code"
|
|
969
1037
|
];
|
|
970
1038
|
|
|
@@ -975,6 +1043,19 @@ function isInside(node, ancestor) {
|
|
|
975
1043
|
}
|
|
976
1044
|
return false;
|
|
977
1045
|
}
|
|
1046
|
+
function matchesHost(node, selector) {
|
|
1047
|
+
if (node.tag !== selector.tag) return false;
|
|
1048
|
+
if (!selector.attr) return true;
|
|
1049
|
+
const attr = node.attrs.find((candidate) => candidate.name === selector.attr);
|
|
1050
|
+
return attr !== void 0 && (selector.values ?? []).includes(attr.value.trim().toLowerCase());
|
|
1051
|
+
}
|
|
1052
|
+
function nearestHost(node, selectors) {
|
|
1053
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
1054
|
+
if (current.origin !== "markup" || current.opaque) return void 0;
|
|
1055
|
+
if (selectors.some((selector) => matchesHost(current, selector))) return current;
|
|
1056
|
+
}
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
978
1059
|
function checkSource(source, manifest) {
|
|
979
1060
|
const diagnostics = [];
|
|
980
1061
|
const tree = parseHtml(neutralizeErb(source), erbElements(source));
|
|
@@ -993,7 +1074,9 @@ function checkSource(source, manifest) {
|
|
|
993
1074
|
(a) => a.name === `data-${identifier}-${dasherize(when.value)}-value`
|
|
994
1075
|
);
|
|
995
1076
|
if (hostAttr && isDynamicValue(hostAttr)) return null;
|
|
996
|
-
|
|
1077
|
+
const raw = hostAttr ? hostAttr.value : when.default;
|
|
1078
|
+
const effective = when.type === "boolean" ? String(!(raw === "0" || raw.toLowerCase() === "false")) : raw;
|
|
1079
|
+
return when.equals.includes(effective);
|
|
997
1080
|
};
|
|
998
1081
|
const isContentEditable2 = (element) => {
|
|
999
1082
|
let current = element;
|
|
@@ -1066,6 +1149,17 @@ function checkSource(source, manifest) {
|
|
|
1066
1149
|
const scopes = [];
|
|
1067
1150
|
const presence = /* @__PURE__ */ new Map();
|
|
1068
1151
|
const targetNodes = /* @__PURE__ */ new Map();
|
|
1152
|
+
const wiredActions = /* @__PURE__ */ new Map();
|
|
1153
|
+
const recordAction = (owner, identifier, element, method, eventType) => {
|
|
1154
|
+
let byId = wiredActions.get(owner);
|
|
1155
|
+
if (!byId) {
|
|
1156
|
+
byId = /* @__PURE__ */ new Map();
|
|
1157
|
+
wiredActions.set(owner, byId);
|
|
1158
|
+
}
|
|
1159
|
+
const found = byId.get(identifier);
|
|
1160
|
+
if (found) found.push({ element, method, eventType });
|
|
1161
|
+
else byId.set(identifier, [{ element, method, eventType }]);
|
|
1162
|
+
};
|
|
1069
1163
|
const recordPresence = (owner, identifier, target) => {
|
|
1070
1164
|
let byId = presence.get(owner);
|
|
1071
1165
|
if (!byId) {
|
|
@@ -1274,11 +1368,12 @@ function checkSource(source, manifest) {
|
|
|
1274
1368
|
(candidate) => dasherize(candidate.value) === parsed.valueToken
|
|
1275
1369
|
);
|
|
1276
1370
|
if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
|
|
1371
|
+
const literal = constraint.type === "string" ? attr.value : attr.value.trim();
|
|
1277
1372
|
report(
|
|
1278
1373
|
node,
|
|
1279
1374
|
"invalid-value",
|
|
1280
1375
|
"error",
|
|
1281
|
-
`Invalid value "${
|
|
1376
|
+
`Invalid value "${literal}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
|
|
1282
1377
|
attr,
|
|
1283
1378
|
constraint.suggestion
|
|
1284
1379
|
);
|
|
@@ -1289,8 +1384,12 @@ function checkSource(source, manifest) {
|
|
|
1289
1384
|
}
|
|
1290
1385
|
}
|
|
1291
1386
|
if (attr.name === "data-action") {
|
|
1292
|
-
for (const { identifier, method } of actionDescriptors(attr.value)) {
|
|
1387
|
+
for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
|
|
1293
1388
|
const controller = known[identifier];
|
|
1389
|
+
if (controller && method.length > 0) {
|
|
1390
|
+
const owner = findOwner(node, identifier);
|
|
1391
|
+
if (owner) recordAction(owner, identifier, node, method, eventType);
|
|
1392
|
+
}
|
|
1294
1393
|
if (!controller) {
|
|
1295
1394
|
const best = nearestName(identifier, knownIdentifiers);
|
|
1296
1395
|
report(
|
|
@@ -1374,19 +1473,87 @@ function checkSource(source, manifest) {
|
|
|
1374
1473
|
if (!present.has(rule.whenPresent)) continue;
|
|
1375
1474
|
const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
|
|
1376
1475
|
const owners = nodesOf(rule.whenPresent);
|
|
1377
|
-
const
|
|
1476
|
+
const hosts = rule.requireSameHost;
|
|
1477
|
+
const sharesHost = (target, selectors) => {
|
|
1478
|
+
const ownerHosts = owners.map((owner) => nearestHost(owner, selectors));
|
|
1479
|
+
if (ownerHosts.some((host) => !host)) return true;
|
|
1480
|
+
const candidates = nodesOf(target);
|
|
1481
|
+
if (candidates.length === 0) return false;
|
|
1482
|
+
return candidates.some((el) => {
|
|
1483
|
+
const host = nearestHost(el, selectors);
|
|
1484
|
+
return host ? ownerHosts.includes(host) : true;
|
|
1485
|
+
});
|
|
1486
|
+
};
|
|
1487
|
+
const satisfied = (target) => {
|
|
1488
|
+
if (hosts) return sharesHost(target, hosts);
|
|
1489
|
+
if (rule.requireInside)
|
|
1490
|
+
return nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner)));
|
|
1491
|
+
return present.has(target);
|
|
1492
|
+
};
|
|
1378
1493
|
const missing = rule.require.filter((target) => !satisfied(target));
|
|
1379
1494
|
if (missing.length === 0) continue;
|
|
1380
1495
|
report(
|
|
1381
1496
|
node,
|
|
1382
1497
|
"missing-conditional-target",
|
|
1383
1498
|
complete ? "error" : "warning",
|
|
1384
|
-
rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1499
|
+
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)}.`,
|
|
1385
1500
|
node,
|
|
1386
1501
|
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
1387
1502
|
);
|
|
1388
1503
|
}
|
|
1389
1504
|
}
|
|
1505
|
+
for (const { node, identifier } of scopes) {
|
|
1506
|
+
const controller = known[identifier];
|
|
1507
|
+
if (!controller) continue;
|
|
1508
|
+
const wired = wiredActions.get(node)?.get(identifier) ?? [];
|
|
1509
|
+
for (const rule of controller.requiredActions) {
|
|
1510
|
+
if (conditionHolds(node, identifier, rule.when) !== true) continue;
|
|
1511
|
+
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
1512
|
+
for (const element of elements) {
|
|
1513
|
+
const actionAttr = element.attrs.find((attr) => attr.name === "data-action");
|
|
1514
|
+
if (element.opaque || actionAttr && isDynamicValue(actionAttr)) continue;
|
|
1515
|
+
const satisfied = wired.some(
|
|
1516
|
+
(action) => action.element === element && action.method === rule.action && rule.eventTypes.includes(action.eventType)
|
|
1517
|
+
);
|
|
1518
|
+
if (satisfied) continue;
|
|
1519
|
+
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
1520
|
+
report(
|
|
1521
|
+
element,
|
|
1522
|
+
"missing-required-action",
|
|
1523
|
+
"error",
|
|
1524
|
+
`The ${where} of "${identifier}" requires action "${rule.action}" on ${list(rule.eventTypes)} in this configuration.`,
|
|
1525
|
+
element,
|
|
1526
|
+
rule.suggestion
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
for (const { node, identifier } of scopes) {
|
|
1532
|
+
const controller = known[identifier];
|
|
1533
|
+
if (!controller) continue;
|
|
1534
|
+
const wired = wiredActions.get(node)?.get(identifier) ?? [];
|
|
1535
|
+
if (wired.length === 0) continue;
|
|
1536
|
+
for (const rule of controller.actionCompletion) {
|
|
1537
|
+
const opens = wired.some(
|
|
1538
|
+
(action) => action.method === rule.opens && rule.whenTriggeredBy.includes(action.eventType)
|
|
1539
|
+
);
|
|
1540
|
+
if (!opens) continue;
|
|
1541
|
+
if (wired.some((action) => rule.closedBy.includes(action.method))) continue;
|
|
1542
|
+
if (rule.escapeValue) {
|
|
1543
|
+
const attrName = `data-${identifier}-${dasherize(rule.escapeValue)}-value`;
|
|
1544
|
+
const authored = node.attrs.find((attr) => attr.name === attrName);
|
|
1545
|
+
if (authored && (isDynamicValue(authored) || Number(authored.value.trim()) !== 0)) continue;
|
|
1546
|
+
}
|
|
1547
|
+
report(
|
|
1548
|
+
node,
|
|
1549
|
+
"missing-action-completion",
|
|
1550
|
+
"warning",
|
|
1551
|
+
`"${identifier}" opens a state with "${rule.opens}" here, and nothing in this scope closes it.`,
|
|
1552
|
+
node,
|
|
1553
|
+
rule.suggestion
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1390
1557
|
for (const { node, identifier } of scopes) {
|
|
1391
1558
|
const controller = known[identifier];
|
|
1392
1559
|
if (!controller) continue;
|
|
@@ -1689,6 +1856,7 @@ function checkSource(source, manifest) {
|
|
|
1689
1856
|
return diagnostics;
|
|
1690
1857
|
}
|
|
1691
1858
|
function valueSatisfiesConstraint(raw, constraint) {
|
|
1859
|
+
if (constraint.type === "string") return constraint.allowedValues.includes(raw);
|
|
1692
1860
|
const value = decodeNumericValue(raw);
|
|
1693
1861
|
if (Number.isNaN(value)) return false;
|
|
1694
1862
|
if (constraint.finite && !Number.isFinite(value)) return false;
|
|
@@ -1700,6 +1868,7 @@ function decodeNumericValue(raw) {
|
|
|
1700
1868
|
return Number(raw.replace(/_/g, ""));
|
|
1701
1869
|
}
|
|
1702
1870
|
function describeValueConstraint(constraint) {
|
|
1871
|
+
if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
|
|
1703
1872
|
const parts = [constraint.finite ? "a finite number" : "a number"];
|
|
1704
1873
|
if (constraint.greaterThan !== void 0) {
|
|
1705
1874
|
parts.push(`greater than ${constraint.greaterThan}`);
|
|
@@ -1954,6 +2123,8 @@ var CONTROLLER_ARRAY_FIELDS = [
|
|
|
1954
2123
|
"events",
|
|
1955
2124
|
"requiredTargets",
|
|
1956
2125
|
"conditionalTargets",
|
|
2126
|
+
"requiredActions",
|
|
2127
|
+
"actionCompletion",
|
|
1957
2128
|
"a11y",
|
|
1958
2129
|
"keyboard",
|
|
1959
2130
|
"hosts",
|
|
@@ -2016,10 +2187,16 @@ function collectFiles(target, out) {
|
|
|
2016
2187
|
}
|
|
2017
2188
|
if (isCheckableFile(target)) out.push(target);
|
|
2018
2189
|
}
|
|
2019
|
-
function checkFile(file, manifest) {
|
|
2020
|
-
const source = readFileSync(file, "utf8");
|
|
2190
|
+
function checkFile(file, source, manifest) {
|
|
2021
2191
|
return { file, diagnostics: checkSource(source, manifest) };
|
|
2022
2192
|
}
|
|
2193
|
+
function withAnnouncerSeating(reports, sources) {
|
|
2194
|
+
const seating = announcerSeatingDiagnostic(sources);
|
|
2195
|
+
if (!seating) return [...reports];
|
|
2196
|
+
return reports.map(
|
|
2197
|
+
(report) => report.file === seating.file ? { file: report.file, diagnostics: [...report.diagnostics, seating.diagnostic] } : report
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2023
2200
|
function formatDiagnostic(d) {
|
|
2024
2201
|
const where = `${d.line}:${d.column}`.padEnd(7);
|
|
2025
2202
|
const head = ` ${where} ${d.severity.padEnd(7)} ${d.message} [${d.code}]`;
|
|
@@ -2152,13 +2329,14 @@ ${USAGE}`);
|
|
|
2152
2329
|
const manifest = load();
|
|
2153
2330
|
const cwd = process2.cwd();
|
|
2154
2331
|
const reports = [];
|
|
2332
|
+
const sources = [];
|
|
2155
2333
|
for (const file of files.sort()) {
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2334
|
+
const path = relative(cwd, file) || file;
|
|
2335
|
+
const source = readFileSync(file, "utf8");
|
|
2336
|
+
sources.push({ file: path, source });
|
|
2337
|
+
reports.push(checkFile(path, source, manifest));
|
|
2160
2338
|
}
|
|
2161
|
-
const summary = buildCheckReport(reports, files.length);
|
|
2339
|
+
const summary = buildCheckReport(withAnnouncerSeating(reports, sources), files.length);
|
|
2162
2340
|
if (json) {
|
|
2163
2341
|
write(JSON.stringify(summary, null, 2));
|
|
2164
2342
|
return summary.errorCount > 0 ? 1 : 0;
|
|
@@ -2388,6 +2566,17 @@ var EXAMPLE_RESULT_SCHEMA = {
|
|
|
2388
2566
|
},
|
|
2389
2567
|
required: ["id", "file", "source", "guidance"]
|
|
2390
2568
|
};
|
|
2569
|
+
var VALUE_CONDITION_SCHEMA = {
|
|
2570
|
+
type: "object",
|
|
2571
|
+
description: "Own-value condition arming a rule; absent means unconditional.",
|
|
2572
|
+
properties: {
|
|
2573
|
+
value: { type: "string" },
|
|
2574
|
+
type: { type: "string", enum: ["boolean", "string"] },
|
|
2575
|
+
equals: STRING_ARRAY,
|
|
2576
|
+
default: { type: "string" }
|
|
2577
|
+
},
|
|
2578
|
+
required: ["value", "type", "equals", "default"]
|
|
2579
|
+
};
|
|
2391
2580
|
var CONTENT_CONDITION_SCHEMA = {
|
|
2392
2581
|
type: "object",
|
|
2393
2582
|
description: "Condition on the element's own contents arming the rule; absent means it does not depend on them. Evaluated per element, unlike the scope-level `when`.",
|
|
@@ -2436,9 +2625,14 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2436
2625
|
type: "object",
|
|
2437
2626
|
properties: {
|
|
2438
2627
|
value: { type: "string", description: "Camel-case declared Value name." },
|
|
2439
|
-
type: { type: "string", enum: ["number"] },
|
|
2628
|
+
type: { type: "string", enum: ["number", "string"] },
|
|
2440
2629
|
finite: { type: "boolean" },
|
|
2441
2630
|
greaterThan: { type: "number" },
|
|
2631
|
+
integer: { type: "boolean" },
|
|
2632
|
+
allowedValues: {
|
|
2633
|
+
...STRING_ARRAY,
|
|
2634
|
+
description: "Accepted literals for a String Value constraint."
|
|
2635
|
+
},
|
|
2442
2636
|
suggestion: { type: "string" }
|
|
2443
2637
|
},
|
|
2444
2638
|
required: ["value", "type", "suggestion"]
|
|
@@ -2479,6 +2673,45 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2479
2673
|
required: ["whenPresent", "require", "suggestion"]
|
|
2480
2674
|
}
|
|
2481
2675
|
},
|
|
2676
|
+
requiredActions: {
|
|
2677
|
+
type: "array",
|
|
2678
|
+
description: "Action bindings required on a particular element in one configured Value state.",
|
|
2679
|
+
items: {
|
|
2680
|
+
type: "object",
|
|
2681
|
+
properties: {
|
|
2682
|
+
target: { type: "string", description: 'Target name; "" means the scope element.' },
|
|
2683
|
+
action: { type: "string", description: "Public action method that must be wired." },
|
|
2684
|
+
eventTypes: {
|
|
2685
|
+
...STRING_ARRAY,
|
|
2686
|
+
description: "Event types that make the binding usable for this contract."
|
|
2687
|
+
},
|
|
2688
|
+
when: VALUE_CONDITION_SCHEMA,
|
|
2689
|
+
suggestion: { type: "string" }
|
|
2690
|
+
},
|
|
2691
|
+
required: ["target", "action", "eventTypes", "suggestion"]
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
actionCompletion: {
|
|
2695
|
+
type: "array",
|
|
2696
|
+
description: "States an action opens that the consumer's own wiring has to close. Wiring the opening action without any closing one leaves the controller busy for good, and nothing else in the markup reveals it.",
|
|
2697
|
+
items: {
|
|
2698
|
+
type: "object",
|
|
2699
|
+
properties: {
|
|
2700
|
+
opens: { type: "string", description: "Action that opens the state." },
|
|
2701
|
+
whenTriggeredBy: {
|
|
2702
|
+
...STRING_ARRAY,
|
|
2703
|
+
description: "Event types for that action the controller cannot complete itself."
|
|
2704
|
+
},
|
|
2705
|
+
closedBy: { ...STRING_ARRAY, description: "Any one of these closes it again." },
|
|
2706
|
+
escapeValue: {
|
|
2707
|
+
type: "string",
|
|
2708
|
+
description: "Value whose non-zero number supplies an automatic exit instead."
|
|
2709
|
+
},
|
|
2710
|
+
suggestion: { type: "string" }
|
|
2711
|
+
},
|
|
2712
|
+
required: ["opens", "whenTriggeredBy", "closedBy", "suggestion"]
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2482
2715
|
a11y: {
|
|
2483
2716
|
type: "array",
|
|
2484
2717
|
description: "Author-supplied ARIA the controller relies on but never sets itself.",
|
|
@@ -2500,16 +2733,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2500
2733
|
required: ["attrs"]
|
|
2501
2734
|
}
|
|
2502
2735
|
},
|
|
2503
|
-
when:
|
|
2504
|
-
type: "object",
|
|
2505
|
-
description: "Own-value condition arming the requirement; absent means unconditional. Outside the named configuration the ARIA is not optional but wrong.",
|
|
2506
|
-
properties: {
|
|
2507
|
-
value: { type: "string" },
|
|
2508
|
-
equals: STRING_ARRAY,
|
|
2509
|
-
default: { type: "string" }
|
|
2510
|
-
},
|
|
2511
|
-
required: ["value", "equals", "default"]
|
|
2512
|
-
},
|
|
2736
|
+
when: VALUE_CONDITION_SCHEMA,
|
|
2513
2737
|
whenContains: CONTENT_CONDITION_SCHEMA,
|
|
2514
2738
|
whenElement: ELEMENT_CONDITION_SCHEMA,
|
|
2515
2739
|
whenDocument: DOCUMENT_CONDITION_SCHEMA,
|
|
@@ -2593,16 +2817,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2593
2817
|
description: "Check the scope element when the target is absent."
|
|
2594
2818
|
},
|
|
2595
2819
|
coController: { type: "string", description: "Companion controller identifier." },
|
|
2596
|
-
when:
|
|
2597
|
-
type: "object",
|
|
2598
|
-
description: "Host-value condition arming the rule; absent means unconditional.",
|
|
2599
|
-
properties: {
|
|
2600
|
-
value: { type: "string" },
|
|
2601
|
-
equals: STRING_ARRAY,
|
|
2602
|
-
default: { type: "string" }
|
|
2603
|
-
},
|
|
2604
|
-
required: ["value", "equals", "default"]
|
|
2605
|
-
},
|
|
2820
|
+
when: VALUE_CONDITION_SCHEMA,
|
|
2606
2821
|
require: {
|
|
2607
2822
|
type: "object",
|
|
2608
2823
|
description: "Companion-value requirement (first allowed value is canonical).",
|
|
@@ -2666,16 +2881,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2666
2881
|
values: { ...STRING_ARRAY, description: "Restrict the attribute to these values." },
|
|
2667
2882
|
min: { type: "integer", description: "Smallest permitted count (inclusive)." },
|
|
2668
2883
|
max: { type: "integer", description: "Largest permitted count (inclusive)." },
|
|
2669
|
-
when:
|
|
2670
|
-
type: "object",
|
|
2671
|
-
description: "Own-value condition arming the bound; absent means unconditional.",
|
|
2672
|
-
properties: {
|
|
2673
|
-
value: { type: "string" },
|
|
2674
|
-
equals: STRING_ARRAY,
|
|
2675
|
-
default: { type: "string" }
|
|
2676
|
-
},
|
|
2677
|
-
required: ["value", "equals", "default"]
|
|
2678
|
-
},
|
|
2884
|
+
when: VALUE_CONDITION_SCHEMA,
|
|
2679
2885
|
suggestion: { type: "string" }
|
|
2680
2886
|
},
|
|
2681
2887
|
required: ["within", "target", "suggestion"]
|
|
@@ -2707,6 +2913,8 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2707
2913
|
"events",
|
|
2708
2914
|
"requiredTargets",
|
|
2709
2915
|
"conditionalTargets",
|
|
2916
|
+
"requiredActions",
|
|
2917
|
+
"actionCompletion",
|
|
2710
2918
|
"a11y",
|
|
2711
2919
|
"keyboard",
|
|
2712
2920
|
"hosts",
|