stimeo-ui 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -0
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +3 -5
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +195 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +5 -1
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +90 -10
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +31 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.js +8 -3
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js +4 -1
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +9 -3
- package/dist/controllers/color_picker_controller.js +41 -11
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +1 -1
- package/dist/controllers/context_menu_controller.js +2 -2
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +5 -3
- package/dist/controllers/countdown_controller.js +5 -1
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +162 -31
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +1 -1
- package/dist/controllers/direct_upload_controller.js +3 -3
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +27 -7
- package/dist/controllers/empty_state_controller.js +107 -16
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +7 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +25 -5
- package/dist/controllers/flash_controller.js +161 -21
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -3
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +8 -2
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +19 -2
- package/dist/controllers/frame_loading_controller.js +94 -22
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +8 -4
- package/dist/controllers/highlight_controller.js +38 -1
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -4
- package/dist/controllers/idle_controller.js +39 -6
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.js +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +1 -1
- package/dist/controllers/masonry_controller.js +1 -1
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +3 -1
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +46 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +15 -13
- package/dist/controllers/network_status_controller.js +1 -3
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +29 -3
- package/dist/controllers/number_input_controller.js +455 -64
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +5 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +38 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +4 -4
- package/dist/controllers/password_strength_controller.js +21 -3
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +4 -4
- package/dist/controllers/persist_controller.js +24 -5
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +10 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -2
- package/dist/controllers/portal_controller.js +10 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +1 -1
- package/dist/controllers/progress_controller.js +7 -3
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +41 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +23 -0
- package/dist/controllers/range_slider_controller.js +385 -94
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +36 -31
- package/dist/controllers/rating_controller.js +274 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +2 -0
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +4 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -1
- package/dist/controllers/scroll_area_controller.js +155 -23
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/separator_controller.js +13 -17
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -2
- package/dist/controllers/skeleton_controller.js +71 -3
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +17 -1
- package/dist/controllers/slider_controller.js +325 -48
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +15 -10
- package/dist/controllers/spinner_controller.js +18 -3
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +1 -1
- package/dist/controllers/step_indicator_controller.js +3 -1
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.js +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -8
- package/dist/controllers/switch_controller.js +162 -18
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +41 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.js +1 -1
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +42 -10
- package/dist/controllers/time_picker_controller.js +296 -104
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +39 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +1 -2
- package/dist/controllers/tree_view_controller.js +24 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +3811 -1048
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +92 -4
- package/dist/inspector/cli.js +174 -6
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +234 -8
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +24 -24
- package/dist/inspector/manifest.json +573 -59
- package/dist/positioning/index.d.ts +2 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -612,6 +612,26 @@ function readEntry(code, start, base) {
|
|
|
612
612
|
};
|
|
613
613
|
}
|
|
614
614
|
}
|
|
615
|
+
const number = /^[+-]?\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?/.exec(
|
|
616
|
+
code.slice(valueStart)
|
|
617
|
+
)?.[0];
|
|
618
|
+
if (number !== void 0) {
|
|
619
|
+
const end = valueStart + number.length;
|
|
620
|
+
const after = skipTrivia(code, end);
|
|
621
|
+
const whole = after >= code.length || code[after] === "," || code[after] === "}";
|
|
622
|
+
if (whole) {
|
|
623
|
+
return {
|
|
624
|
+
attr: {
|
|
625
|
+
name,
|
|
626
|
+
value: number,
|
|
627
|
+
...anchor,
|
|
628
|
+
valueStart: base + valueStart,
|
|
629
|
+
valueEnd: base + end
|
|
630
|
+
},
|
|
631
|
+
end
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
615
635
|
return { attr: { name, value: null, ...anchor }, end: skipValue(code, valueStart) };
|
|
616
636
|
}
|
|
617
637
|
function readKey(code, start) {
|
|
@@ -928,6 +948,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
928
948
|
"unknown-controller",
|
|
929
949
|
"unknown-target",
|
|
930
950
|
"unknown-value",
|
|
951
|
+
"invalid-value",
|
|
931
952
|
"unknown-action-controller",
|
|
932
953
|
"unknown-action-method",
|
|
933
954
|
"orphan-target",
|
|
@@ -936,6 +957,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
936
957
|
"missing-aria",
|
|
937
958
|
"invalid-aria-value",
|
|
938
959
|
"keyboard-inaccessible",
|
|
960
|
+
"invalid-host",
|
|
939
961
|
"unresolved-idref",
|
|
940
962
|
"managed-aria",
|
|
941
963
|
"composition-mismatch",
|
|
@@ -947,6 +969,12 @@ var DIAGNOSTIC_CODES = [
|
|
|
947
969
|
];
|
|
948
970
|
|
|
949
971
|
// src/inspector/check.ts
|
|
972
|
+
function isInside(node, ancestor) {
|
|
973
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
974
|
+
if (current === ancestor) return true;
|
|
975
|
+
}
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
950
978
|
function checkSource(source, manifest) {
|
|
951
979
|
const diagnostics = [];
|
|
952
980
|
const tree = parseHtml(neutralizeErb(source), erbElements(source));
|
|
@@ -967,6 +995,36 @@ function checkSource(source, manifest) {
|
|
|
967
995
|
if (hostAttr && isDynamicValue(hostAttr)) return null;
|
|
968
996
|
return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
|
|
969
997
|
};
|
|
998
|
+
const isContentEditable2 = (element) => {
|
|
999
|
+
let current = element;
|
|
1000
|
+
while (current && current.tag !== "#root") {
|
|
1001
|
+
const attr = current.attrs.find((candidate) => candidate.name === "contenteditable");
|
|
1002
|
+
if (attr) {
|
|
1003
|
+
if (isDynamicValue(attr)) return null;
|
|
1004
|
+
const value = attr.value.trim().toLowerCase();
|
|
1005
|
+
if (value === "false") return false;
|
|
1006
|
+
if (value === "" || value === "true" || value === "plaintext-only") return true;
|
|
1007
|
+
}
|
|
1008
|
+
current = current.parent;
|
|
1009
|
+
}
|
|
1010
|
+
return false;
|
|
1011
|
+
};
|
|
1012
|
+
const hostIsAllowed = (element, rule) => {
|
|
1013
|
+
if (element.tag === "button") {
|
|
1014
|
+
if (rule.mode === "non-interactive") return false;
|
|
1015
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1016
|
+
if (type && isDynamicValue(type)) return null;
|
|
1017
|
+
const effectiveType = type?.value.trim().toLowerCase() || "submit";
|
|
1018
|
+
return (rule.buttonTypes ?? []).includes(effectiveType);
|
|
1019
|
+
}
|
|
1020
|
+
if (ALWAYS_INTERACTIVE_HOSTS.has(element.tag)) return false;
|
|
1021
|
+
if ((element.tag === "a" || element.tag === "area") && hasAttr(element, "href")) return false;
|
|
1022
|
+
if ((element.tag === "audio" || element.tag === "video") && hasAttr(element, "controls")) {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
const editable = isContentEditable2(element);
|
|
1026
|
+
return editable === null ? null : !editable;
|
|
1027
|
+
};
|
|
970
1028
|
const suppressed = (node, code) => {
|
|
971
1029
|
if (code === "unknown-ignore-code") return false;
|
|
972
1030
|
let current = node;
|
|
@@ -1074,6 +1132,7 @@ function checkSource(source, manifest) {
|
|
|
1074
1132
|
focusableRoleTally.set(role, (focusableRoleTally.get(role) ?? 0) + 1);
|
|
1075
1133
|
}
|
|
1076
1134
|
}
|
|
1135
|
+
const authoredValueIdentifiers = /* @__PURE__ */ new Set();
|
|
1077
1136
|
for (const attr of node.attrs) {
|
|
1078
1137
|
if (attr.name === IGNORE_ATTR) {
|
|
1079
1138
|
for (const token of attr.value.split(/\s+/).filter((t) => t.length > 0)) {
|
|
@@ -1198,6 +1257,7 @@ function checkSource(source, manifest) {
|
|
|
1198
1257
|
attr
|
|
1199
1258
|
);
|
|
1200
1259
|
} else {
|
|
1260
|
+
authoredValueIdentifiers.add(parsed.identifier);
|
|
1201
1261
|
const controller = known[parsed.identifier];
|
|
1202
1262
|
const valid = controller?.values.some((v) => dasherize(v) === parsed.valueToken) ?? false;
|
|
1203
1263
|
if (!valid) {
|
|
@@ -1209,6 +1269,20 @@ function checkSource(source, manifest) {
|
|
|
1209
1269
|
attr,
|
|
1210
1270
|
didYouMean(parsed.valueToken, (controller?.values ?? []).map(dasherize))
|
|
1211
1271
|
);
|
|
1272
|
+
} else if (!isDynamicValue(attr)) {
|
|
1273
|
+
const constraint = controller?.valueConstraints.find(
|
|
1274
|
+
(candidate) => dasherize(candidate.value) === parsed.valueToken
|
|
1275
|
+
);
|
|
1276
|
+
if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
|
|
1277
|
+
report(
|
|
1278
|
+
node,
|
|
1279
|
+
"invalid-value",
|
|
1280
|
+
"error",
|
|
1281
|
+
`Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
|
|
1282
|
+
attr,
|
|
1283
|
+
constraint.suggestion
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1212
1286
|
}
|
|
1213
1287
|
}
|
|
1214
1288
|
continue;
|
|
@@ -1243,6 +1317,34 @@ function checkSource(source, manifest) {
|
|
|
1243
1317
|
}
|
|
1244
1318
|
}
|
|
1245
1319
|
}
|
|
1320
|
+
for (const identifier of authoredValueIdentifiers) {
|
|
1321
|
+
const controller = known[identifier];
|
|
1322
|
+
if (!controller) continue;
|
|
1323
|
+
for (const relation of controller.valueRelations) {
|
|
1324
|
+
const leftAttr = node.attrs.find(
|
|
1325
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.left)}-value`
|
|
1326
|
+
);
|
|
1327
|
+
const rightAttr = node.attrs.find(
|
|
1328
|
+
(attr) => attr.name === `data-${identifier}-${dasherize(relation.right)}-value`
|
|
1329
|
+
);
|
|
1330
|
+
const at = rightAttr ?? leftAttr;
|
|
1331
|
+
if (!at || leftAttr && isDynamicValue(leftAttr) || rightAttr && isDynamicValue(rightAttr)) {
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
const left = leftAttr ? decodeNumericValue(leftAttr.value) : relation.leftDefault;
|
|
1335
|
+
const right = rightAttr ? decodeNumericValue(rightAttr.value) : relation.rightDefault;
|
|
1336
|
+
if (!Number.isFinite(left) || !Number.isFinite(right)) continue;
|
|
1337
|
+
if (relation.operator === "less-than-or-equal" && left <= right) continue;
|
|
1338
|
+
report(
|
|
1339
|
+
node,
|
|
1340
|
+
"invalid-value",
|
|
1341
|
+
"error",
|
|
1342
|
+
`Invalid values for "${identifier}": "${relation.left}" (${left}) must be less than or equal to "${relation.right}" (${right}).`,
|
|
1343
|
+
at,
|
|
1344
|
+
relation.suggestion
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1246
1348
|
});
|
|
1247
1349
|
const scopeIsComplete = (container, scope, identifier) => container.origin === "markup" && !targetsUnreadable(container, scope, identifier);
|
|
1248
1350
|
for (const { node, identifier } of scopes) {
|
|
@@ -1270,13 +1372,16 @@ function checkSource(source, manifest) {
|
|
|
1270
1372
|
const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
|
|
1271
1373
|
for (const rule of controller.conditionalTargets) {
|
|
1272
1374
|
if (!present.has(rule.whenPresent)) continue;
|
|
1273
|
-
const
|
|
1375
|
+
const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
|
|
1376
|
+
const owners = nodesOf(rule.whenPresent);
|
|
1377
|
+
const satisfied = (target) => rule.requireInside ? nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner))) : present.has(target);
|
|
1378
|
+
const missing = rule.require.filter((target) => !satisfied(target));
|
|
1274
1379
|
if (missing.length === 0) continue;
|
|
1275
1380
|
report(
|
|
1276
1381
|
node,
|
|
1277
1382
|
"missing-conditional-target",
|
|
1278
1383
|
complete ? "error" : "warning",
|
|
1279
|
-
`"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1384
|
+
rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
1280
1385
|
node,
|
|
1281
1386
|
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
1282
1387
|
);
|
|
@@ -1294,7 +1399,7 @@ function checkSource(source, manifest) {
|
|
|
1294
1399
|
if (!hasReadableMarkup(el)) continue;
|
|
1295
1400
|
if (!contentHolds(node, identifier, el, req.whenContains)) continue;
|
|
1296
1401
|
if (!elementHolds(el, req.whenElement)) continue;
|
|
1297
|
-
checkA11y(report, el, identifier, req, severity);
|
|
1402
|
+
checkA11y(report, el, identifier, req, severity, isDynamicValue);
|
|
1298
1403
|
}
|
|
1299
1404
|
}
|
|
1300
1405
|
}
|
|
@@ -1320,6 +1425,28 @@ function checkSource(source, manifest) {
|
|
|
1320
1425
|
}
|
|
1321
1426
|
}
|
|
1322
1427
|
}
|
|
1428
|
+
for (const { node, identifier } of scopes) {
|
|
1429
|
+
const controller = known[identifier];
|
|
1430
|
+
if (!controller) continue;
|
|
1431
|
+
for (const rule of controller.hosts) {
|
|
1432
|
+
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
1433
|
+
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
1434
|
+
for (const element of elements) {
|
|
1435
|
+
if (!hasReadableMarkup(element)) continue;
|
|
1436
|
+
if (hostIsAllowed(element, rule) !== false) continue;
|
|
1437
|
+
const type = element.attrs.find((attr) => attr.name === "type");
|
|
1438
|
+
const detail = element.tag === "button" ? `the unsupported ${type ? `type="${type.value}"` : 'implicit type="submit"'}` : `an unsupported interactive <${element.tag}>`;
|
|
1439
|
+
report(
|
|
1440
|
+
element,
|
|
1441
|
+
"invalid-host",
|
|
1442
|
+
"error",
|
|
1443
|
+
`The ${where} of "${identifier}" uses ${detail} host.`,
|
|
1444
|
+
type ?? element,
|
|
1445
|
+
rule.suggestion
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1323
1450
|
for (const { node, identifier } of scopes) {
|
|
1324
1451
|
const controller = known[identifier];
|
|
1325
1452
|
if (!controller) continue;
|
|
@@ -1561,12 +1688,31 @@ function checkSource(source, manifest) {
|
|
|
1561
1688
|
diagnostics.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
1562
1689
|
return diagnostics;
|
|
1563
1690
|
}
|
|
1691
|
+
function valueSatisfiesConstraint(raw, constraint) {
|
|
1692
|
+
const value = decodeNumericValue(raw);
|
|
1693
|
+
if (Number.isNaN(value)) return false;
|
|
1694
|
+
if (constraint.finite && !Number.isFinite(value)) return false;
|
|
1695
|
+
if (constraint.greaterThan !== void 0 && !(value > constraint.greaterThan)) return false;
|
|
1696
|
+
if (constraint.integer && !Number.isInteger(value)) return false;
|
|
1697
|
+
return true;
|
|
1698
|
+
}
|
|
1699
|
+
function decodeNumericValue(raw) {
|
|
1700
|
+
return Number(raw.replace(/_/g, ""));
|
|
1701
|
+
}
|
|
1702
|
+
function describeValueConstraint(constraint) {
|
|
1703
|
+
const parts = [constraint.finite ? "a finite number" : "a number"];
|
|
1704
|
+
if (constraint.greaterThan !== void 0) {
|
|
1705
|
+
parts.push(`greater than ${constraint.greaterThan}`);
|
|
1706
|
+
}
|
|
1707
|
+
if (constraint.integer) parts.push("with no fractional part");
|
|
1708
|
+
return parts.join(" ");
|
|
1709
|
+
}
|
|
1564
1710
|
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.";
|
|
1565
1711
|
var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal hash, so the host controller may be declared at runtime.";
|
|
1566
1712
|
function hasReadableMarkup(el) {
|
|
1567
1713
|
return el.origin === "markup";
|
|
1568
1714
|
}
|
|
1569
|
-
function checkA11y(report, el, identifier, req, severity) {
|
|
1715
|
+
function checkA11y(report, el, identifier, req, severity, isDynamicValue) {
|
|
1570
1716
|
const groups = [
|
|
1571
1717
|
{ attrs: req.attrs, values: req.values },
|
|
1572
1718
|
...req.or ?? []
|
|
@@ -1586,9 +1732,14 @@ function checkA11y(report, el, identifier, req, severity) {
|
|
|
1586
1732
|
}
|
|
1587
1733
|
const invalid = [];
|
|
1588
1734
|
let anyPresent = false;
|
|
1735
|
+
let blank;
|
|
1589
1736
|
for (const [name, allowed] of allowedByAttr) {
|
|
1590
1737
|
const attr = el.attrs.find((a) => a.name === name);
|
|
1591
1738
|
if (!attr) continue;
|
|
1739
|
+
if (allowed.anyValue && attr.value.trim() === "" && !isDynamicValue(attr)) {
|
|
1740
|
+
blank ??= attr;
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1592
1743
|
anyPresent = true;
|
|
1593
1744
|
if (!allowed.anyValue && !allowed.values.has(attr.value.trim())) {
|
|
1594
1745
|
invalid.push({ attr, values: [...allowed.values] });
|
|
@@ -1600,8 +1751,8 @@ function checkA11y(report, el, identifier, req, severity) {
|
|
|
1600
1751
|
el,
|
|
1601
1752
|
"missing-aria",
|
|
1602
1753
|
severity,
|
|
1603
|
-
`"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
|
|
1604
|
-
el,
|
|
1754
|
+
blank ? `${blank.name} is empty on the ${where} of "${identifier}", which ${verb} a value there.` : `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
|
|
1755
|
+
blank ?? el,
|
|
1605
1756
|
req.suggestion
|
|
1606
1757
|
);
|
|
1607
1758
|
return;
|
|
@@ -1633,6 +1784,20 @@ function describeCounted(target, attr, values) {
|
|
|
1633
1784
|
return `"${target}" targets with ${attr}${constrained}`;
|
|
1634
1785
|
}
|
|
1635
1786
|
var NATIVE_FOCUSABLE_TAGS = /* @__PURE__ */ new Set(["button", "select", "textarea", "summary"]);
|
|
1787
|
+
var ALWAYS_INTERACTIVE_HOSTS = /* @__PURE__ */ new Set([
|
|
1788
|
+
"input",
|
|
1789
|
+
"select",
|
|
1790
|
+
"textarea",
|
|
1791
|
+
"label",
|
|
1792
|
+
"summary",
|
|
1793
|
+
"details",
|
|
1794
|
+
"iframe",
|
|
1795
|
+
"object",
|
|
1796
|
+
"embed"
|
|
1797
|
+
]);
|
|
1798
|
+
function hasAttr(element, name) {
|
|
1799
|
+
return element.attrs.some((attr) => attr.name === name);
|
|
1800
|
+
}
|
|
1636
1801
|
function isNativelyFocusable(el) {
|
|
1637
1802
|
if (NATIVE_FOCUSABLE_TAGS.has(el.tag)) return true;
|
|
1638
1803
|
if (el.tag === "input") {
|
|
@@ -1783,12 +1948,15 @@ function isCheckableFile(path) {
|
|
|
1783
1948
|
var CONTROLLER_ARRAY_FIELDS = [
|
|
1784
1949
|
"targets",
|
|
1785
1950
|
"values",
|
|
1951
|
+
"valueConstraints",
|
|
1952
|
+
"valueRelations",
|
|
1786
1953
|
"actions",
|
|
1787
1954
|
"events",
|
|
1788
1955
|
"requiredTargets",
|
|
1789
1956
|
"conditionalTargets",
|
|
1790
1957
|
"a11y",
|
|
1791
1958
|
"keyboard",
|
|
1959
|
+
"hosts",
|
|
1792
1960
|
"managedAria",
|
|
1793
1961
|
"compositions",
|
|
1794
1962
|
"companions",
|
|
@@ -2261,6 +2429,37 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2261
2429
|
id: { type: "string", description: 'Controller identifier, e.g. "stimeo--tabs".' },
|
|
2262
2430
|
targets: STRING_ARRAY,
|
|
2263
2431
|
values: STRING_ARRAY,
|
|
2432
|
+
valueConstraints: {
|
|
2433
|
+
type: "array",
|
|
2434
|
+
description: "Statically checkable semantic bounds on authored Stimulus Values.",
|
|
2435
|
+
items: {
|
|
2436
|
+
type: "object",
|
|
2437
|
+
properties: {
|
|
2438
|
+
value: { type: "string", description: "Camel-case declared Value name." },
|
|
2439
|
+
type: { type: "string", enum: ["number"] },
|
|
2440
|
+
finite: { type: "boolean" },
|
|
2441
|
+
greaterThan: { type: "number" },
|
|
2442
|
+
suggestion: { type: "string" }
|
|
2443
|
+
},
|
|
2444
|
+
required: ["value", "type", "suggestion"]
|
|
2445
|
+
}
|
|
2446
|
+
},
|
|
2447
|
+
valueRelations: {
|
|
2448
|
+
type: "array",
|
|
2449
|
+
description: "Statically checkable relationships between authored Stimulus Values.",
|
|
2450
|
+
items: {
|
|
2451
|
+
type: "object",
|
|
2452
|
+
properties: {
|
|
2453
|
+
left: { type: "string", description: "Camel-case left Value name." },
|
|
2454
|
+
operator: { type: "string", enum: ["less-than-or-equal"] },
|
|
2455
|
+
right: { type: "string", description: "Camel-case right Value name." },
|
|
2456
|
+
leftDefault: { type: "number" },
|
|
2457
|
+
rightDefault: { type: "number" },
|
|
2458
|
+
suggestion: { type: "string" }
|
|
2459
|
+
},
|
|
2460
|
+
required: ["left", "operator", "right", "leftDefault", "rightDefault", "suggestion"]
|
|
2461
|
+
}
|
|
2462
|
+
},
|
|
2264
2463
|
actions: STRING_ARRAY,
|
|
2265
2464
|
events: STRING_ARRAY,
|
|
2266
2465
|
requiredTargets: {
|
|
@@ -2342,6 +2541,30 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2342
2541
|
required: ["target", "suggestion"]
|
|
2343
2542
|
}
|
|
2344
2543
|
},
|
|
2544
|
+
hosts: {
|
|
2545
|
+
type: "array",
|
|
2546
|
+
description: "Scope or target host-element contracts that prevent the controller from competing with an incompatible native interaction.",
|
|
2547
|
+
items: {
|
|
2548
|
+
type: "object",
|
|
2549
|
+
properties: {
|
|
2550
|
+
target: {
|
|
2551
|
+
type: "string",
|
|
2552
|
+
description: 'Target name; "" means the data-controller element itself.'
|
|
2553
|
+
},
|
|
2554
|
+
mode: {
|
|
2555
|
+
type: "string",
|
|
2556
|
+
enum: ["non-interactive", "non-interactive-or-button"],
|
|
2557
|
+
description: "Supported static host family."
|
|
2558
|
+
},
|
|
2559
|
+
buttonTypes: {
|
|
2560
|
+
...STRING_ARRAY,
|
|
2561
|
+
description: "Literal button types admitted by non-interactive-or-button."
|
|
2562
|
+
},
|
|
2563
|
+
suggestion: { type: "string" }
|
|
2564
|
+
},
|
|
2565
|
+
required: ["target", "mode", "suggestion"]
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2345
2568
|
managedAria: {
|
|
2346
2569
|
type: "array",
|
|
2347
2570
|
description: "ARIA the controller recomputes at runtime; authoring it is a warning.",
|
|
@@ -2478,12 +2701,15 @@ var CONTROLLER_CONTRACT_SCHEMA = {
|
|
|
2478
2701
|
"id",
|
|
2479
2702
|
"targets",
|
|
2480
2703
|
"values",
|
|
2704
|
+
"valueConstraints",
|
|
2705
|
+
"valueRelations",
|
|
2481
2706
|
"actions",
|
|
2482
2707
|
"events",
|
|
2483
2708
|
"requiredTargets",
|
|
2484
2709
|
"conditionalTargets",
|
|
2485
2710
|
"a11y",
|
|
2486
2711
|
"keyboard",
|
|
2712
|
+
"hosts",
|
|
2487
2713
|
"managedAria",
|
|
2488
2714
|
"compositions",
|
|
2489
2715
|
"companions",
|
|
@@ -2502,7 +2728,7 @@ var TOOL_DESCRIPTORS = [
|
|
|
2502
2728
|
{
|
|
2503
2729
|
name: "stimeo_check",
|
|
2504
2730
|
title: "Check Stimeo UI markup",
|
|
2505
|
-
description: "Statically check HTML or ERB source that uses Stimeo UI (stimeo--*) controllers \u2014 the headless, accessible Stimulus UI component library for Rails. Returns a CheckReport with diagnostics \u2014 unknown controllers/targets/values/action methods, missing required targets, missing or invalid author-supplied ARIA, keyboard-focusability prerequisites, and misaligned cross-controller composition values \u2014 each with a fix suggestion when available. Call this to validate any Rails view, ERB template, or HTML you write or edit with these components, before finalizing it; ok=true means no errors.",
|
|
2731
|
+
description: "Statically check HTML or ERB source that uses Stimeo UI (stimeo--*) controllers \u2014 the headless, accessible Stimulus UI component library for Rails. Returns a CheckReport with diagnostics \u2014 unknown controllers/targets/values/action methods, missing required targets, missing or invalid author-supplied ARIA, keyboard-focusability prerequisites, incompatible interactive hosts, and misaligned cross-controller composition values \u2014 each with a fix suggestion when available. Call this to validate any Rails view, ERB template, or HTML you write or edit with these components, before finalizing it; ok=true means no errors.",
|
|
2506
2732
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
2507
2733
|
inputSchema: {
|
|
2508
2734
|
type: "object",
|
|
@@ -2536,7 +2762,7 @@ var TOOL_DESCRIPTORS = [
|
|
|
2536
2762
|
{
|
|
2537
2763
|
name: "stimeo_controller",
|
|
2538
2764
|
title: "Get one controller's usage contract",
|
|
2539
|
-
description: 'Fetch the full usage contract of one Stimeo UI controller by identifier (e.g. "stimeo--tabs"): targets, values, actions, events, required targets, and the accessibility contract \u2014 author-supplied ARIA requirements, keyboard-focusability prerequisites, the ARIA attributes the controller manages at runtime (which the author must NOT hardcode), and cross-controller composition value alignments. Use it after stimeo_catalog to write correct, accessible Rails ERB or HTML markup for that Stimulus component.',
|
|
2765
|
+
description: 'Fetch the full usage contract of one Stimeo UI controller by identifier (e.g. "stimeo--tabs"): targets, values, actions, events, required targets, and the accessibility contract \u2014 author-supplied ARIA requirements, keyboard-focusability prerequisites, supported host-element shapes, the ARIA attributes the controller manages at runtime (which the author must NOT hardcode), and cross-controller composition value alignments. Use it after stimeo_catalog to write correct, accessible Rails ERB or HTML markup for that Stimulus component.',
|
|
2540
2766
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
2541
2767
|
inputSchema: {
|
|
2542
2768
|
type: "object",
|