stimeo-ui 0.12.0 → 0.14.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 (183) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/cable/index.d.ts +68 -14
  3. package/dist/cable/index.js +438 -152
  4. package/dist/cable/index.js.map +1 -1
  5. package/dist/controllers/accordion_controller.d.ts +1 -1
  6. package/dist/controllers/accordion_controller.js.map +1 -1
  7. package/dist/controllers/alert_dialog_controller.d.ts +1 -1
  8. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  9. package/dist/controllers/announcer_controller.d.ts +1 -1
  10. package/dist/controllers/announcer_controller.js.map +1 -1
  11. package/dist/controllers/auto_submit_controller.js.map +1 -1
  12. package/dist/controllers/breadcrumb_controller.d.ts +1 -1
  13. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  14. package/dist/controllers/calendar_controller.d.ts +4 -3
  15. package/dist/controllers/calendar_controller.js +34 -2
  16. package/dist/controllers/calendar_controller.js.map +1 -1
  17. package/dist/controllers/carousel_controller.js +9 -7
  18. package/dist/controllers/carousel_controller.js.map +1 -1
  19. package/dist/controllers/character_counter_controller.js.map +1 -1
  20. package/dist/controllers/clipboard_controller.d.ts +2 -2
  21. package/dist/controllers/clipboard_controller.js.map +1 -1
  22. package/dist/controllers/collapsible_controller.d.ts +1 -1
  23. package/dist/controllers/collapsible_controller.js.map +1 -1
  24. package/dist/controllers/color_picker_controller.d.ts +1 -1
  25. package/dist/controllers/color_picker_controller.js.map +1 -1
  26. package/dist/controllers/command_palette_controller.d.ts +3 -3
  27. package/dist/controllers/command_palette_controller.js +2 -2
  28. package/dist/controllers/command_palette_controller.js.map +1 -1
  29. package/dist/controllers/conditional_fields_controller.js +7 -1
  30. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  31. package/dist/controllers/confirm_controller.d.ts +2 -2
  32. package/dist/controllers/confirm_controller.js.map +1 -1
  33. package/dist/controllers/context_menu_controller.d.ts +1 -1
  34. package/dist/controllers/context_menu_controller.js.map +1 -1
  35. package/dist/controllers/countdown_controller.d.ts +1 -1
  36. package/dist/controllers/countdown_controller.js.map +1 -1
  37. package/dist/controllers/currency_input_controller.js +68 -23
  38. package/dist/controllers/currency_input_controller.js.map +1 -1
  39. package/dist/controllers/data_grid_controller.d.ts +1 -1
  40. package/dist/controllers/data_grid_controller.js.map +1 -1
  41. package/dist/controllers/date_range_picker_controller.d.ts +3 -3
  42. package/dist/controllers/date_range_picker_controller.js +34 -10
  43. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  44. package/dist/controllers/dialog_controller.d.ts +3 -3
  45. package/dist/controllers/dialog_controller.js.map +1 -1
  46. package/dist/controllers/direct_upload_controller.js +21 -10
  47. package/dist/controllers/direct_upload_controller.js.map +1 -1
  48. package/dist/controllers/dirty_form_controller.js.map +1 -1
  49. package/dist/controllers/dismissible_controller.d.ts +1 -1
  50. package/dist/controllers/dismissible_controller.js.map +1 -1
  51. package/dist/controllers/drawer_controller.d.ts +2 -2
  52. package/dist/controllers/drawer_controller.js +2 -2
  53. package/dist/controllers/drawer_controller.js.map +1 -1
  54. package/dist/controllers/dropdown_controller.d.ts +2 -2
  55. package/dist/controllers/dropdown_controller.js.map +1 -1
  56. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  57. package/dist/controllers/flash_controller.js.map +1 -1
  58. package/dist/controllers/focus_controller.d.ts +2 -2
  59. package/dist/controllers/focus_controller.js.map +1 -1
  60. package/dist/controllers/form_field_controller.js +7 -1
  61. package/dist/controllers/form_field_controller.js.map +1 -1
  62. package/dist/controllers/form_validation_controller.js.map +1 -1
  63. package/dist/controllers/frame_loading_controller.d.ts +3 -3
  64. package/dist/controllers/frame_loading_controller.js.map +1 -1
  65. package/dist/controllers/highlight_controller.js.map +1 -1
  66. package/dist/controllers/hover_card_controller.d.ts +1 -1
  67. package/dist/controllers/hover_card_controller.js.map +1 -1
  68. package/dist/controllers/idle_controller.d.ts +1 -1
  69. package/dist/controllers/idle_controller.js.map +1 -1
  70. package/dist/controllers/input_mask_controller.d.ts +3 -2
  71. package/dist/controllers/input_mask_controller.js +33 -12
  72. package/dist/controllers/input_mask_controller.js.map +1 -1
  73. package/dist/controllers/intersection_controller.js +10 -5
  74. package/dist/controllers/intersection_controller.js.map +1 -1
  75. package/dist/controllers/lazy_frame_controller.js +10 -5
  76. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  77. package/dist/controllers/listbox_controller.js.map +1 -1
  78. package/dist/controllers/local_time_controller.d.ts +4 -3
  79. package/dist/controllers/local_time_controller.js +34 -7
  80. package/dist/controllers/local_time_controller.js.map +1 -1
  81. package/dist/controllers/masonry_controller.d.ts +2 -2
  82. package/dist/controllers/masonry_controller.js +1 -1
  83. package/dist/controllers/masonry_controller.js.map +1 -1
  84. package/dist/controllers/menu_controller.d.ts +1 -1
  85. package/dist/controllers/menu_controller.js +10 -2
  86. package/dist/controllers/menu_controller.js.map +1 -1
  87. package/dist/controllers/menubar_controller.d.ts +2 -2
  88. package/dist/controllers/menubar_controller.js +10 -2
  89. package/dist/controllers/menubar_controller.js.map +1 -1
  90. package/dist/controllers/navigation_menu_controller.d.ts +1 -1
  91. package/dist/controllers/navigation_menu_controller.js +8 -2
  92. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  93. package/dist/controllers/nested_form_controller.js.map +1 -1
  94. package/dist/controllers/network_status_controller.js.map +1 -1
  95. package/dist/controllers/number_input_controller.js.map +1 -1
  96. package/dist/controllers/optimistic_controller.d.ts +23 -10
  97. package/dist/controllers/optimistic_controller.js +141 -20
  98. package/dist/controllers/optimistic_controller.js.map +1 -1
  99. package/dist/controllers/otp_controller.js +23 -10
  100. package/dist/controllers/otp_controller.js.map +1 -1
  101. package/dist/controllers/overflow_indicator_controller.d.ts +1 -1
  102. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  103. package/dist/controllers/overflow_menu_controller.js +10 -1
  104. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  105. package/dist/controllers/pagination_controller.js.map +1 -1
  106. package/dist/controllers/password_reveal_controller.d.ts +1 -1
  107. package/dist/controllers/password_reveal_controller.js.map +1 -1
  108. package/dist/controllers/password_strength_controller.d.ts +2 -1
  109. package/dist/controllers/password_strength_controller.js.map +1 -1
  110. package/dist/controllers/persist_controller.js.map +1 -1
  111. package/dist/controllers/pointer_drag_controller.js +10 -3
  112. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  113. package/dist/controllers/popover_controller.d.ts +1 -1
  114. package/dist/controllers/popover_controller.js.map +1 -1
  115. package/dist/controllers/portal_controller.d.ts +2 -2
  116. package/dist/controllers/portal_controller.js +21 -10
  117. package/dist/controllers/portal_controller.js.map +1 -1
  118. package/dist/controllers/radio_group_controller.d.ts +1 -1
  119. package/dist/controllers/radio_group_controller.js +11 -3
  120. package/dist/controllers/radio_group_controller.js.map +1 -1
  121. package/dist/controllers/read_more_controller.d.ts +1 -1
  122. package/dist/controllers/read_more_controller.js.map +1 -1
  123. package/dist/controllers/relative_time_controller.d.ts +3 -3
  124. package/dist/controllers/relative_time_controller.js +31 -5
  125. package/dist/controllers/relative_time_controller.js.map +1 -1
  126. package/dist/controllers/reset_before_cache_controller.js +23 -10
  127. package/dist/controllers/reset_before_cache_controller.js.map +1 -1
  128. package/dist/controllers/resizable_controller.js +7 -2
  129. package/dist/controllers/resizable_controller.js.map +1 -1
  130. package/dist/controllers/roving_controller.d.ts +1 -1
  131. package/dist/controllers/roving_controller.js +7 -3
  132. package/dist/controllers/roving_controller.js.map +1 -1
  133. package/dist/controllers/scroll_area_controller.js.map +1 -1
  134. package/dist/controllers/scroll_visibility_controller.d.ts +1 -1
  135. package/dist/controllers/scroll_visibility_controller.js +22 -13
  136. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  137. package/dist/controllers/scrollspy_controller.d.ts +1 -1
  138. package/dist/controllers/scrollspy_controller.js +11 -6
  139. package/dist/controllers/scrollspy_controller.js.map +1 -1
  140. package/dist/controllers/sidebar_controller.d.ts +1 -1
  141. package/dist/controllers/sidebar_controller.js.map +1 -1
  142. package/dist/controllers/skeleton_controller.d.ts +2 -2
  143. package/dist/controllers/skeleton_controller.js.map +1 -1
  144. package/dist/controllers/smart_sticky_header_controller.js +23 -12
  145. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  146. package/dist/controllers/spinner_controller.d.ts +3 -3
  147. package/dist/controllers/spinner_controller.js.map +1 -1
  148. package/dist/controllers/sticky_observer_controller.js +10 -5
  149. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  150. package/dist/controllers/submit_once_controller.d.ts +3 -3
  151. package/dist/controllers/submit_once_controller.js.map +1 -1
  152. package/dist/controllers/switch_controller.d.ts +1 -1
  153. package/dist/controllers/switch_controller.js.map +1 -1
  154. package/dist/controllers/tabs_controller.d.ts +1 -1
  155. package/dist/controllers/tabs_controller.js.map +1 -1
  156. package/dist/controllers/textarea_autosize_controller.d.ts +1 -1
  157. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  158. package/dist/controllers/theme_controller.d.ts +5 -5
  159. package/dist/controllers/theme_controller.js +23 -13
  160. package/dist/controllers/theme_controller.js.map +1 -1
  161. package/dist/controllers/toast_controller.js +1 -1
  162. package/dist/controllers/toast_controller.js.map +1 -1
  163. package/dist/controllers/toggle_group_controller.js +11 -3
  164. package/dist/controllers/toggle_group_controller.js.map +1 -1
  165. package/dist/controllers/toolbar_controller.d.ts +1 -1
  166. package/dist/controllers/toolbar_controller.js +7 -3
  167. package/dist/controllers/toolbar_controller.js.map +1 -1
  168. package/dist/controllers/tooltip_controller.d.ts +1 -1
  169. package/dist/controllers/tooltip_controller.js.map +1 -1
  170. package/dist/controllers/transition_controller.d.ts +1 -1
  171. package/dist/controllers/transition_controller.js.map +1 -1
  172. package/dist/controllers/tree_view_controller.d.ts +4 -3
  173. package/dist/controllers/tree_view_controller.js.map +1 -1
  174. package/dist/index.js +259 -202
  175. package/dist/index.js.map +1 -1
  176. package/dist/inspector/cli.d.ts +39 -2
  177. package/dist/inspector/cli.js +81 -8
  178. package/dist/inspector/cli.js.map +1 -1
  179. package/dist/inspector/cli_bin.js +104 -8
  180. package/dist/inspector/cli_bin.js.map +1 -1
  181. package/dist/inspector/examples.json +5 -5
  182. package/dist/inspector/manifest.json +132 -3
  183. package/package.json +9 -11
