stimeo-ui 0.6.0 → 0.8.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.
Files changed (212) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/controllers/alert_dialog_controller.d.ts +2 -0
  3. package/dist/controllers/alert_dialog_controller.js +75 -4
  4. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  5. package/dist/controllers/auto_submit_controller.d.ts +16 -3
  6. package/dist/controllers/auto_submit_controller.js +94 -16
  7. package/dist/controllers/auto_submit_controller.js.map +1 -1
  8. package/dist/controllers/avatar_controller.js +47 -5
  9. package/dist/controllers/avatar_controller.js.map +1 -1
  10. package/dist/controllers/breadcrumb_controller.d.ts +2 -0
  11. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  12. package/dist/controllers/bulk_select_controller.d.ts +2 -0
  13. package/dist/controllers/bulk_select_controller.js.map +1 -1
  14. package/dist/controllers/calendar_controller.d.ts +2 -0
  15. package/dist/controllers/calendar_controller.js.map +1 -1
  16. package/dist/controllers/carousel_controller.d.ts +81 -47
  17. package/dist/controllers/carousel_controller.js +451 -100
  18. package/dist/controllers/carousel_controller.js.map +1 -1
  19. package/dist/controllers/character_counter_controller.d.ts +52 -20
  20. package/dist/controllers/character_counter_controller.js +338 -63
  21. package/dist/controllers/character_counter_controller.js.map +1 -1
  22. package/dist/controllers/checkbox_controller.d.ts +3 -0
  23. package/dist/controllers/checkbox_controller.js.map +1 -1
  24. package/dist/controllers/color_picker_controller.d.ts +3 -0
  25. package/dist/controllers/color_picker_controller.js.map +1 -1
  26. package/dist/controllers/combobox_controller.d.ts +2 -0
  27. package/dist/controllers/combobox_controller.js.map +1 -1
  28. package/dist/controllers/command_palette_controller.d.ts +2 -0
  29. package/dist/controllers/command_palette_controller.js +75 -4
  30. package/dist/controllers/command_palette_controller.js.map +1 -1
  31. package/dist/controllers/conditional_fields_controller.d.ts +42 -14
  32. package/dist/controllers/conditional_fields_controller.js +345 -51
  33. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  34. package/dist/controllers/confirm_controller.d.ts +2 -0
  35. package/dist/controllers/confirm_controller.js +75 -4
  36. package/dist/controllers/confirm_controller.js.map +1 -1
  37. package/dist/controllers/count_up_controller.d.ts +2 -0
  38. package/dist/controllers/count_up_controller.js.map +1 -1
  39. package/dist/controllers/countdown_controller.d.ts +4 -0
  40. package/dist/controllers/countdown_controller.js.map +1 -1
  41. package/dist/controllers/currency_input_controller.d.ts +40 -4
  42. package/dist/controllers/currency_input_controller.js +305 -74
  43. package/dist/controllers/currency_input_controller.js.map +1 -1
  44. package/dist/controllers/data_grid_controller.d.ts +3 -0
  45. package/dist/controllers/data_grid_controller.js.map +1 -1
  46. package/dist/controllers/dialog_controller.js +75 -4
  47. package/dist/controllers/dialog_controller.js.map +1 -1
  48. package/dist/controllers/direct_upload_controller.d.ts +59 -25
  49. package/dist/controllers/direct_upload_controller.js +212 -46
  50. package/dist/controllers/direct_upload_controller.js.map +1 -1
  51. package/dist/controllers/dirty_form_controller.d.ts +14 -6
  52. package/dist/controllers/dirty_form_controller.js +192 -29
  53. package/dist/controllers/dirty_form_controller.js.map +1 -1
  54. package/dist/controllers/dismissible_controller.d.ts +2 -0
  55. package/dist/controllers/dismissible_controller.js +83 -18
  56. package/dist/controllers/dismissible_controller.js.map +1 -1
  57. package/dist/controllers/drawer_controller.js +75 -4
  58. package/dist/controllers/drawer_controller.js.map +1 -1
  59. package/dist/controllers/empty_state_controller.d.ts +2 -0
  60. package/dist/controllers/empty_state_controller.js.map +1 -1
  61. package/dist/controllers/file_dropzone_controller.d.ts +123 -27
  62. package/dist/controllers/file_dropzone_controller.js +386 -63
  63. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  64. package/dist/controllers/filter_controller.d.ts +2 -0
  65. package/dist/controllers/filter_controller.js.map +1 -1
  66. package/dist/controllers/flash_controller.d.ts +7 -1
  67. package/dist/controllers/flash_controller.js +3 -1
  68. package/dist/controllers/flash_controller.js.map +1 -1
  69. package/dist/controllers/focus_controller.d.ts +2 -0
  70. package/dist/controllers/focus_controller.js +75 -4
  71. package/dist/controllers/focus_controller.js.map +1 -1
  72. package/dist/controllers/form_field_controller.d.ts +50 -10
  73. package/dist/controllers/form_field_controller.js +280 -62
  74. package/dist/controllers/form_field_controller.js.map +1 -1
  75. package/dist/controllers/form_validation_controller.d.ts +10 -8
  76. package/dist/controllers/form_validation_controller.js +208 -83
  77. package/dist/controllers/form_validation_controller.js.map +1 -1
  78. package/dist/controllers/frame_loading_controller.d.ts +5 -1
  79. package/dist/controllers/frame_loading_controller.js +2 -1
  80. package/dist/controllers/frame_loading_controller.js.map +1 -1
  81. package/dist/controllers/highlight_controller.d.ts +2 -0
  82. package/dist/controllers/highlight_controller.js.map +1 -1
  83. package/dist/controllers/idle_controller.d.ts +2 -0
  84. package/dist/controllers/idle_controller.js.map +1 -1
  85. package/dist/controllers/input_mask_controller.d.ts +43 -15
  86. package/dist/controllers/input_mask_controller.js +251 -76
  87. package/dist/controllers/input_mask_controller.js.map +1 -1
  88. package/dist/controllers/lazy_frame_controller.d.ts +2 -0
  89. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  90. package/dist/controllers/listbox_controller.d.ts +2 -0
  91. package/dist/controllers/listbox_controller.js.map +1 -1
  92. package/dist/controllers/local_time_controller.d.ts +2 -0
  93. package/dist/controllers/local_time_controller.js.map +1 -1
  94. package/dist/controllers/masonry_controller.d.ts +2 -0
  95. package/dist/controllers/masonry_controller.js.map +1 -1
  96. package/dist/controllers/meter_controller.d.ts +2 -0
  97. package/dist/controllers/meter_controller.js.map +1 -1
  98. package/dist/controllers/multi_select_controller.d.ts +2 -0
  99. package/dist/controllers/multi_select_controller.js.map +1 -1
  100. package/dist/controllers/nested_form_controller.d.ts +52 -13
  101. package/dist/controllers/nested_form_controller.js +450 -42
  102. package/dist/controllers/nested_form_controller.js.map +1 -1
  103. package/dist/controllers/network_status_controller.d.ts +2 -0
  104. package/dist/controllers/network_status_controller.js.map +1 -1
  105. package/dist/controllers/number_input_controller.d.ts +2 -0
  106. package/dist/controllers/number_input_controller.js +47 -5
  107. package/dist/controllers/number_input_controller.js.map +1 -1
  108. package/dist/controllers/otp_controller.d.ts +62 -23
  109. package/dist/controllers/otp_controller.js +472 -114
  110. package/dist/controllers/otp_controller.js.map +1 -1
  111. package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
  112. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  113. package/dist/controllers/overflow_menu_controller.d.ts +2 -0
  114. package/dist/controllers/overflow_menu_controller.js +2 -1
  115. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  116. package/dist/controllers/pagination_controller.d.ts +2 -0
  117. package/dist/controllers/pagination_controller.js +2 -1
  118. package/dist/controllers/pagination_controller.js.map +1 -1
  119. package/dist/controllers/password_reveal_controller.d.ts +2 -0
  120. package/dist/controllers/password_reveal_controller.js.map +1 -1
  121. package/dist/controllers/password_strength_controller.d.ts +2 -0
  122. package/dist/controllers/password_strength_controller.js.map +1 -1
  123. package/dist/controllers/persist_controller.d.ts +30 -16
  124. package/dist/controllers/persist_controller.js +432 -122
  125. package/dist/controllers/persist_controller.js.map +1 -1
  126. package/dist/controllers/pointer_drag_controller.d.ts +3 -0
  127. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  128. package/dist/controllers/popover_controller.js +77 -4
  129. package/dist/controllers/popover_controller.js.map +1 -1
  130. package/dist/controllers/portal_controller.d.ts +2 -0
  131. package/dist/controllers/portal_controller.js.map +1 -1
  132. package/dist/controllers/preview_guard_controller.d.ts +2 -0
  133. package/dist/controllers/preview_guard_controller.js.map +1 -1
  134. package/dist/controllers/progress_controller.d.ts +3 -0
  135. package/dist/controllers/progress_controller.js.map +1 -1
  136. package/dist/controllers/radio_group_controller.d.ts +3 -0
  137. package/dist/controllers/radio_group_controller.js.map +1 -1
  138. package/dist/controllers/rating_controller.d.ts +2 -0
  139. package/dist/controllers/rating_controller.js +9 -5
  140. package/dist/controllers/rating_controller.js.map +1 -1
  141. package/dist/controllers/reading_progress_controller.d.ts +2 -0
  142. package/dist/controllers/reading_progress_controller.js.map +1 -1
  143. package/dist/controllers/resizable_controller.d.ts +2 -0
  144. package/dist/controllers/resizable_controller.js.map +1 -1
  145. package/dist/controllers/roving_controller.d.ts +2 -0
  146. package/dist/controllers/roving_controller.js.map +1 -1
  147. package/dist/controllers/scroll_area_controller.d.ts +23 -15
  148. package/dist/controllers/scroll_area_controller.js +462 -162
  149. package/dist/controllers/scroll_area_controller.js.map +1 -1
  150. package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
  151. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  152. package/dist/controllers/scrollspy_controller.d.ts +2 -0
  153. package/dist/controllers/scrollspy_controller.js.map +1 -1
  154. package/dist/controllers/separator_controller.d.ts +54 -11
  155. package/dist/controllers/separator_controller.js +354 -38
  156. package/dist/controllers/separator_controller.js.map +1 -1
  157. package/dist/controllers/sidebar_controller.js +83 -10
  158. package/dist/controllers/sidebar_controller.js.map +1 -1
  159. package/dist/controllers/skeleton_controller.d.ts +2 -0
  160. package/dist/controllers/skeleton_controller.js.map +1 -1
  161. package/dist/controllers/slider_controller.d.ts +2 -0
  162. package/dist/controllers/slider_controller.js.map +1 -1
  163. package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
  164. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  165. package/dist/controllers/spinner_controller.d.ts +4 -1
  166. package/dist/controllers/spinner_controller.js +7 -5
  167. package/dist/controllers/spinner_controller.js.map +1 -1
  168. package/dist/controllers/step_indicator_controller.d.ts +2 -0
  169. package/dist/controllers/step_indicator_controller.js.map +1 -1
  170. package/dist/controllers/stepper_controller.d.ts +2 -0
  171. package/dist/controllers/stepper_controller.js.map +1 -1
  172. package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
  173. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  174. package/dist/controllers/sticky_observer_controller.d.ts +2 -0
  175. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  176. package/dist/controllers/submit_once_controller.d.ts +98 -39
  177. package/dist/controllers/submit_once_controller.js +402 -122
  178. package/dist/controllers/submit_once_controller.js.map +1 -1
  179. package/dist/controllers/switch_controller.d.ts +2 -0
  180. package/dist/controllers/switch_controller.js.map +1 -1
  181. package/dist/controllers/tags_input_controller.d.ts +2 -0
  182. package/dist/controllers/tags_input_controller.js.map +1 -1
  183. package/dist/controllers/textarea_autosize_controller.d.ts +31 -9
  184. package/dist/controllers/textarea_autosize_controller.js +131 -3
  185. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  186. package/dist/controllers/theme_controller.d.ts +2 -0
  187. package/dist/controllers/theme_controller.js +8 -6
  188. package/dist/controllers/theme_controller.js.map +1 -1
  189. package/dist/controllers/time_picker_controller.d.ts +2 -0
  190. package/dist/controllers/time_picker_controller.js.map +1 -1
  191. package/dist/controllers/toast_controller.d.ts +4 -0
  192. package/dist/controllers/toast_controller.js.map +1 -1
  193. package/dist/controllers/toggle_group_controller.d.ts +2 -0
  194. package/dist/controllers/toggle_group_controller.js.map +1 -1
  195. package/dist/controllers/toolbar_controller.js.map +1 -1
  196. package/dist/controllers/transition_controller.d.ts +2 -0
  197. package/dist/controllers/transition_controller.js.map +1 -1
  198. package/dist/controllers/tree_view_controller.d.ts +2 -0
  199. package/dist/controllers/tree_view_controller.js +2 -1
  200. package/dist/controllers/tree_view_controller.js.map +1 -1
  201. package/dist/index.js +4293 -1447
  202. package/dist/index.js.map +1 -1
  203. package/dist/inspector/cli.d.ts +115 -4
  204. package/dist/inspector/cli.js +195 -15
  205. package/dist/inspector/cli.js.map +1 -1
  206. package/dist/inspector/cli_bin.js +257 -47
  207. package/dist/inspector/cli_bin.js.map +1 -1
  208. package/dist/inspector/examples.json +11 -11
  209. package/dist/inspector/manifest.json +615 -66
  210. package/dist/positioning/index.d.ts +2 -0
  211. package/dist/positioning/index.js.map +1 -1
  212. package/package.json +2 -2
