stimeo-ui 0.5.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.
Files changed (230) hide show
  1. package/CHANGELOG.md +178 -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/aspect_ratio_controller.d.ts +1 -3
  6. package/dist/controllers/aspect_ratio_controller.js +19 -11
  7. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  8. package/dist/controllers/auto_submit_controller.d.ts +2 -0
  9. package/dist/controllers/auto_submit_controller.js.map +1 -1
  10. package/dist/controllers/avatar_controller.d.ts +36 -15
  11. package/dist/controllers/avatar_controller.js +237 -40
  12. package/dist/controllers/avatar_controller.js.map +1 -1
  13. package/dist/controllers/breadcrumb_controller.d.ts +2 -0
  14. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  15. package/dist/controllers/bulk_select_controller.d.ts +2 -0
  16. package/dist/controllers/bulk_select_controller.js.map +1 -1
  17. package/dist/controllers/calendar_controller.d.ts +2 -0
  18. package/dist/controllers/calendar_controller.js.map +1 -1
  19. package/dist/controllers/carousel_controller.d.ts +12 -3
  20. package/dist/controllers/carousel_controller.js +85 -9
  21. package/dist/controllers/carousel_controller.js.map +1 -1
  22. package/dist/controllers/character_counter_controller.d.ts +52 -20
  23. package/dist/controllers/character_counter_controller.js +338 -63
  24. package/dist/controllers/character_counter_controller.js.map +1 -1
  25. package/dist/controllers/checkbox_controller.d.ts +34 -5
  26. package/dist/controllers/checkbox_controller.js +136 -25
  27. package/dist/controllers/checkbox_controller.js.map +1 -1
  28. package/dist/controllers/color_picker_controller.d.ts +10 -1
  29. package/dist/controllers/color_picker_controller.js +35 -9
  30. package/dist/controllers/color_picker_controller.js.map +1 -1
  31. package/dist/controllers/combobox_controller.d.ts +2 -0
  32. package/dist/controllers/combobox_controller.js.map +1 -1
  33. package/dist/controllers/command_palette_controller.d.ts +2 -0
  34. package/dist/controllers/command_palette_controller.js +75 -4
  35. package/dist/controllers/command_palette_controller.js.map +1 -1
  36. package/dist/controllers/conditional_fields_controller.d.ts +42 -14
  37. package/dist/controllers/conditional_fields_controller.js +345 -51
  38. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  39. package/dist/controllers/confirm_controller.d.ts +2 -0
  40. package/dist/controllers/confirm_controller.js +75 -4
  41. package/dist/controllers/confirm_controller.js.map +1 -1
  42. package/dist/controllers/count_up_controller.d.ts +2 -0
  43. package/dist/controllers/count_up_controller.js.map +1 -1
  44. package/dist/controllers/countdown_controller.d.ts +4 -0
  45. package/dist/controllers/countdown_controller.js.map +1 -1
  46. package/dist/controllers/currency_input_controller.d.ts +2 -0
  47. package/dist/controllers/currency_input_controller.js.map +1 -1
  48. package/dist/controllers/data_grid_controller.d.ts +3 -0
  49. package/dist/controllers/data_grid_controller.js.map +1 -1
  50. package/dist/controllers/date_range_picker_controller.d.ts +23 -4
  51. package/dist/controllers/date_range_picker_controller.js +157 -30
  52. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  53. package/dist/controllers/dialog_controller.js +75 -4
  54. package/dist/controllers/dialog_controller.js.map +1 -1
  55. package/dist/controllers/direct_upload_controller.d.ts +56 -24
  56. package/dist/controllers/direct_upload_controller.js +201 -45
  57. package/dist/controllers/direct_upload_controller.js.map +1 -1
  58. package/dist/controllers/dirty_form_controller.d.ts +14 -6
  59. package/dist/controllers/dirty_form_controller.js +192 -29
  60. package/dist/controllers/dirty_form_controller.js.map +1 -1
  61. package/dist/controllers/dismissible_controller.d.ts +2 -0
  62. package/dist/controllers/dismissible_controller.js +83 -18
  63. package/dist/controllers/dismissible_controller.js.map +1 -1
  64. package/dist/controllers/drawer_controller.js +75 -4
  65. package/dist/controllers/drawer_controller.js.map +1 -1
  66. package/dist/controllers/empty_state_controller.d.ts +2 -0
  67. package/dist/controllers/empty_state_controller.js.map +1 -1
  68. package/dist/controllers/file_dropzone_controller.d.ts +9 -1
  69. package/dist/controllers/file_dropzone_controller.js +26 -3
  70. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  71. package/dist/controllers/filter_controller.d.ts +2 -0
  72. package/dist/controllers/filter_controller.js.map +1 -1
  73. package/dist/controllers/flash_controller.d.ts +4 -0
  74. package/dist/controllers/flash_controller.js.map +1 -1
  75. package/dist/controllers/focus_controller.d.ts +4 -3
  76. package/dist/controllers/focus_controller.js +75 -4
  77. package/dist/controllers/focus_controller.js.map +1 -1
  78. package/dist/controllers/form_field_controller.d.ts +50 -10
  79. package/dist/controllers/form_field_controller.js +280 -62
  80. package/dist/controllers/form_field_controller.js.map +1 -1
  81. package/dist/controllers/form_validation_controller.d.ts +10 -8
  82. package/dist/controllers/form_validation_controller.js +208 -83
  83. package/dist/controllers/form_validation_controller.js.map +1 -1
  84. package/dist/controllers/frame_loading_controller.d.ts +2 -0
  85. package/dist/controllers/frame_loading_controller.js.map +1 -1
  86. package/dist/controllers/highlight_controller.d.ts +2 -0
  87. package/dist/controllers/highlight_controller.js.map +1 -1
  88. package/dist/controllers/hover_card_controller.d.ts +2 -2
  89. package/dist/controllers/hover_card_controller.js.map +1 -1
  90. package/dist/controllers/idle_controller.d.ts +5 -3
  91. package/dist/controllers/idle_controller.js +27 -5
  92. package/dist/controllers/idle_controller.js.map +1 -1
  93. package/dist/controllers/input_mask_controller.d.ts +2 -0
  94. package/dist/controllers/input_mask_controller.js.map +1 -1
  95. package/dist/controllers/lazy_frame_controller.d.ts +2 -0
  96. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  97. package/dist/controllers/listbox_controller.d.ts +2 -0
  98. package/dist/controllers/listbox_controller.js.map +1 -1
  99. package/dist/controllers/local_time_controller.d.ts +2 -0
  100. package/dist/controllers/local_time_controller.js.map +1 -1
  101. package/dist/controllers/masonry_controller.d.ts +2 -0
  102. package/dist/controllers/masonry_controller.js.map +1 -1
  103. package/dist/controllers/menubar_controller.js +5 -3
  104. package/dist/controllers/menubar_controller.js.map +1 -1
  105. package/dist/controllers/meter_controller.d.ts +2 -0
  106. package/dist/controllers/meter_controller.js.map +1 -1
  107. package/dist/controllers/multi_select_controller.d.ts +48 -12
  108. package/dist/controllers/multi_select_controller.js +460 -151
  109. package/dist/controllers/multi_select_controller.js.map +1 -1
  110. package/dist/controllers/nested_form_controller.d.ts +2 -0
  111. package/dist/controllers/nested_form_controller.js.map +1 -1
  112. package/dist/controllers/network_status_controller.d.ts +2 -0
  113. package/dist/controllers/network_status_controller.js.map +1 -1
  114. package/dist/controllers/number_input_controller.d.ts +26 -6
  115. package/dist/controllers/number_input_controller.js +317 -51
  116. package/dist/controllers/number_input_controller.js.map +1 -1
  117. package/dist/controllers/otp_controller.d.ts +3 -0
  118. package/dist/controllers/otp_controller.js.map +1 -1
  119. package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
  120. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  121. package/dist/controllers/overflow_menu_controller.d.ts +2 -0
  122. package/dist/controllers/overflow_menu_controller.js +6 -1
  123. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  124. package/dist/controllers/pagination_controller.d.ts +2 -0
  125. package/dist/controllers/pagination_controller.js +35 -1
  126. package/dist/controllers/pagination_controller.js.map +1 -1
  127. package/dist/controllers/password_reveal_controller.d.ts +2 -0
  128. package/dist/controllers/password_reveal_controller.js.map +1 -1
  129. package/dist/controllers/password_strength_controller.d.ts +5 -3
  130. package/dist/controllers/password_strength_controller.js +20 -2
  131. package/dist/controllers/password_strength_controller.js.map +1 -1
  132. package/dist/controllers/persist_controller.d.ts +33 -19
  133. package/dist/controllers/persist_controller.js +449 -120
  134. package/dist/controllers/persist_controller.js.map +1 -1
  135. package/dist/controllers/pointer_drag_controller.d.ts +3 -0
  136. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  137. package/dist/controllers/popover_controller.d.ts +2 -2
  138. package/dist/controllers/popover_controller.js +77 -4
  139. package/dist/controllers/popover_controller.js.map +1 -1
  140. package/dist/controllers/portal_controller.d.ts +3 -2
  141. package/dist/controllers/portal_controller.js.map +1 -1
  142. package/dist/controllers/preview_guard_controller.d.ts +2 -0
  143. package/dist/controllers/preview_guard_controller.js.map +1 -1
  144. package/dist/controllers/progress_controller.d.ts +3 -0
  145. package/dist/controllers/progress_controller.js.map +1 -1
  146. package/dist/controllers/radio_group_controller.d.ts +44 -15
  147. package/dist/controllers/radio_group_controller.js +540 -56
  148. package/dist/controllers/radio_group_controller.js.map +1 -1
  149. package/dist/controllers/rating_controller.d.ts +38 -31
  150. package/dist/controllers/rating_controller.js +276 -89
  151. package/dist/controllers/rating_controller.js.map +1 -1
  152. package/dist/controllers/reading_progress_controller.d.ts +2 -0
  153. package/dist/controllers/reading_progress_controller.js.map +1 -1
  154. package/dist/controllers/resizable_controller.d.ts +2 -0
  155. package/dist/controllers/resizable_controller.js +33 -0
  156. package/dist/controllers/resizable_controller.js.map +1 -1
  157. package/dist/controllers/roving_controller.d.ts +6 -0
  158. package/dist/controllers/roving_controller.js +60 -5
  159. package/dist/controllers/roving_controller.js.map +1 -1
  160. package/dist/controllers/scroll_area_controller.d.ts +25 -11
  161. package/dist/controllers/scroll_area_controller.js +557 -125
  162. package/dist/controllers/scroll_area_controller.js.map +1 -1
  163. package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
  164. package/dist/controllers/scroll_visibility_controller.js +33 -0
  165. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  166. package/dist/controllers/scrollspy_controller.d.ts +2 -0
  167. package/dist/controllers/scrollspy_controller.js.map +1 -1
  168. package/dist/controllers/separator_controller.d.ts +54 -11
  169. package/dist/controllers/separator_controller.js +354 -38
  170. package/dist/controllers/separator_controller.js.map +1 -1
  171. package/dist/controllers/sidebar_controller.js +83 -10
  172. package/dist/controllers/sidebar_controller.js.map +1 -1
  173. package/dist/controllers/skeleton_controller.d.ts +2 -0
  174. package/dist/controllers/skeleton_controller.js.map +1 -1
  175. package/dist/controllers/slider_controller.d.ts +2 -0
  176. package/dist/controllers/slider_controller.js.map +1 -1
  177. package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
  178. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  179. package/dist/controllers/spinner_controller.d.ts +2 -0
  180. package/dist/controllers/spinner_controller.js.map +1 -1
  181. package/dist/controllers/step_indicator_controller.d.ts +2 -0
  182. package/dist/controllers/step_indicator_controller.js.map +1 -1
  183. package/dist/controllers/stepper_controller.d.ts +2 -0
  184. package/dist/controllers/stepper_controller.js.map +1 -1
  185. package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
  186. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  187. package/dist/controllers/sticky_observer_controller.d.ts +2 -0
  188. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  189. package/dist/controllers/submit_once_controller.d.ts +94 -38
  190. package/dist/controllers/submit_once_controller.js +399 -121
  191. package/dist/controllers/submit_once_controller.js.map +1 -1
  192. package/dist/controllers/switch_controller.d.ts +2 -0
  193. package/dist/controllers/switch_controller.js.map +1 -1
  194. package/dist/controllers/tags_input_controller.d.ts +43 -11
  195. package/dist/controllers/tags_input_controller.js +356 -120
  196. package/dist/controllers/tags_input_controller.js.map +1 -1
  197. package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
  198. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  199. package/dist/controllers/theme_controller.d.ts +2 -0
  200. package/dist/controllers/theme_controller.js +8 -6
  201. package/dist/controllers/theme_controller.js.map +1 -1
  202. package/dist/controllers/time_picker_controller.d.ts +44 -13
  203. package/dist/controllers/time_picker_controller.js +296 -107
  204. package/dist/controllers/time_picker_controller.js.map +1 -1
  205. package/dist/controllers/toast_controller.d.ts +4 -0
  206. package/dist/controllers/toast_controller.js.map +1 -1
  207. package/dist/controllers/toggle_group_controller.d.ts +41 -13
  208. package/dist/controllers/toggle_group_controller.js +378 -55
  209. package/dist/controllers/toggle_group_controller.js.map +1 -1
  210. package/dist/controllers/toolbar_controller.js +5 -3
  211. package/dist/controllers/toolbar_controller.js.map +1 -1
  212. package/dist/controllers/tooltip_controller.d.ts +2 -2
  213. package/dist/controllers/tooltip_controller.js.map +1 -1
  214. package/dist/controllers/transition_controller.d.ts +4 -3
  215. package/dist/controllers/transition_controller.js.map +1 -1
  216. package/dist/controllers/tree_view_controller.d.ts +2 -0
  217. package/dist/controllers/tree_view_controller.js +7 -4
  218. package/dist/controllers/tree_view_controller.js.map +1 -1
  219. package/dist/index.js +4963 -1581
  220. package/dist/index.js.map +1 -1
  221. package/dist/inspector/cli.d.ts +123 -6
  222. package/dist/inspector/cli.js +212 -18
  223. package/dist/inspector/cli.js.map +1 -1
  224. package/dist/inspector/cli_bin.js +274 -50
  225. package/dist/inspector/cli_bin.js.map +1 -1
  226. package/dist/inspector/examples.json +15 -15
  227. package/dist/inspector/manifest.json +602 -80
  228. package/dist/positioning/index.d.ts +4 -2
  229. package/dist/positioning/index.js.map +1 -1
  230. 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, 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--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