@@ -2,7 +2,7 @@
2
2
  * Shared types for the Stimeo Inspector CLI (`stimeo check`).
3
3
  *
4
4
  * The Inspector statically checks HTML/ERB against a *manifest* describing the
5
- * official `stimeo--` controllers. The same engine powers both the library's
5
+ * `stimeo--` controllers this package ships. The same engine powers both the library's
6
6
  * own contract checks (e.g. `stimeo check app/views`) and the user-facing CLI;
7
7
  * it is therefore intentionally input-path agnostic.
8
8
  */
@@ -29,6 +29,13 @@ interface ControllerManifest {
29
29
  * strictly positive step, so those contracts stay explicit and reviewable.
30
30
  */
31
31
  readonly valueConstraints: readonly ValueConstraint[];
32
+ /**
33
+ * Grammar contracts on String Values whose accepted set cannot be enumerated
34
+ * (stage 1). Optional on the wire: a manifest written before the field existed
35
+ * omits it, and an engine written before it skips it, so either side pairs
36
+ * with the other. See {@link StringSyntaxConstraint}.
37
+ */
38
+ readonly valueSyntaxConstraints?: readonly StringSyntaxConstraint[];
32
39
  /** Statically decidable relationships between literal Stimulus Values. */
33
40
  readonly valueRelations: readonly ValueRelation[];
34
41
  /**
@@ -740,6 +747,36 @@ interface StringValueConstraint {
740
747
  /** Human-readable fix suggestion shown by the CLI (stage 4). */
741
748
  readonly suggestion: string;
742
749
  }