@@ -1,12 +1,72 @@
1
1
  #!/usr/bin/env node
2
2
  import process2 from 'process';
3
- import { statSync, readdirSync, readFileSync } from 'fs';
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--file-dropzone",
19
+ "stimeo--flash",
20
+ "stimeo--form-field",
21
+ "stimeo--frame-loading",
22
+ "stimeo--meter",
23
+ "stimeo--multi-select",
24
+ "stimeo--nested-form",
25
+ "stimeo--network-status",
26
+ "stimeo--progress",
27
+ "stimeo--skeleton",
28
+ "stimeo--spinner",
29
+ "stimeo--submit-once",
30
+ "stimeo--tags-input"
31
+ ];
32
+ function positionAt(source, index) {
33
+ const before = source.slice(0, index);
34
+ const line = before.split("\n").length;
35
+ const lastBreak = before.lastIndexOf("\n");
36
+ return { line, column: index - lastBreak };
37
+ }
38
+ function firstAnnouncingHit(scanned) {
39
+ let best = null;
40
+ let bestIndex = Number.POSITIVE_INFINITY;
41
+ for (const identifier of ANNOUNCING_CONTROLLERS) {
42
+ const index = scanned.source.indexOf(identifier);
43
+ if (index === -1 || index >= bestIndex) continue;
44
+ bestIndex = index;
45
+ best = { file: scanned.file, identifier, ...positionAt(scanned.source, index) };
46
+ }
47
+ return best;
48
+ }
49
+ function announcerSeatingDiagnostic(sources) {
50
+ let speaker = null;
51
+ for (const scanned of sources) {
52
+ if (scanned.source.includes(ANNOUNCER_IDENTIFIER)) return null;
53
+ if (!speaker) speaker = firstAnnouncingHit(scanned);
54
+ }
55
+ if (!speaker) return null;
56
+ return {
57
+ file: speaker.file,
58
+ diagnostic: {
59
+ code: "missing-announcer",
60
+ severity: "warning",
61
+ message: `"${speaker.identifier}" speaks through the shared announcer, and none of the scanned files seats one.`,
62
+ line: speaker.line,
63
+ column: speaker.column,
64
+ length: speaker.identifier.length,
65
+ 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.`
66
+ }
67
+ };
68
+ }
69
+
10
70
  // src/inspector/html_parser.ts
11
71
  var ERB_ELEMENT_TAG = "#erb";
12
72
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
@@ -932,10 +992,16 @@ function actionDescriptors(dataActionValue) {
932
992
  if (hash === -1) continue;
933
993
  let lhs = descriptor.slice(0, hash);
934
994
  const arrow = lhs.indexOf("->");
935
- if (arrow !== -1) lhs = lhs.slice(arrow + 2);
995
+ let eventType = "";
996
+ if (arrow !== -1) {
997
+ eventType = lhs.slice(0, arrow);
998
+ lhs = lhs.slice(arrow + 2);
999
+ const at = eventType.indexOf("@");
1000
+ if (at !== -1) eventType = eventType.slice(0, at);
1001
+ }
936
1002
  if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
937
1003
  const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
938
- descriptors.push({ identifier: lhs, method });
1004
+ descriptors.push({ identifier: lhs, method, eventType });
939
1005
  }
940
1006
  return descriptors;
941
1007
  }
@@ -954,6 +1020,8 @@ var DIAGNOSTIC_CODES = [
954
1020
  "orphan-target",
955
1021
  "missing-required-target",
956
1022
  "missing-conditional-target",
1023
+ "missing-required-action",
1024
+ "missing-action-completion",
957
1025
  "missing-aria",
958
1026
  "invalid-aria-value",
959
1027
  "keyboard-inaccessible",
@@ -965,6 +1033,7 @@ var DIAGNOSTIC_CODES = [
965
1033
  "undeclared-target",
966
1034
  "cardinality-violation",
967
1035
  "forbidden-aria",
1036
+ "missing-announcer",
968
1037
  "unknown-ignore-code"
969
1038
  ];
970
1039
 
@@ -975,6 +1044,19 @@ function isInside(node, ancestor) {
975
1044
  }
976
1045
  return false;
977
1046
  }
1047
+ function matchesHost(node, selector) {
1048
+ if (node.tag !== selector.tag) return false;
1049
+ if (!selector.attr) return true;
1050
+ const attr = node.attrs.find((candidate) => candidate.name === selector.attr);
1051
+ return attr !== void 0 && (selector.values ?? []).includes(attr.value.trim().toLowerCase());
1052
+ }
1053
+ function nearestHost(node, selectors) {
1054
+ for (let current = node.parent; current; current = current.parent) {
1055
+ if (current.origin !== "markup" || current.opaque) return void 0;
1056
+ if (selectors.some((selector) => matchesHost(current, selector))) return current;
1057
+ }
1058
+ return null;
1059
+ }
978
1060
  function checkSource(source, manifest) {
979
1061
  const diagnostics = [];
980
1062
  const tree = parseHtml(neutralizeErb(source), erbElements(source));
@@ -993,7 +1075,9 @@ function checkSource(source, manifest) {
993
1075
  (a) => a.name === `data-${identifier}-${dasherize(when.value)}-value`
994
1076
  );
995
1077
  if (hostAttr && isDynamicValue(hostAttr)) return null;
996
- return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
1078
+ const raw = hostAttr ? hostAttr.value : when.default;
1079
+ const effective = when.type === "boolean" ? String(!(raw === "0" || raw.toLowerCase() === "false")) : raw;
1080
+ return when.equals.includes(effective);
997
1081
  };
998
1082
  const isContentEditable2 = (element) => {
999
1083
  let current = element;
@@ -1066,6 +1150,17 @@ function checkSource(source, manifest) {
1066
1150
  const scopes = [];
1067
1151
  const presence = /* @__PURE__ */ new Map();
1068
1152
  const targetNodes = /* @__PURE__ */ new Map();
1153
+ const wiredActions = /* @__PURE__ */ new Map();
1154
+ const recordAction = (owner, identifier, element, method, eventType) => {
1155
+ let byId = wiredActions.get(owner);
1156
+ if (!byId) {
1157
+ byId = /* @__PURE__ */ new Map();
1158
+ wiredActions.set(owner, byId);
1159
+ }
1160
+ const found = byId.get(identifier);
1161
+ if (found) found.push({ element, method, eventType });
1162
+ else byId.set(identifier, [{ element, method, eventType }]);
1163
+ };
1069
1164
  const recordPresence = (owner, identifier, target) => {
1070
1165
  let byId = presence.get(owner);
1071
1166
  if (!byId) {
@@ -1097,8 +1192,9 @@ function checkSource(source, manifest) {
1097
1192
  const contentHolds = (scope, identifier, element, condition) => {
1098
1193
  if (!condition) return true;
1099
1194
  const nodes = targetNodes.get(scope)?.get(identifier)?.get(condition.target) ?? [];
1195
+ const container = condition.within === "scope" ? scope : element;
1100
1196
  let held = 0;
1101
- for (const node of nodes) if (isWithin(node, element)) held += 1;
1197
+ for (const node of nodes) if (isWithin(node, container)) held += 1;
1102
1198
  if (condition.min !== void 0 && held < condition.min) return false;
1103
1199
  if (condition.max !== void 0 && held > condition.max) return false;
1104
1200
  return true;
@@ -1274,11 +1370,12 @@ function checkSource(source, manifest) {
1274
1370
  (candidate) => dasherize(candidate.value) === parsed.valueToken
1275
1371
  );
1276
1372
  if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
1373
+ const literal = constraint.type === "string" ? attr.value : attr.value.trim();
1277
1374
  report(
1278
1375
  node,
1279
1376
  "invalid-value",
1280
1377
  "error",
1281
- `Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1378
+ `Invalid value "${literal}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1282
1379
  attr,
1283
1380
  constraint.suggestion
1284
1381
  );
@@ -1289,8 +1386,12 @@ function checkSource(source, manifest) {
1289
1386
  }
1290
1387
  }
1291
1388
  if (attr.name === "data-action") {
1292
- for (const { identifier, method } of actionDescriptors(attr.value)) {
1389
+ for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
1293
1390
  const controller = known[identifier];
1391
+ if (controller && method.length > 0) {
1392
+ const owner = findOwner(node, identifier);
1393
+ if (owner) recordAction(owner, identifier, node, method, eventType);
1394
+ }
1294
1395
  if (!controller) {
1295
1396
  const best = nearestName(identifier, knownIdentifiers);
1296
1397
  report(
@@ -1374,19 +1475,87 @@ function checkSource(source, manifest) {
1374
1475
  if (!present.has(rule.whenPresent)) continue;
1375
1476
  const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
1376
1477
  const owners = nodesOf(rule.whenPresent);
1377
- const satisfied = (target) => rule.requireInside ? nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner))) : present.has(target);
1478
+ const hosts = rule.requireSameHost;
1479
+ const sharesHost = (target, selectors) => {
1480
+ const ownerHosts = owners.map((owner) => nearestHost(owner, selectors));
1481
+ if (ownerHosts.some((host) => !host)) return true;
1482
+ const candidates = nodesOf(target);
1483
+ if (candidates.length === 0) return false;
1484
+ return candidates.some((el) => {
1485
+ const host = nearestHost(el, selectors);
1486
+ return host ? ownerHosts.includes(host) : true;
1487
+ });
1488
+ };
1489
+ const satisfied = (target) => {
1490
+ if (hosts) return sharesHost(target, hosts);
1491
+ if (rule.requireInside)
1492
+ return nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner)));
1493
+ return present.has(target);
1494
+ };
1378
1495
  const missing = rule.require.filter((target) => !satisfied(target));
1379
1496
  if (missing.length === 0) continue;
1380
1497
  report(
1381
1498
  node,
1382
1499
  "missing-conditional-target",
1383
1500
  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)}.`,
1501
+ 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
1502
  node,
1386
1503
  complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
1387
1504
  );
1388
1505
  }
1389
1506
  }