- if (arrow !== -1) lhs = lhs.slice(arrow + 2);
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,10 +1032,30 @@ 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
 
971
1039
  // src/inspector/check.ts
1040
+ function isInside(node, ancestor) {
1041
+ for (let current = node.parent; current; current = current.parent) {
1042
+ if (current === ancestor) return true;
1043
+ }
1044
+ return false;
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
+ }
972
1059
  function checkSource(source, manifest) {
973
1060
  const diagnostics = [];
974
1061
  const tree = parseHtml(neutralizeErb(source), erbElements(source));
@@ -987,7 +1074,9 @@ function checkSource(source, manifest) {
987
1074
  (a) => a.name === `data-${identifier}-${dasherize(when.value)}-value`
988
1075
  );
989
1076
  if (hostAttr && isDynamicValue(hostAttr)) return null;
990
- return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
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);
991
1080
  };
992
1081
  const isContentEditable2 = (element) => {
993
1082
  let current = element;
@@ -1060,6 +1149,17 @@ function checkSource(source, manifest) {
1060
1149
  const scopes = [];
1061
1150
  const presence = /* @__PURE__ */ new Map();
1062
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
+ };
1063
1163
  const recordPresence = (owner, identifier, target) => {
1064
1164
  let byId = presence.get(owner);
1065
1165
  if (!byId) {
@@ -1268,11 +1368,12 @@ function checkSource(source, manifest) {
1268
1368
  (candidate) => dasherize(candidate.value) === parsed.valueToken
1269
1369
  );
1270
1370
  if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
1371
+ const literal = constraint.type === "string" ? attr.value : attr.value.trim();
1271
1372
  report(
1272
1373
  node,
1273
1374
  "invalid-value",
1274
1375
  "error",
1275
- `Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1376
+ `Invalid value "${literal}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1276
1377
  attr,
1277
1378
  constraint.suggestion
1278
1379
  );
@@ -1283,8 +1384,12 @@ function checkSource(source, manifest) {
1283
1384
  }
1284
1385
  }
1285
1386
  if (attr.name === "data-action") {
1286
- for (const { identifier, method } of actionDescriptors(attr.value)) {
1387
+ for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
1287
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
+ }
1288
1393
  if (!controller) {
1289
1394
  const best = nearestName(identifier, knownIdentifiers);
1290
1395
  report(
@@ -1366,18 +1471,89 @@ function checkSource(source, manifest) {
1366
1471
  const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
1367
1472
  for (const rule of controller.conditionalTargets) {
1368
1473
  if (!present.has(rule.whenPresent)) continue;
1369
- const missing = rule.require.filter((target) => !present.has(target));
1474
+ const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
1475
+ const owners = nodesOf(rule.whenPresent);
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
+ };
1493
+ const missing = rule.require.filter((target) => !satisfied(target));
1370
1494
  if (missing.length === 0) continue;
1371
1495
  report(
1372
1496
  node,
1373
1497
  "missing-conditional-target",
1374
1498
  complete ? "error" : "warning",
1375
- `"${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)}.`,
1376
1500
  node,
1377
1501
  complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
1378
1502
  );
1379
1503
  }
1380
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
+ }
1381
1557
  for (const { node, identifier } of scopes) {
1382
1558
  const controller = known[identifier];
1383
1559
  if (!controller) continue;
@@ -1390,7 +1566,7 @@ function checkSource(source, manifest) {
1390
1566
  if (!hasReadableMarkup(el)) continue;
1391
1567
  if (!contentHolds(node, identifier, el, req.whenContains)) continue;
1392
1568
  if (!elementHolds(el, req.whenElement)) continue;
1393
- checkA11y(report, el, identifier, req, severity);
1569
+ checkA11y(report, el, identifier, req, severity, isDynamicValue);
1394
1570
  }
1395
1571
  }
1396
1572
  }
@@ -1680,20 +1856,24 @@ function checkSource(source, manifest) {
1680
1856
  return diagnostics;
1681
1857
  }
1682
1858
  function valueSatisfiesConstraint(raw, constraint) {
1859
+ if (constraint.type === "string") return constraint.allowedValues.includes(raw);
1683
1860
  const value = decodeNumericValue(raw);
1684
1861
  if (Number.isNaN(value)) return false;
1685
1862
  if (constraint.finite && !Number.isFinite(value)) return false;
1686
1863
  if (constraint.greaterThan !== void 0 && !(value > constraint.greaterThan)) return false;
1864
+ if (constraint.integer && !Number.isInteger(value)) return false;
1687
1865
  return true;
1688
1866
  }
1689
1867
  function decodeNumericValue(raw) {
1690
1868
  return Number(raw.replace(/_/g, ""));
1691
1869
  }
1692
1870
  function describeValueConstraint(constraint) {
1871
+ if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
1693
1872
  const parts = [constraint.finite ? "a finite number" : "a number"];
1694
1873
  if (constraint.greaterThan !== void 0) {
1695
1874
  parts.push(`greater than ${constraint.greaterThan}`);
1696
1875
  }
1876
+ if (constraint.integer) parts.push("with no fractional part");
1697
1877
  return parts.join(" ");
1698
1878
  }
1699
1879
  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.";
@@ -1701,7 +1881,7 @@ var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal
1701
1881
  function hasReadableMarkup(el) {
1702
1882
  return el.origin === "markup";
1703
1883
  }
1704
- function checkA11y(report, el, identifier, req, severity) {
1884
+ function checkA11y(report, el, identifier, req, severity, isDynamicValue) {
1705
1885
  const groups = [
1706
1886
  { attrs: req.attrs, values: req.values },
1707
1887
  ...req.or ?? []
@@ -1721,9 +1901,14 @@ function checkA11y(report, el, identifier, req, severity) {
1721
1901
  }
1722
1902
  const invalid = [];
1723
1903
  let anyPresent = false;
1904
+ let blank;
1724
1905
  for (const [name, allowed] of allowedByAttr) {
1725
1906
  const attr = el.attrs.find((a) => a.name === name);
1726
1907
  if (!attr) continue;
1908
+ if (allowed.anyValue && attr.value.trim() === "" && !isDynamicValue(attr)) {
1909
+ blank ??= attr;
1910
+ continue;
1911
+ }
1727
1912
  anyPresent = true;
1728
1913
  if (!allowed.anyValue && !allowed.values.has(attr.value.trim())) {
1729
1914
  invalid.push({ attr, values: [...allowed.values] });
@@ -1735,8 +1920,8 @@ function checkA11y(report, el, identifier, req, severity) {
1735
1920
  el,
1736
1921
  "missing-aria",
1737
1922
  severity,
1738
- `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
1739
- el,
1923
+ blank ? `${blank.name} is empty on the ${where} of "${identifier}", which ${verb} a value there.` : `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
1924
+ blank ?? el,
1740
1925
  req.suggestion
1741
1926
  );
1742
1927
  return;
@@ -1938,6 +2123,8 @@ var CONTROLLER_ARRAY_FIELDS = [
1938
2123
  "events",
1939
2124
  "requiredTargets",
1940
2125
  "conditionalTargets",
2126
+ "requiredActions",
2127
+ "actionCompletion",
1941
2128
  "a11y",
1942
2129
  "keyboard",
1943
2130
  "hosts",
@@ -2000,10 +2187,16 @@ function collectFiles(target, out) {
2000
2187
  }
2001
2188
  if (isCheckableFile(target)) out.push(target);
2002
2189
  }
2003
- function checkFile(file, manifest) {
2004
- const source = readFileSync(file, "utf8");
2190
+ function checkFile(file, source, manifest) {
2005
2191
  return { file, diagnostics: checkSource(source, manifest) };
2006
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
+ }
2007
2200
  function formatDiagnostic(d) {
2008
2201
  const where = `${d.line}:${d.column}`.padEnd(7);
2009
2202
  const head = ` ${where} ${d.severity.padEnd(7)} ${d.message} [${d.code}]`;
@@ -2136,13 +2329,14 @@ ${USAGE}`);
2136
2329
  const manifest = load();
2137
2330
  const cwd = process2.cwd();
2138
2331
  const reports = [];
2332
+ const sources = [];
2139
2333
  for (const file of files.sort()) {
2140
- reports.push({
2141
- file: relative(cwd, file) || file,
2142
- diagnostics: checkFile(file, manifest).diagnostics
2143
- });
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));
2144
2338
  }
2145
- const summary = buildCheckReport(reports, files.length);
2339
+ const summary = buildCheckReport(withAnnouncerSeating(reports, sources), files.length);
2146
2340
  if (json) {
2147
2341
  write(JSON.stringify(summary, null, 2));
2148
2342
  return summary.errorCount > 0 ? 1 : 0;
@@ -2372,6 +2566,17 @@ var EXAMPLE_RESULT_SCHEMA = {
2372
2566
  },
2373
2567
  required: ["id", "file", "source", "guidance"]
2374
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
+ };
2375
2580
  var CONTENT_CONDITION_SCHEMA = {
2376
2581
  type: "object",
2377
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`.",
@@ -2420,9 +2625,14 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2420
2625
  type: "object",
2421
2626
  properties: {
2422
2627
  value: { type: "string", description: "Camel-case declared Value name." },
2423
- type: { type: "string", enum: ["number"] },
2628
+ type: { type: "string", enum: ["number", "string"] },
2424
2629
  finite: { type: "boolean" },
2425
2630
  greaterThan: { type: "number" },
2631
+ integer: { type: "boolean" },
2632
+ allowedValues: {
2633
+ ...STRING_ARRAY,
2634
+ description: "Accepted literals for a String Value constraint."
2635
+ },
2426
2636
  suggestion: { type: "string" }
2427
2637
  },
2428
2638
  required: ["value", "type", "suggestion"]
@@ -2463,6 +2673,45 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2463
2673
  required: ["whenPresent", "require", "suggestion"]
2464
2674
  }
2465
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
+ },
2466
2715
  a11y: {
2467
2716
  type: "array",
2468
2717
  description: "Author-supplied ARIA the controller relies on but never sets itself.",
@@ -2484,16 +2733,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2484
2733
  required: ["attrs"]
2485
2734
  }
2486
2735
  },
2487
- when: {
2488
- type: "object",
2489
- description: "Own-value condition arming the requirement; absent means unconditional. Outside the named configuration the ARIA is not optional but wrong.",
2490
- properties: {
2491
- value: { type: "string" },
2492
- equals: STRING_ARRAY,
2493
- default: { type: "string" }
2494
- },
2495
- required: ["value", "equals", "default"]
2496
- },
2736
+ when: VALUE_CONDITION_SCHEMA,
2497
2737
  whenContains: CONTENT_CONDITION_SCHEMA,
2498
2738
  whenElement: ELEMENT_CONDITION_SCHEMA,
2499
2739
  whenDocument: DOCUMENT_CONDITION_SCHEMA,
@@ -2577,16 +2817,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2577
2817
  description: "Check the scope element when the target is absent."
2578
2818
  },
2579
2819
  coController: { type: "string", description: "Companion controller identifier." },
2580
- when: {
2581
- type: "object",
2582
- description: "Host-value condition arming the rule; absent means unconditional.",
2583
- properties: {
2584
- value: { type: "string" },
2585
- equals: STRING_ARRAY,
2586
- default: { type: "string" }
2587
- },
2588
- required: ["value", "equals", "default"]
2589
- },
2820
+ when: VALUE_CONDITION_SCHEMA,
2590
2821
  require: {
2591
2822
  type: "object",
2592
2823
  description: "Companion-value requirement (first allowed value is canonical).",
@@ -2650,16 +2881,7 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2650
2881
  values: { ...STRING_ARRAY, description: "Restrict the attribute to these values." },
2651
2882
  min: { type: "integer", description: "Smallest permitted count (inclusive)." },
2652
2883
  max: { type: "integer", description: "Largest permitted count (inclusive)." },
2653
- when: {
2654
- type: "object",
2655
- description: "Own-value condition arming the bound; absent means unconditional.",
2656
- properties: {
2657
- value: { type: "string" },
2658
- equals: STRING_ARRAY,
2659
- default: { type: "string" }
2660
- },
2661
- required: ["value", "equals", "default"]
2662
- },
2884
+ when: VALUE_CONDITION_SCHEMA,
2663
2885
  suggestion: { type: "string" }
2664
2886
  },
2665
2887
  required: ["within", "target", "suggestion"]
@@ -2691,6 +2913,8 @@ var CONTROLLER_CONTRACT_SCHEMA = {
2691
2913
  "events",
2692
2914
  "requiredTargets",
2693
2915
  "conditionalTargets",
2916
+ "requiredActions",
2917
+ "actionCompletion",
2694
2918
  "a11y",
2695
2919
  "keyboard",
2696
2920
  "hosts",