750
+ /**
751
+ * A String Value whose text is only readable by handing it to a parser, so the
752
+ * contract is a grammar rather than a set of literals.
753
+ *
754
+ * The controller parses the same text at runtime and falls back to the Value's
755
+ * default when it does not read, so a violation is a silently degraded element
756
+ * rather than a crash — which is exactly what a static check is for. Selectors
757
+ * are deliberately absent: the Inspector parses HTML itself and has no DOM to
758
+ * ask, so it cannot tell a malformed selector from one that simply matches
759
+ * nothing.
760
+ *
761
+ * These live in their own manifest field rather than alongside
762
+ * {@link StringValueConstraint}: an engine that predates them reads every String
763
+ * entry of `valueConstraints` through `allowedValues`, so a grammar contract
764
+ * placed there would be accepted by the shape check and then fail on the first
765
+ * document that declares the Value. A field such an engine never looks at is
766
+ * skipped instead.
767
+ */
768
+ interface StringSyntaxConstraint {
769
+ /** Value name (camelCase, as declared in `static values`). */
770
+ readonly value: string;
771
+ /** Decoder family. */
772
+ readonly type: "string";
773
+ /** Grammar the whole literal has to satisfy. */
774
+ readonly syntax: "regexp" | "json-object";
775
+ /** For `json-object`, the grammar every property value has to satisfy. */
776
+ readonly entries?: "regexp";
777
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
778
+ readonly suggestion: string;
779
+ }
743
780
  /** A statically checkable semantic contract on one declared Stimulus Value. */