1507
+ for (const { node, identifier } of scopes) {
1508
+ const controller = known[identifier];
1509
+ if (!controller) continue;
1510
+ const wired = wiredActions.get(node)?.get(identifier) ?? [];
1511
+ for (const rule of controller.requiredActions) {
1512
+ if (conditionHolds(node, identifier, rule.when) !== true) continue;
1513
+ const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
1514
+ for (const element of elements) {
1515
+ const actionAttr = element.attrs.find((attr) => attr.name === "data-action");
1516
+ if (element.opaque || actionAttr && isDynamicValue(actionAttr)) continue;
1517
+ const satisfied = wired.some(
1518
+ (action) => action.element === element && action.method === rule.action && rule.eventTypes.includes(action.eventType)
1519
+ );
1520
+ if (satisfied) continue;
1521
+ const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
1522
+ report(
1523
+ element,
1524
+ "missing-required-action",
1525
+ "error",
1526
+ `The ${where} of "${identifier}" requires action "${rule.action}" on ${list(rule.eventTypes)} in this configuration.`,
1527
+ element,
1528
+ rule.suggestion
1529
+ );
1530
+ }
1531
+ }
1532
+ }
1533
+ for (const { node, identifier } of scopes) {
1534
+ const controller = known[identifier];
1535
+ if (!controller) continue;
1536
+ const wired = wiredActions.get(node)?.get(identifier) ?? [];
1537
+ if (wired.length === 0) continue;
1538
+ for (const rule of controller.actionCompletion) {
1539
+ const opens = wired.some(
1540
+ (action) => action.method === rule.opens && rule.whenTriggeredBy.includes(action.eventType)
1541
+ );
1542
+ if (!opens) continue;
1543
+ if (wired.some((action) => rule.closedBy.includes(action.method))) continue;
1544
+ if (rule.escapeValue) {
1545
+ const attrName = `data-${identifier}-${dasherize(rule.escapeValue)}-value`;
1546
+ const authored = node.attrs.find((attr) => attr.name === attrName);
1547
+ if (authored && (isDynamicValue(authored) || Number(authored.value.trim()) !== 0)) continue;
1548
+ }
1549
+ report(
1550
+ node,
1551
+ "missing-action-completion",
1552
+ "warning",
1553
+ `"${identifier}" opens a state with "${rule.opens}" here, and nothing in this scope closes it.`,
1554
+ node,
1555
+ rule.suggestion
1556
+ );
1557
+ }
1558
+ }
1390
1559
  for (const { node, identifier } of scopes) {
1391
1560
  const controller = known[identifier];
1392
1561
  if (!controller) continue;
@@ -1689,6 +1858,7 @@ function checkSource(source, manifest) {
1689
1858
  return diagnostics;
1690
1859
  }
1691
1860
  function valueSatisfiesConstraint(raw, constraint) {
1861
+ if (constraint.type === "string") return constraint.allowedValues.includes(raw);
1692
1862
  const value = decodeNumericValue(raw);
1693
1863
  if (Number.isNaN(value)) return false;
1694
1864
  if (constraint.finite && !Number.isFinite(value)) return false;
@@ -1700,6 +1870,7 @@ function decodeNumericValue(raw) {
1700
1870
  return Number(raw.replace(/_/g, ""));
1701
1871
  }
1702
1872
  function describeValueConstraint(constraint) {
1873
+ if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
1703
1874
  const parts = [constraint.finite ? "a finite number" : "a number"];
1704
1875
  if (constraint.greaterThan !== void 0) {
1705
1876
  parts.push(`greater than ${constraint.greaterThan}`);
@@ -1954,6 +2125,8 @@ var CONTROLLER_ARRAY_FIELDS = [
1954
2125
  "events",
1955
2126
  "requiredTargets",
1956
2127
  "conditionalTargets",
2128
+ "requiredActions",
2129
+ "actionCompletion",
1957
2130
  "a11y",
1958
2131
  "keyboard",
1959
2132
  "hosts",
@@ -2016,10 +2189,16 @@ function collectFiles(target, out) {
2016
2189
  }
2017
2190
  if (isCheckableFile(target)) out.push(target);
2018
2191
  }
2019
- function checkFile(file, manifest) {
2020
- const source = readFileSync(file, "utf8");
2192
+ function checkFile(file, source, manifest) {
2021
2193
  return { file, diagnostics: checkSource(source, manifest) };
2022
2194
  }
2195
+ function withAnnouncerSeating(reports, sources) {
2196
+ const seating = announcerSeatingDiagnostic(sources);
2197
+ if (!seating) return [...reports];
2198
+ return reports.map(
2199
+ (report) => report.file === seating.file ? { file: report.file, diagnostics: [...report.diagnostics, seating.diagnostic] } : report
2200
+ );
2201
+ }
2023
2202
  function formatDiagnostic(d) {
2024
2203
  const where = `${d.line}:${d.column}`.padEnd(7);
2025
2204
  const head = ` ${where} ${d.severity.padEnd(7)} ${d.message} [${d.code}]`;
@@ -2152,13 +2331,14 @@ ${USAGE}`);
2152
2331
  const manifest = load();
2153
2332
  const cwd = process2.cwd();
2154
2333
  const reports = [];
2334
+ const sources = [];
2155
2335
  for (const file of files.sort()) {
2156
- reports.push({
2157
- file: relative(cwd, file) || file,
2158
- diagnostics: checkFile(file, manifest).diagnostics
2159
- });
2336
+ const path = relative(cwd, file) || file;
2337
+ const source = readFileSync(file, "utf8");
2338
+ sources.push({ file: path, source });
2339
+ reports.push(checkFile(path, source, manifest));
2160
2340
  }
2161
- const summary = buildCheckReport(reports, files.length);
2341
+ const summary = buildCheckReport(withAnnouncerSeating(reports, sources), files.length);
2162
2342
  if (json) {
2163
2343
  write(JSON.stringify(summary, null, 2));
2164
2344
  return summary.errorCount > 0 ? 1 : 0;
@@ -2388,6 +2568,17 @@ var EXAMPLE_RESULT_SCHEMA = {
2388
2568
  },
2389
2569
  required: ["id", "file", "source", "guidance"]
2390
2570
  };
2571
+ var VALUE_CONDITION_SCHEMA = {
2572
+ type: "object",
2573
+ description: "Own-value condition arming a rule; absent means unconditional.",
2574
+ properties: {
2575
+ value: { type: "string" },
2576
+ type: { type: "string", enum: ["boolean", "string"] },
2577
+ equals: STRING_ARRAY,
2578
+ default: { type: "string" }
2579
+ },
2580
+ required: ["value", "type", "equals", "default"]
2581
+ };
2391
2582
  var CONTENT_CONDITION_SCHEMA = {
2392
2583
  type: "object",
2393
2584
  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 +2627,14 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2436
2627
  type: "object",
2437
2628
  properties: {
2438
2629
  value: { type: "string", description: "Camel-case declared Value name." },
2439
- type: { type: "string", enum: ["number"] },
2630
+ type: { type: "string", enum: ["number", "string"] },
2440
2631
  finite: { type: "boolean" },
2441
2632
  greaterThan: { type: "number" },
2633
+ integer: { type: "boolean" },
2634
+ allowedValues: {
2635
+ ...STRING_ARRAY,
2636
+ description: "Accepted literals for a String Value constraint."
2637
+ },
2442
2638
  suggestion: { type: "string" }
2443
2639
  },
2444
2640
  required: ["value", "type", "suggestion"]
@@ -2479,6 +2675,45 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2479
2675
  required: ["whenPresent", "require", "suggestion"]
2480
2676
  }
2481
2677
  },
2678
+ requiredActions: {
2679
+ type: "array",
2680
+ description: "Action bindings required on a particular element in one configured Value state.",
2681
+ items: {
2682
+ type: "object",
2683
+ properties: {
2684
+ target: { type: "string", description: 'Target name; "" means the scope element.' },
2685
+ action: { type: "string", description: "Public action method that must be wired." },
2686
+ eventTypes: {
2687
+ ...STRING_ARRAY,
2688
+ description: "Event types that make the binding usable for this contract."
2689
+ },
2690
+ when: VALUE_CONDITION_SCHEMA,
2691
+ suggestion: { type: "string" }
2692
+ },
2693
+ required: ["target", "action", "eventTypes", "suggestion"]
2694
+ }
2695
+ },
2696
+ actionCompletion: {
2697
+ type: "array",
2698
+ 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.",
2699
+ items: {
2700
+ type: "object",
2701
+ properties: {
2702
+ opens: { type: "string", description: "Action that opens the state." },
2703
+ whenTriggeredBy: {
2704
+ ...STRING_ARRAY,
2705
+ description: "Event types for that action the controller cannot complete itself."
2706
+ },
2707
+ closedBy: { ...STRING_ARRAY, description: "Any one of these closes it again." },
2708
+ escapeValue: {
2709
+ type: "string",
2710
+ description: "Value whose non-zero number supplies an automatic exit instead."
2711
+ },
2712
+ suggestion: { type: "string" }
2713
+ },
2714
+ required: ["opens", "whenTriggeredBy", "closedBy", "suggestion"]
2715
+ }
2716
+ },
2482
2717
  a11y: {
2483
2718
  type: "array",
2484
2719
  description: "Author-supplied ARIA the controller relies on but never sets itself.",
@@ -2500,16 +2735,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2500
2735
  required: ["attrs"]
2501
2736
  }
2502
2737
  },
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
- },
2738
+ when: VALUE_CONDITION_SCHEMA,
2513
2739
  whenContains: CONTENT_CONDITION_SCHEMA,
2514
2740
  whenElement: ELEMENT_CONDITION_SCHEMA,
2515
2741
  whenDocument: DOCUMENT_CONDITION_SCHEMA,
@@ -2593,16 +2819,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2593
2819
  description: "Check the scope element when the target is absent."
2594
2820
  },
2595
2821
  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
- },
2822
+ when: VALUE_CONDITION_SCHEMA,
2606
2823
  require: {
2607
2824
  type: "object",
2608
2825
  description: "Companion-value requirement (first allowed value is canonical).",
@@ -2666,16 +2883,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2666
2883
  values: { ...STRING_ARRAY, description: "Restrict the attribute to these values." },
2667
2884
  min: { type: "integer", description: "Smallest permitted count (inclusive)." },
2668
2885
  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
- },
2886
+ when: VALUE_CONDITION_SCHEMA,
2679
2887
  suggestion: { type: "string" }
2680
2888
  },
2681
2889
  required: ["within", "target", "suggestion"]
@@ -2707,6 +2915,8 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2707
2915
  "events",
2708
2916
  "requiredTargets",
2709
2917
  "conditionalTargets",
2918
+ "requiredActions",
2919
+ "actionCompletion",
2710
2920
  "a11y",
2711
2921
  "keyboard",
2712
2922
  "hosts",