744
781
  type ValueConstraint = NumericValueConstraint | StringValueConstraint;
745
782
  /**
@@ -769,7 +806,7 @@ type DiagnosticSeverity = "error" | "warning";
769
806
  * for testing, machine-readable output, and `data-stimeo-ignore` suppression
770
807
  * lists (which is why the set also exists as a runtime array).
771
808
  */
772
- declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "invalid-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-required-action", "missing-action-completion", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "missing-announcer", "unknown-ignore-code"];
809
+ declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "invalid-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-required-action", "missing-action-completion", "missing-action-event", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "missing-announcer", "unknown-ignore-code"];
773
810
  /** See {@link DIAGNOSTIC_CODES}. */
774
811
  type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
775
812
  /**
@@ -69,6 +69,30 @@ function announcerSeatingDiagnostic(sources) {
69
69
  };
70
70
  }
71
71
 
72
+ // src/utils/declared_value.ts
73
+ function parseDeclared(raw, parse, fallback) {
74
+ try {
75
+ return parse(raw);
76
+ } catch {
77
+ return fallback;
78
+ }
79
+ }
80
+ function compileRegExp(source, anchor = "none") {
81
+ return parseDeclared(
82
+ source,
83
+ (text) => {
84
+ const bare = new RegExp(text);
85
+ return anchor === "exact" ? new RegExp(`^(?:${text})$`) : bare;
86
+ },
87
+ null
88
+ );
89
+ }
90
+ function parseJsonObject(raw) {
91
+ const parsed = parseDeclared(raw, (text) => JSON.parse(text), null);
92
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
93
+ return parsed;
94
+ }
95
+
72
96
  // src/inspector/html_parser.ts
73
97
  var ERB_ELEMENT_TAG = "#erb";
74
98
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
@@ -987,7 +1011,21 @@ function parseValueAttr(attrName, knownIdentifiers) {
987
1011
  const valueGuess = dash === -1 ? "" : rest.slice(dash + 1);
988
1012
  return { identifier: null, valueToken: valueGuess || identifierGuess };
989
1013
  }
990
- function actionDescriptors(dataActionValue) {
1014
+ var DEFAULT_EVENT_NAMES = {
1015
+ a: "click",
1016
+ button: "click",
1017
+ details: "toggle",
1018
+ form: "submit",
1019
+ input: "input",
1020
+ select: "change",
1021
+ textarea: "input"
1022
+ };
1023
+ function defaultEventFor(host) {
1024
+ if (!host) return "";
1025
+ if (host.tag === "input" && host.inputType === "submit") return "click";
1026
+ return DEFAULT_EVENT_NAMES[host.tag] ?? "";
1027
+ }
1028
+ function actionDescriptors(dataActionValue, host) {
991
1029
  const descriptors = [];
992
1030
  for (const descriptor of dataActionValue.split(/\s+/)) {
993
1031
  const hash = descriptor.indexOf("#");
@@ -1000,6 +1038,8 @@ function actionDescriptors(dataActionValue) {
1000
1038
  lhs = lhs.slice(arrow + 2);
1001
1039
  const at = eventType.indexOf("@");
1002
1040
  if (at !== -1) eventType = eventType.slice(0, at);
1041
+ } else {
1042
+ eventType = defaultEventFor(host);
1003
1043
  }
1004
1044
  if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
1005
1045
  const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
@@ -1024,6 +1064,7 @@ var DIAGNOSTIC_CODES = [
1024
1064
  "missing-conditional-target",
1025
1065
  "missing-required-action",
1026
1066
  "missing-action-completion",
1067
+ "missing-action-event",
1027
1068
  "missing-aria",
1028
1069
  "invalid-aria-value",
1029
1070
  "keyboard-inaccessible",
@@ -1368,9 +1409,7 @@ function checkSource(source, manifest) {
1368
1409
  didYouMean(parsed.valueToken, (controller?.values ?? []).map(dasherize))
1369
1410
  );
1370
1411
  } else if (!isDynamicValue(attr)) {
1371
- const constraint = controller?.valueConstraints.find(
1372
- (candidate) => dasherize(candidate.value) === parsed.valueToken
1373
- );
1412
+ const constraint = declaredConstraint(controller, parsed.valueToken);
1374
1413
  if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
1375
1414
  const literal = constraint.type === "string" ? attr.value : attr.value.trim();
1376
1415
  report(
@@ -1388,12 +1427,26 @@ function checkSource(source, manifest) {
1388
1427
  }
1389
1428
  }
1390
1429
  if (attr.name === "data-action") {
1391
- for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
1430
+ const host = {
1431
+ tag: node.tag,
1432
+ inputType: node.attrs.find((a) => a.name === "type")?.value
1433
+ };
1434
+ for (const { identifier, method, eventType } of actionDescriptors(attr.value, host)) {
1392
1435
  const controller = known[identifier];
1393
1436
  if (controller && method.length > 0) {
1394
1437
  const owner = findOwner(node, identifier);
1395
1438
  if (owner) recordAction(owner, identifier, node, method, eventType);
1396
1439
  }
1440
+ if (controller && method.length > 0 && eventType === "" && node.origin === "markup" && !isDynamicValue(attr)) {
1441
+ report(
1442
+ node,
1443
+ "missing-action-event",
1444
+ "error",
1445
+ `"${identifier}#${method}" omits the event and <${node.tag}> has no default one, so Stimulus never binds it.`,
1446
+ attr,
1447
+ `Name the event: data-action="<event>->${identifier}#${method}".`
1448
+ );
1449
+ }
1397
1450
  if (!controller) {
1398
1451
  const best = nearestName(identifier, knownIdentifiers);
1399
1452
  report(
@@ -1859,8 +1912,13 @@ function checkSource(source, manifest) {
1859
1912
  diagnostics.sort((a, b) => a.line - b.line || a.column - b.column);
1860
1913
  return diagnostics;
1861
1914
  }
1915
+ function declaredConstraint(controller, valueToken) {
1916
+ const matches = (candidate) => dasherize(candidate.value) === valueToken;
1917
+ const syntax = controller?.valueSyntaxConstraints;
1918
+ return controller?.valueConstraints.find(matches) ?? (Array.isArray(syntax) ? syntax.find(matches) : void 0);
1919
+ }
1862
1920
  function valueSatisfiesConstraint(raw, constraint) {
1863
- if (constraint.type === "string") return constraint.allowedValues.includes(raw);
1921
+ if (constraint.type === "string") return stringValueSatisfies(raw, constraint);
1864
1922
  const value = decodeNumericValue(raw);
1865
1923
  if (Number.isNaN(value)) return false;
1866
1924
  if (constraint.finite && !Number.isFinite(value)) return false;
@@ -1871,8 +1929,18 @@ function valueSatisfiesConstraint(raw, constraint) {
1871
1929
  function decodeNumericValue(raw) {
1872
1930
  return Number(raw.replace(/_/g, ""));
1873
1931
  }
1932
+ function stringValueSatisfies(raw, constraint) {
1933
+ if (!("syntax" in constraint)) return constraint.allowedValues.includes(raw);
1934
+ if (constraint.syntax === "regexp") return compileRegExp(raw, "exact") !== null;
1935
+ if (raw === "") return true;
1936
+ const parsed = parseJsonObject(raw);
1937
+ if (parsed === null) return false;
1938
+ return Object.values(parsed).every(
1939
+ (source) => typeof source === "string" && (constraint.entries !== "regexp" || compileRegExp(source, "exact") !== null)
1940
+ );
1941
+ }
1874
1942
  function describeValueConstraint(constraint) {
1875
- if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
1943
+ if (constraint.type === "string") return describeStringConstraint(constraint);
1876
1944
  const parts = [constraint.finite ? "a finite number" : "a number"];
1877
1945
  if (constraint.greaterThan !== void 0) {
1878
1946
  parts.push(`greater than ${constraint.greaterThan}`);
@@ -1880,6 +1948,11 @@ function describeValueConstraint(constraint) {
1880
1948
  if (constraint.integer) parts.push("with no fractional part");
1881
1949
  return parts.join(" ");
1882
1950
  }
1951
+ function describeStringConstraint(constraint) {
1952
+ if (!("syntax" in constraint)) return `one of ${list(constraint.allowedValues)}`;
1953
+ if (constraint.syntax === "regexp") return "a valid regular expression";
1954
+ return constraint.entries === "regexp" ? "a JSON object whose values are valid regular expressions" : "a JSON object of strings";
1955
+ }
1883
1956
  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.";
1884
1957
  var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal hash, so the host controller may be declared at runtime.";
1885
1958
  function hasReadableMarkup(el) {
@@ -2265,7 +2338,7 @@ Usage:
2265
2338
  Check HTML/ERB files (or directories) for
2266
2339
  unknown or misused stimeo--* controllers,
2267
2340
  targets, values and action methods.
2268
- stimeo catalog [--json] Print the official controller catalog
2341
+ stimeo catalog [--json] Print the controller catalog
2269
2342
  (identifiers, targets, values, actions,
2270
2343
  events).
2271
2344
  stimeo mcp Run a Model Context Protocol server over