@burdenoff/microfe-buildmyiq 2026.1025.2 → 2026.1025.4

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 (45) hide show
  1. package/dist/buildmyiq/components/AccessibilityChecker.d.ts +21 -0
  2. package/dist/buildmyiq/components/AccessibilityChecker.d.ts.map +1 -0
  3. package/dist/buildmyiq/components/AccessibilityChecker.js +151 -0
  4. package/dist/buildmyiq/components/AccessibilityChecker.js.map +1 -0
  5. package/dist/buildmyiq/components/index.d.ts +1 -0
  6. package/dist/buildmyiq/components/index.d.ts.map +1 -1
  7. package/dist/buildmyiq/components/index.js +1 -0
  8. package/dist/buildmyiq/hooks/accessibilityDraft.d.ts +4 -0
  9. package/dist/buildmyiq/hooks/accessibilityDraft.d.ts.map +1 -0
  10. package/dist/buildmyiq/hooks/accessibilityDraft.js +6 -0
  11. package/dist/buildmyiq/hooks/accessibilityDraft.js.map +1 -0
  12. package/dist/buildmyiq/hooks/index.d.ts +1 -0
  13. package/dist/buildmyiq/hooks/index.d.ts.map +1 -1
  14. package/dist/buildmyiq/hooks/index.js +2 -0
  15. package/dist/buildmyiq/hooks/useBuiAccessibility.d.ts +21 -0
  16. package/dist/buildmyiq/hooks/useBuiAccessibility.d.ts.map +1 -0
  17. package/dist/buildmyiq/hooks/useBuiAccessibility.js +67 -0
  18. package/dist/buildmyiq/hooks/useBuiAccessibility.js.map +1 -0
  19. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -1
  20. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +154 -144
  21. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
  22. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.d.ts +5 -0
  23. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.d.ts.map +1 -0
  24. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.js +156 -0
  25. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.js.map +1 -0
  26. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
  27. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +91 -74
  28. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
  29. package/dist/buildmyiq/pages/submissions/AnswersView.d.ts +13 -0
  30. package/dist/buildmyiq/pages/submissions/AnswersView.d.ts.map +1 -1
  31. package/dist/buildmyiq/pages/submissions/AnswersView.js +13 -9
  32. package/dist/buildmyiq/pages/submissions/AnswersView.js.map +1 -1
  33. package/dist/buildmyiq/pages/submissions/BulkGradingPage.d.ts.map +1 -1
  34. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js +139 -138
  35. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js.map +1 -1
  36. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +4 -1
  37. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  38. package/dist/buildmyiq/types/index.d.ts +48 -0
  39. package/dist/buildmyiq/types/index.d.ts.map +1 -1
  40. package/dist/buildmyiq/types/index.js.map +1 -1
  41. package/dist/buildmyiq/utils/wcag.d.ts +6 -0
  42. package/dist/buildmyiq/utils/wcag.d.ts.map +1 -0
  43. package/dist/buildmyiq/utils/wcag.js +21 -0
  44. package/dist/buildmyiq/utils/wcag.js.map +1 -0
  45. package/package.json +1 -1
@@ -0,0 +1,21 @@
1
+ import { FC } from 'react';
2
+ import { BuiAccessibilityIssue, BuiAccessibilityReport, BuiCheckContentAccessibilityInput } from '../types';
3
+ export declare const AccessibilityIssueList: FC<{
4
+ issues: BuiAccessibilityIssue[];
5
+ className?: string;
6
+ }>;
7
+ /** One-line result summary, e.g. "2 errors · 1 warning". */
8
+ export declare const useAccessibilitySummary: () => ((report: Pick<BuiAccessibilityReport, "errorCount" | "warningCount">) => string);
9
+ interface AccessibilityCheckerProps {
10
+ /** Draft to check; re-checked 600 ms after it stops changing. */
11
+ input: BuiCheckContentAccessibilityInput;
12
+ id?: string;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Live accessibility check for an authoring form. Advisory: it never blocks
17
+ * saving, it tells the author what to fix and why (with the WCAG criterion).
18
+ */
19
+ export declare const AccessibilityChecker: FC<AccessibilityCheckerProps>;
20
+ export {};
21
+ //# sourceMappingURL=AccessibilityChecker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccessibilityChecker.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/AccessibilityChecker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAShC,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EAEtB,iCAAiC,EAClC,MAAM,UAAU,CAAC;AAyFlB,eAAO,MAAM,sBAAsB,EAAE,EAAE,CAAC;IACtC,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAMA,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,QAAO,CAAC,CAC1C,MAAM,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,cAAc,CAAC,KAChE,MAAM,CAyBV,CAAC;AAyBF,UAAU,yBAAyB;IACjC,iEAAiE;IACjE,KAAK,EAAE,iCAAiC,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,yBAAyB,CAmF9D,CAAC"}
@@ -0,0 +1,151 @@
1
+ import { useTr as e } from "../shared/hooks/useTr.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import { hasCheckableContent as n } from "../hooks/accessibilityDraft.js";
4
+ import { useBuiCheckContentAccessibility as r, useDebouncedValue as i } from "../hooks/useBuiAccessibility.js";
5
+ import { wcagUnderstandingUrl as a } from "../utils/wcag.js";
6
+ import { AlertTriangle as o, CheckCircle2 as s, ExternalLink as c, Loader2 as l, XCircle as u } from "lucide-react";
7
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
+ //#region src/buildmyiq/components/AccessibilityChecker.tsx
9
+ var p = 600, m = {
10
+ IMAGE_ALT_MISSING: "Image without alt text",
11
+ IMAGE_ALT_EMPTY: "Image marked decorative",
12
+ IMAGE_ALT_NOT_DESCRIPTIVE: "Alt text is not descriptive",
13
+ LINK_TEXT_EMPTY: "Link without text",
14
+ LINK_TEXT_NOT_DESCRIPTIVE: "Vague link text",
15
+ LINK_TEXT_RAW_URL: "Raw URL as link text",
16
+ HEADING_EMPTY: "Empty heading",
17
+ HEADING_LEVEL_SKIPPED: "Skipped heading level",
18
+ TABLE_HEADER_MISSING: "Table without headers",
19
+ MEDIA_CAPTIONS_MISSING: "Video without captions",
20
+ MEDIA_AUTOPLAY: "Autoplaying media",
21
+ MEDIA_TRANSCRIPT_MISSING: "No transcript",
22
+ IFRAME_TITLE_MISSING: "Embed without a title",
23
+ MOVING_CONTENT: "Moving or blinking text",
24
+ COLOR_STYLING: "Inline text colour",
25
+ SENSORY_INSTRUCTION: "Colour or position-only instruction",
26
+ OPTION_LABEL_EMPTY: "Answer option without a label",
27
+ OPTION_LABEL_DUPLICATE: "Duplicate answer options"
28
+ }, h = ({ issue: n }) => {
29
+ let r = e(), i = n.severity === "ERROR", s = i ? u : o, l = a(n.wcag), p = r("buildmyiq.accessibility.wcagLink", "WCAG {{criterion}} (Level {{level}})", {
30
+ criterion: n.wcag,
31
+ level: n.wcagLevel
32
+ });
33
+ return /* @__PURE__ */ f("li", {
34
+ className: "flex gap-2.5 py-2.5 first:pt-0 last:pb-0",
35
+ children: [/* @__PURE__ */ d(s, {
36
+ "aria-hidden": "true",
37
+ className: t("mt-0.5 h-4 w-4 shrink-0", i ? "text-status-error-text" : "text-status-warning-text")
38
+ }), /* @__PURE__ */ f("div", {
39
+ className: "min-w-0 flex-1",
40
+ children: [
41
+ /* @__PURE__ */ f("p", {
42
+ className: "text-sm font-medium text-text-primary",
43
+ children: [
44
+ /* @__PURE__ */ f("span", {
45
+ className: "sr-only",
46
+ children: [i ? r("buildmyiq.accessibility.severity.error", "Error:") : r("buildmyiq.accessibility.severity.warning", "Warning:"), " "]
47
+ }),
48
+ r(`buildmyiq.accessibility.rules.${n.rule}`, m[n.rule] ?? n.rule),
49
+ n.line != null && /* @__PURE__ */ d("span", {
50
+ className: "ml-1.5 text-xs font-normal text-text-secondary",
51
+ children: r("buildmyiq.accessibility.line", "line {{line}}", { line: n.line })
52
+ })
53
+ ]
54
+ }),
55
+ /* @__PURE__ */ d("p", {
56
+ className: "mt-0.5 text-sm text-text-secondary",
57
+ children: r(`buildmyiq.accessibility.messages.${n.rule}`, n.message)
58
+ }),
59
+ n.excerpt && /* @__PURE__ */ d("code", {
60
+ className: "mt-1 block truncate rounded bg-bg-sunken px-1.5 py-0.5 font-mono text-xs text-text-secondary",
61
+ children: n.excerpt
62
+ }),
63
+ l ? /* @__PURE__ */ f("a", {
64
+ href: l,
65
+ target: "_blank",
66
+ rel: "noreferrer noopener",
67
+ className: "mt-1 inline-flex items-center gap-1 text-xs text-text-secondary underline underline-offset-2 hover:text-text-primary",
68
+ children: [
69
+ p,
70
+ /* @__PURE__ */ d(c, {
71
+ "aria-hidden": "true",
72
+ className: "h-3 w-3"
73
+ }),
74
+ /* @__PURE__ */ d("span", {
75
+ className: "sr-only",
76
+ children: r("buildmyiq.accessibility.opensNewTab", "(opens in a new tab)")
77
+ })
78
+ ]
79
+ }) : /* @__PURE__ */ d("span", {
80
+ className: "mt-1 block text-xs text-text-secondary",
81
+ children: p
82
+ })
83
+ ]
84
+ })]
85
+ });
86
+ }, g = ({ issues: e, className: n }) => /* @__PURE__ */ d("ul", {
87
+ className: t("divide-y divide-border-seam", n),
88
+ children: e.map((e, t) => /* @__PURE__ */ d(h, { issue: e }, `${e.rule}-${e.line ?? "x"}-${t}`))
89
+ }), _ = () => {
90
+ let t = e();
91
+ return (e) => {
92
+ if (e.errorCount === 0 && e.warningCount === 0) return t("buildmyiq.accessibility.noIssues", "No accessibility issues found");
93
+ let n = [];
94
+ return e.errorCount > 0 && n.push(e.errorCount === 1 ? t("buildmyiq.accessibility.oneError", "1 error") : t("buildmyiq.accessibility.errors", "{{count}} errors", { count: e.errorCount })), e.warningCount > 0 && n.push(e.warningCount === 1 ? t("buildmyiq.accessibility.oneWarning", "1 warning") : t("buildmyiq.accessibility.warnings", "{{count}} warnings", { count: e.warningCount })), n.join(" · ");
95
+ };
96
+ }, v = {
97
+ checking: l,
98
+ error: u,
99
+ warning: o,
100
+ clean: s
101
+ }, y = {
102
+ checking: "motion-safe:animate-spin",
103
+ error: "text-status-error-text",
104
+ warning: "text-status-warning-text",
105
+ clean: "text-status-success-text"
106
+ }, b = (e, t) => e || !t ? "checking" : t.errorCount > 0 ? "error" : t.warningCount > 0 ? "warning" : "clean", x = ({ input: a, id: o = "accessibility-check", className: s }) => {
107
+ let c = e(), l = _(), u = i(a, p), m = r(u), h = n(a), x = h ? m.data : void 0, S = JSON.stringify(a) !== JSON.stringify(u) || m.isFetching || m.isPlaceholderData, C = h && !S && m.isError, w = !h, T = `${o}-heading`, E;
108
+ E = w ? c("buildmyiq.accessibility.idle", "Start writing and the content will be checked for accessibility.") : C ? c("buildmyiq.accessibility.unavailable", "Accessibility check is unavailable right now.") : S || !x ? c("buildmyiq.accessibility.checking", "Checking accessibility…") : l(x);
109
+ let D = w || S || !x && !C ? "" : E, O = b(S, x), k = v[O];
110
+ return /* @__PURE__ */ f("section", {
111
+ "aria-labelledby": T,
112
+ "data-testid": "accessibility-checker",
113
+ className: t("rounded-xl border border-border-seam bg-bg-sunken/40 p-3", s),
114
+ children: [
115
+ /* @__PURE__ */ f("div", {
116
+ className: "flex items-center justify-between gap-2",
117
+ children: [
118
+ /* @__PURE__ */ d("h3", {
119
+ id: T,
120
+ className: "text-sm font-semibold text-text-primary",
121
+ children: c("buildmyiq.accessibility.title", "Accessibility check")
122
+ }),
123
+ /* @__PURE__ */ f("p", {
124
+ "aria-hidden": "true",
125
+ "data-testid": "accessibility-checker-status",
126
+ className: "inline-flex items-center gap-1.5 text-xs text-text-secondary",
127
+ children: [!w && !C && /* @__PURE__ */ d(k, { className: t("h-3.5 w-3.5", y[O]) }), E]
128
+ }),
129
+ /* @__PURE__ */ d("p", {
130
+ role: "status",
131
+ "aria-live": "polite",
132
+ className: "sr-only",
133
+ children: D
134
+ })
135
+ ]
136
+ }),
137
+ x && !C && x.issues.length > 0 && /* @__PURE__ */ d(g, {
138
+ issues: x.issues,
139
+ className: "mt-3"
140
+ }),
141
+ x?.truncated && /* @__PURE__ */ d("p", {
142
+ className: "mt-2 text-xs text-text-secondary",
143
+ children: c("buildmyiq.accessibility.truncated", "Only the first part of this content was checked because it is very long.")
144
+ })
145
+ ]
146
+ });
147
+ };
148
+ //#endregion
149
+ export { x as AccessibilityChecker, g as AccessibilityIssueList, _ as useAccessibilitySummary };
150
+
151
+ //# sourceMappingURL=AccessibilityChecker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccessibilityChecker.js","names":[],"sources":["../../../src/buildmyiq/components/AccessibilityChecker.tsx"],"sourcesContent":["/**\n * Accessibility checker UI (BOFF-4423).\n * @module buildmyiq/components/AccessibilityChecker\n *\n * - `AccessibilityChecker` — live, debounced check of a draft inside an\n * authoring form (lesson body, question prompt/options/explanation).\n * - `AccessibilityIssueList` — the issue list, shared with the course-level\n * report on the Course builder's Accessibility tab.\n *\n * Rules run server-side (`buiCheckContentAccessibility`); this only renders.\n * Status changes are announced through a polite live region so screen-reader\n * authors hear the result without losing their place in the form.\n */\n\nimport type { FC } from 'react';\nimport { AlertTriangle, CheckCircle2, ExternalLink, Loader2, XCircle } from 'lucide-react';\n\nimport {\n hasCheckableContent,\n useBuiCheckContentAccessibility,\n useDebouncedValue,\n} from '../hooks/useBuiAccessibility';\nimport { useTr } from '../shared/hooks/useTr';\nimport type {\n BuiAccessibilityIssue,\n BuiAccessibilityReport,\n BuiAccessibilityRule,\n BuiCheckContentAccessibilityInput,\n} from '../types';\nimport { cn } from '../utils/cn';\nimport { wcagUnderstandingUrl } from '../utils/wcag';\n\nconst DEBOUNCE_MS = 600;\n\n/** Short English title per rule (translatable via `buildmyiq.accessibility.rules.<RULE>`). */\nconst RULE_TITLES: Record<BuiAccessibilityRule, string> = {\n IMAGE_ALT_MISSING: 'Image without alt text',\n IMAGE_ALT_EMPTY: 'Image marked decorative',\n IMAGE_ALT_NOT_DESCRIPTIVE: 'Alt text is not descriptive',\n LINK_TEXT_EMPTY: 'Link without text',\n LINK_TEXT_NOT_DESCRIPTIVE: 'Vague link text',\n LINK_TEXT_RAW_URL: 'Raw URL as link text',\n HEADING_EMPTY: 'Empty heading',\n HEADING_LEVEL_SKIPPED: 'Skipped heading level',\n TABLE_HEADER_MISSING: 'Table without headers',\n MEDIA_CAPTIONS_MISSING: 'Video without captions',\n MEDIA_AUTOPLAY: 'Autoplaying media',\n MEDIA_TRANSCRIPT_MISSING: 'No transcript',\n IFRAME_TITLE_MISSING: 'Embed without a title',\n MOVING_CONTENT: 'Moving or blinking text',\n COLOR_STYLING: 'Inline text colour',\n SENSORY_INSTRUCTION: 'Colour or position-only instruction',\n OPTION_LABEL_EMPTY: 'Answer option without a label',\n OPTION_LABEL_DUPLICATE: 'Duplicate answer options',\n};\n\nconst IssueRow: FC<{ issue: BuiAccessibilityIssue }> = ({ issue }) => {\n const tr = useTr();\n const isError = issue.severity === 'ERROR';\n const Icon = isError ? XCircle : AlertTriangle;\n const url = wcagUnderstandingUrl(issue.wcag);\n const wcagLabel = tr('buildmyiq.accessibility.wcagLink', 'WCAG {{criterion}} (Level {{level}})', {\n criterion: issue.wcag,\n level: issue.wcagLevel,\n });\n return (\n <li className=\"flex gap-2.5 py-2.5 first:pt-0 last:pb-0\">\n <Icon\n aria-hidden=\"true\"\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n isError ? 'text-status-error-text' : 'text-status-warning-text'\n )}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n <span className=\"sr-only\">\n {isError\n ? tr('buildmyiq.accessibility.severity.error', 'Error:')\n : tr('buildmyiq.accessibility.severity.warning', 'Warning:')}{' '}\n </span>\n {tr(`buildmyiq.accessibility.rules.${issue.rule}`, RULE_TITLES[issue.rule] ?? issue.rule)}\n {issue.line != null && (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr('buildmyiq.accessibility.line', 'line {{line}}', { line: issue.line })}\n </span>\n )}\n </p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(`buildmyiq.accessibility.messages.${issue.rule}`, issue.message)}\n </p>\n {issue.excerpt && (\n <code className=\"mt-1 block truncate rounded bg-bg-sunken px-1.5 py-0.5 font-mono text-xs text-text-secondary\">\n {issue.excerpt}\n </code>\n )}\n {url ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary underline underline-offset-2 hover:text-text-primary\"\n >\n {wcagLabel}\n <ExternalLink aria-hidden=\"true\" className=\"h-3 w-3\" />\n <span className=\"sr-only\">\n {tr('buildmyiq.accessibility.opensNewTab', '(opens in a new tab)')}\n </span>\n </a>\n ) : (\n <span className=\"mt-1 block text-xs text-text-secondary\">{wcagLabel}</span>\n )}\n </div>\n </li>\n );\n};\n\nexport const AccessibilityIssueList: FC<{\n issues: BuiAccessibilityIssue[];\n className?: string;\n}> = ({ issues, className }) => (\n <ul className={cn('divide-y divide-border-seam', className)}>\n {issues.map((issue, index) => (\n <IssueRow key={`${issue.rule}-${issue.line ?? 'x'}-${index}`} issue={issue} />\n ))}\n </ul>\n);\n\n/** One-line result summary, e.g. \"2 errors · 1 warning\". */\nexport const useAccessibilitySummary = (): ((\n report: Pick<BuiAccessibilityReport, 'errorCount' | 'warningCount'>\n) => string) => {\n const tr = useTr();\n return (report) => {\n if (report.errorCount === 0 && report.warningCount === 0) {\n return tr('buildmyiq.accessibility.noIssues', 'No accessibility issues found');\n }\n const parts: string[] = [];\n if (report.errorCount > 0) {\n parts.push(\n report.errorCount === 1\n ? tr('buildmyiq.accessibility.oneError', '1 error')\n : tr('buildmyiq.accessibility.errors', '{{count}} errors', { count: report.errorCount })\n );\n }\n if (report.warningCount > 0) {\n parts.push(\n report.warningCount === 1\n ? tr('buildmyiq.accessibility.oneWarning', '1 warning')\n : tr('buildmyiq.accessibility.warnings', '{{count}} warnings', {\n count: report.warningCount,\n })\n );\n }\n return parts.join(' · ');\n };\n};\n\ntype StatusTone = 'checking' | 'error' | 'warning' | 'clean';\n\nconst STATUS_ICONS = {\n checking: Loader2,\n error: XCircle,\n warning: AlertTriangle,\n clean: CheckCircle2,\n} as const;\n\nconst STATUS_TONE_CLASS: Record<StatusTone, string> = {\n checking: 'motion-safe:animate-spin',\n error: 'text-status-error-text',\n warning: 'text-status-warning-text',\n clean: 'text-status-success-text',\n};\n\nconst statusTone = (stale: boolean, report: BuiAccessibilityReport | undefined): StatusTone => {\n if (stale || !report) return 'checking';\n if (report.errorCount > 0) return 'error';\n if (report.warningCount > 0) return 'warning';\n return 'clean';\n};\n\ninterface AccessibilityCheckerProps {\n /** Draft to check; re-checked 600 ms after it stops changing. */\n input: BuiCheckContentAccessibilityInput;\n id?: string;\n className?: string;\n}\n\n/**\n * Live accessibility check for an authoring form. Advisory: it never blocks\n * saving, it tells the author what to fix and why (with the WCAG criterion).\n */\nexport const AccessibilityChecker: FC<AccessibilityCheckerProps> = ({\n input,\n id = 'accessibility-check',\n className,\n}) => {\n const tr = useTr();\n const summarise = useAccessibilitySummary();\n const debounced = useDebouncedValue(input, DEBOUNCE_MS);\n const query = useBuiCheckContentAccessibility(debounced);\n // A cleared draft is idle again: never show the previous draft's result.\n const checkable = hasCheckableContent(input);\n const report = checkable ? query.data : undefined;\n // The shown report belongs to an older draft while the author is still\n // typing (debounce pending), while a request is in flight, or while React\n // Query is showing the previous key's data as a placeholder.\n const stale =\n JSON.stringify(input) !== JSON.stringify(debounced) ||\n query.isFetching ||\n query.isPlaceholderData;\n const failed = checkable && !stale && query.isError;\n const idle = !checkable;\n const headingId = `${id}-heading`;\n\n let status: string;\n if (idle) {\n status = tr(\n 'buildmyiq.accessibility.idle',\n 'Start writing and the content will be checked for accessibility.'\n );\n } else if (failed) {\n status = tr(\n 'buildmyiq.accessibility.unavailable',\n 'Accessibility check is unavailable right now.'\n );\n } else if (stale || !report) {\n status = tr('buildmyiq.accessibility.checking', 'Checking accessibility…');\n } else {\n status = summarise(report);\n }\n // Screen readers only hear settled results, never the transient \"checking\"\n // state, so typing does not trigger two announcements per pause.\n const announcement = idle || stale || (!report && !failed) ? '' : status;\n\n const tone = statusTone(stale, report);\n const StatusIcon = STATUS_ICONS[tone];\n\n return (\n <section\n aria-labelledby={headingId}\n data-testid=\"accessibility-checker\"\n className={cn('rounded-xl border border-border-seam bg-bg-sunken/40 p-3', className)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <h3 id={headingId} className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.accessibility.title', 'Accessibility check')}\n </h3>\n <p\n aria-hidden=\"true\"\n data-testid=\"accessibility-checker-status\"\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n {!idle && !failed && (\n <StatusIcon className={cn('h-3.5 w-3.5', STATUS_TONE_CLASS[tone])} />\n )}\n {status}\n </p>\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </p>\n </div>\n {report && !failed && report.issues.length > 0 && (\n <AccessibilityIssueList issues={report.issues} className=\"mt-3\" />\n )}\n {report?.truncated && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'buildmyiq.accessibility.truncated',\n 'Only the first part of this content was checked because it is very long.'\n )}\n </p>\n )}\n </section>\n );\n};\n"],"mappings":";;;;;;;;AAgCA,IAAM,IAAc,KAGd,IAAoD;CACxD,mBAAmB;CACnB,iBAAiB;CACjB,2BAA2B;CAC3B,iBAAiB;CACjB,2BAA2B;CAC3B,mBAAmB;CACnB,eAAe;CACf,uBAAuB;CACvB,sBAAsB;CACtB,wBAAwB;CACxB,gBAAgB;CAChB,0BAA0B;CAC1B,sBAAsB;CACtB,gBAAgB;CAChB,eAAe;CACf,qBAAqB;CACrB,oBAAoB;CACpB,wBAAwB;AAC1B,GAEM,KAAkD,EAAE,eAAY;CACpE,IAAM,IAAK,EAAM,GACX,IAAU,EAAM,aAAa,SAC7B,IAAO,IAAU,IAAU,GAC3B,IAAM,EAAqB,EAAM,IAAI,GACrC,IAAY,EAAG,oCAAoC,wCAAwC;EAC/F,WAAW,EAAM;EACjB,OAAO,EAAM;CACf,CAAC;CACD,OACE,kBAAC,MAAD;EAAI,WAAU;YAAd,CACE,kBAAC,GAAD;GACE,eAAY;GACZ,WAAW,EACT,2BACA,IAAU,2BAA2B,0BACvC;EACD,CAAA,GACD,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,IACG,EAAG,0CAA0C,QAAQ,IACrD,EAAG,4CAA4C,UAAU,GAAG,GAC5D;;MACL,EAAG,iCAAiC,EAAM,QAAQ,EAAY,EAAM,SAAS,EAAM,IAAI;MACvF,EAAM,QAAQ,QACb,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,gCAAgC,iBAAiB,EAAE,MAAM,EAAM,KAAK,CAAC;MACrE,CAAA;KAEP;;IACH,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,oCAAoC,EAAM,QAAQ,EAAM,OAAO;IAClE,CAAA;IACF,EAAM,WACL,kBAAC,QAAD;KAAM,WAAU;eACb,EAAM;IACH,CAAA;IAEP,IACC,kBAAC,KAAD;KACE,MAAM;KACN,QAAO;KACP,KAAI;KACJ,WAAU;eAJZ;MAMG;MACD,kBAAC,GAAD;OAAc,eAAY;OAAO,WAAU;MAAW,CAAA;MACtD,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,uCAAuC,sBAAsB;MAC7D,CAAA;KACL;SAEH,kBAAC,QAAD;KAAM,WAAU;eAA0C;IAAgB,CAAA;GAEzE;IACH;;AAER,GAEa,KAGP,EAAE,WAAQ,mBACd,kBAAC,MAAD;CAAI,WAAW,EAAG,+BAA+B,CAAS;WACvD,EAAO,KAAK,GAAO,MAClB,kBAAC,GAAD,EAAqE,SAAQ,GAA9D,GAAG,EAAM,KAAK,GAAG,EAAM,QAAQ,IAAI,GAAG,GAAwB,CAC9E;AACC,CAAA,GAIO,UAEG;CACd,IAAM,IAAK,EAAM;CACjB,QAAQ,MAAW;EACjB,IAAI,EAAO,eAAe,KAAK,EAAO,iBAAiB,GACrD,OAAO,EAAG,oCAAoC,+BAA+B;EAE/E,IAAM,IAAkB,CAAC;EAiBzB,OAhBI,EAAO,aAAa,KACtB,EAAM,KACJ,EAAO,eAAe,IAClB,EAAG,oCAAoC,SAAS,IAChD,EAAG,kCAAkC,oBAAoB,EAAE,OAAO,EAAO,WAAW,CAAC,CAC3F,GAEE,EAAO,eAAe,KACxB,EAAM,KACJ,EAAO,iBAAiB,IACpB,EAAG,sCAAsC,WAAW,IACpD,EAAG,oCAAoC,sBAAsB,EAC3D,OAAO,EAAO,aAChB,CAAC,CACP,GAEK,EAAM,KAAK,KAAK;CACzB;AACF,GAIM,IAAe;CACnB,UAAU;CACV,OAAO;CACP,SAAS;CACT,OAAO;AACT,GAEM,IAAgD;CACpD,UAAU;CACV,OAAO;CACP,SAAS;CACT,OAAO;AACT,GAEM,KAAc,GAAgB,MAC9B,KAAS,CAAC,IAAe,aACzB,EAAO,aAAa,IAAU,UAC9B,EAAO,eAAe,IAAU,YAC7B,SAcI,KAAuD,EAClE,UACA,QAAK,uBACL,mBACI;CACJ,IAAM,IAAK,EAAM,GACX,IAAY,EAAwB,GACpC,IAAY,EAAkB,GAAO,CAAW,GAChD,IAAQ,EAAgC,CAAS,GAEjD,IAAY,EAAoB,CAAK,GACrC,IAAS,IAAY,EAAM,OAAO,KAAA,GAIlC,IACJ,KAAK,UAAU,CAAK,MAAM,KAAK,UAAU,CAAS,KAClD,EAAM,cACN,EAAM,mBACF,IAAS,KAAa,CAAC,KAAS,EAAM,SACtC,IAAO,CAAC,GACR,IAAY,GAAG,EAAG,WAEpB;CACJ,AAaE,IAbE,IACO,EACP,gCACA,kEACF,IACS,IACA,EACP,uCACA,+CACF,IACS,KAAS,CAAC,IACV,EAAG,oCAAoC,yBAAyB,IAEhE,EAAU,CAAM;CAI3B,IAAM,IAAe,KAAQ,KAAU,CAAC,KAAU,CAAC,IAAU,KAAK,GAE5D,IAAO,EAAW,GAAO,CAAM,GAC/B,IAAa,EAAa;CAEhC,OACE,kBAAC,WAAD;EACE,mBAAiB;EACjB,eAAY;EACZ,WAAW,EAAG,4DAA4D,CAAS;YAHrF;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,IAAI;MAAW,WAAU;gBAC1B,EAAG,iCAAiC,qBAAqB;KACxD,CAAA;KACJ,kBAAC,KAAD;MACE,eAAY;MACZ,eAAY;MACZ,WAAU;gBAHZ,CAKG,CAAC,KAAQ,CAAC,KACT,kBAAC,GAAD,EAAY,WAAW,EAAG,eAAe,EAAkB,EAAK,EAAI,CAAA,GAErE,CACA;;KACH,kBAAC,KAAD;MAAG,MAAK;MAAS,aAAU;MAAS,WAAU;gBAC3C;KACA,CAAA;IACA;;GACJ,KAAU,CAAC,KAAU,EAAO,OAAO,SAAS,KAC3C,kBAAC,GAAD;IAAwB,QAAQ,EAAO;IAAQ,WAAU;GAAQ,CAAA;GAElE,GAAQ,aACP,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,qCACA,0EACF;GACC,CAAA;EAEE;;AAEb"}
@@ -21,4 +21,5 @@ export { TutorPanel } from './TutorPanel';
21
21
  export { BuiTags } from './BuiTags';
22
22
  export { BuiPermissionButton, type BuiPermissionButtonProps } from './BuiPermissionButton';
23
23
  export { OfflineLessonControl } from './OfflineLessonControl';
24
+ export { AccessibilityChecker, AccessibilityIssueList, useAccessibilitySummary, } from './AccessibilityChecker';
24
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC"}
@@ -17,3 +17,4 @@ import "./TutorPanel.js";
17
17
  import "./BuiTags.js";
18
18
  import "./BuiPermissionButton.js";
19
19
  import "./OfflineLessonControl.js";
20
+ import "./AccessibilityChecker.js";
@@ -0,0 +1,4 @@
1
+ import { BuiCheckContentAccessibilityInput } from '../types';
2
+ /** True when a draft has anything worth checking. */
3
+ export declare const hasCheckableContent: (input: BuiCheckContentAccessibilityInput) => boolean;
4
+ //# sourceMappingURL=accessibilityDraft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibilityDraft.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/accessibilityDraft.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,UAAU,CAAC;AAElE,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,GAAI,OAAO,iCAAiC,KAAG,OAM5E,CAAC"}
@@ -0,0 +1,6 @@
1
+ //#region src/buildmyiq/hooks/accessibilityDraft.ts
2
+ var e = (e) => !!(e.content?.trim() || e.explanation?.trim() || (e.options?.some((e) => e.label.trim().length > 0) ?? !1) || e.hasMedia && (e.lessonType === "VIDEO" || e.lessonType === "LIVE_SESSION"));
3
+ //#endregion
4
+ export { e as hasCheckableContent };
5
+
6
+ //# sourceMappingURL=accessibilityDraft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibilityDraft.js","names":[],"sources":["../../../src/buildmyiq/hooks/accessibilityDraft.ts"],"sourcesContent":["/**\n * Pure helpers for the authoring-time accessibility checker (BOFF-4423).\n * @module buildmyiq/hooks/accessibilityDraft\n */\n\nimport type { BuiCheckContentAccessibilityInput } from '../types';\n\n/** True when a draft has anything worth checking. */\nexport const hasCheckableContent = (input: BuiCheckContentAccessibilityInput): boolean =>\n Boolean(\n input.content?.trim() ||\n input.explanation?.trim() ||\n (input.options?.some((o) => o.label.trim().length > 0) ?? false) ||\n (input.hasMedia && (input.lessonType === 'VIDEO' || input.lessonType === 'LIVE_SESSION'))\n );\n"],"mappings":";AAQA,IAAa,KAAuB,MAClC,GACE,EAAM,SAAS,KAAK,KACpB,EAAM,aAAa,KAAK,MACvB,EAAM,SAAS,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,SAAS,CAAC,KAAK,OACzD,EAAM,aAAa,EAAM,eAAe,WAAW,EAAM,eAAe"}
@@ -11,4 +11,5 @@ export * from './useBuiPermissions';
11
11
  export * from './useBuiProctoring';
12
12
  export * from './proctoringState';
13
13
  export * from './useBuiCredentials';
14
+ export * from './useBuiAccessibility';
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
@@ -7,3 +7,5 @@ import "./useBuiPermissions.js";
7
7
  import "./useBuiProctoring.js";
8
8
  import "./proctoringState.js";
9
9
  import "./useBuiCredentials.js";
10
+ import "./accessibilityDraft.js";
11
+ import "./useBuiAccessibility.js";
@@ -0,0 +1,21 @@
1
+ import { BuiAccessibilityReport, BuiCheckContentAccessibilityInput, BuiCourseAccessibilityReport } from '../types';
2
+ import { hasCheckableContent } from './accessibilityDraft';
3
+ export { hasCheckableContent };
4
+ export declare const accessibilityKeys: {
5
+ draft: (input: BuiCheckContentAccessibilityInput) => readonly ["buildmyiq", "accessibility", "draft", BuiCheckContentAccessibilityInput];
6
+ course: (courseId: string) => readonly ["buildmyiq", "accessibility", "course", string];
7
+ };
8
+ /**
9
+ * Returns a JSON-serialisable `value` once it has stopped changing for
10
+ * `delayMs`. Changes are detected structurally, so callers may pass a fresh
11
+ * object literal on every render without restarting the timer or looping.
12
+ */
13
+ export declare function useDebouncedValue<T>(value: T, delayMs: number): T;
14
+ /**
15
+ * Checks a draft (debounced by the caller). Disabled while the draft is empty,
16
+ * so an untouched form makes no request.
17
+ */
18
+ export declare const useBuiCheckContentAccessibility: (input: BuiCheckContentAccessibilityInput) => import('@tanstack/react-query').UseQueryResult<NoInfer<BuiAccessibilityReport>, Error>;
19
+ /** Checks every saved piece of authored content in one course. */
20
+ export declare const useBuiCourseAccessibilityReport: (courseId: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<BuiCourseAccessibilityReport>, Error>;
21
+ //# sourceMappingURL=useBuiAccessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuiAccessibility.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiAccessibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EACV,sBAAsB,EACtB,iCAAiC,EACjC,4BAA4B,EAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAU/B,eAAO,MAAM,iBAAiB;mBACb,iCAAiC;uBAE7B,MAAM;CAC1B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CASjE;AAED;;;GAGG;AACH,eAAO,MAAM,+BAA+B,GAAI,OAAO,iCAAiC,2FA0BvF,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,+BAA+B,GAAI,UAAU,MAAM,GAAG,SAAS,iGA2B3E,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
+ import "../context/index.js";
3
+ import { wspaceFetch as t } from "./useBuiFiles.js";
4
+ import { hasCheckableContent as n } from "./accessibilityDraft.js";
5
+ import { useEffect as r, useMemo as i, useState as a } from "react";
6
+ import { useQuery as o } from "@tanstack/react-query";
7
+ //#region src/buildmyiq/hooks/useBuiAccessibility.ts
8
+ var s = "\n passed\n errorCount\n warningCount\n truncated\n issues { rule severity wcag wcagLevel message excerpt line }\n", c = {
9
+ draft: (e) => [
10
+ "buildmyiq",
11
+ "accessibility",
12
+ "draft",
13
+ e
14
+ ],
15
+ course: (e) => [
16
+ "buildmyiq",
17
+ "accessibility",
18
+ "course",
19
+ e
20
+ ]
21
+ };
22
+ function l(e, t) {
23
+ let n = JSON.stringify(e) ?? "null", [o, s] = a(n);
24
+ return r(() => {
25
+ let e = setTimeout(() => s(n), t);
26
+ return () => clearTimeout(e);
27
+ }, [n, t]), i(() => JSON.parse(o), [o]);
28
+ }
29
+ var u = (r) => {
30
+ let { apiGatewayUrl: i } = e();
31
+ return o({
32
+ queryKey: c.draft(r),
33
+ enabled: n(r),
34
+ placeholderData: (e) => e,
35
+ gcTime: 3e4,
36
+ retry: 1,
37
+ staleTime: Infinity,
38
+ refetchOnWindowFocus: !1,
39
+ refetchOnReconnect: !1,
40
+ queryFn: async () => (await t(`query BuiCheckContentAccessibility($input: BuiCheckContentAccessibilityInput!) {
41
+ buiCheckContentAccessibility(input: $input) { ${s} }
42
+ }`, { input: r }, i)).buiCheckContentAccessibility
43
+ });
44
+ }, d = (n) => {
45
+ let { apiGatewayUrl: r } = e();
46
+ return o({
47
+ queryKey: n ? c.course(n) : [
48
+ "buildmyiq",
49
+ "accessibility",
50
+ "course",
51
+ "nil"
52
+ ],
53
+ enabled: !!n,
54
+ staleTime: 0,
55
+ refetchOnWindowFocus: !1,
56
+ queryFn: async () => (await t(`query BuiCourseAccessibilityReport($courseId: ID!) {
57
+ buiCourseAccessibilityReport(courseId: $courseId) {
58
+ courseId passed errorCount warningCount itemsChecked itemsWithIssues truncated generatedAt
59
+ items { targetType targetId parentId title report { ${s} } }
60
+ }
61
+ }`, { courseId: n }, r)).buiCourseAccessibilityReport
62
+ });
63
+ };
64
+ //#endregion
65
+ export { c as accessibilityKeys, u as useBuiCheckContentAccessibility, d as useBuiCourseAccessibilityReport, l as useDebouncedValue };
66
+
67
+ //# sourceMappingURL=useBuiAccessibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuiAccessibility.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiAccessibility.ts"],"sourcesContent":["/**\n * Buildmyiq — authoring-time accessibility checker hooks (BOFF-4423).\n * @module buildmyiq/hooks/useBuiAccessibility\n *\n * Wraps the two read-only `wspace-buildmyiq-svc` queries:\n * - `buiCheckContentAccessibility` — checks a draft while the author types.\n * - `buiCourseAccessibilityReport` — checks every saved item in a course.\n *\n * The rules themselves live server-side (one source of truth shared with the\n * CLI/MCP surfaces); this file only transports them.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport type {\n BuiAccessibilityReport,\n BuiCheckContentAccessibilityInput,\n BuiCourseAccessibilityReport,\n} from '../types';\nimport { hasCheckableContent } from './accessibilityDraft';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport { hasCheckableContent };\n\nconst REPORT_FIELDS = `\n passed\n errorCount\n warningCount\n truncated\n issues { rule severity wcag wcagLevel message excerpt line }\n`;\n\nexport const accessibilityKeys = {\n draft: (input: BuiCheckContentAccessibilityInput) =>\n ['buildmyiq', 'accessibility', 'draft', input] as const,\n course: (courseId: string) => ['buildmyiq', 'accessibility', 'course', courseId] as const,\n};\n\n/**\n * Returns a JSON-serialisable `value` once it has stopped changing for\n * `delayMs`. Changes are detected structurally, so callers may pass a fresh\n * object literal on every render without restarting the timer or looping.\n */\nexport function useDebouncedValue<T>(value: T, delayMs: number): T {\n const key = JSON.stringify(value) ?? 'null';\n const [debouncedKey, setDebouncedKey] = useState(key);\n useEffect(() => {\n const handle = setTimeout(() => setDebouncedKey(key), delayMs);\n return () => clearTimeout(handle);\n }, [key, delayMs]);\n // The key round-trips the same JSON-serialisable shape the caller passed in.\n return useMemo(() => JSON.parse(debouncedKey) as T, [debouncedKey]);\n}\n\n/**\n * Checks a draft (debounced by the caller). Disabled while the draft is empty,\n * so an untouched form makes no request.\n */\nexport const useBuiCheckContentAccessibility = (input: BuiCheckContentAccessibilityInput) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: accessibilityKeys.draft(input),\n enabled: hasCheckableContent(input),\n placeholderData: (previous) => previous,\n // Every debounced draft is its own key: keep old drafts briefly, and fail\n // fast so the author sees \"unavailable\" instead of a long retry spinner.\n gcTime: 30_000,\n retry: 1,\n // A draft's result never changes for the same input: do not refetch it on\n // focus or reconnect (that would flash \"Checking…\" and re-announce).\n staleTime: Number.POSITIVE_INFINITY,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n queryFn: async () => {\n const data = await wspaceFetch<{ buiCheckContentAccessibility: BuiAccessibilityReport }>(\n `query BuiCheckContentAccessibility($input: BuiCheckContentAccessibilityInput!) {\n buiCheckContentAccessibility(input: $input) { ${REPORT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return data.buiCheckContentAccessibility;\n },\n });\n};\n\n/** Checks every saved piece of authored content in one course. */\nexport const useBuiCourseAccessibilityReport = (courseId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: courseId\n ? accessibilityKeys.course(courseId)\n : ['buildmyiq', 'accessibility', 'course', 'nil'],\n enabled: Boolean(courseId),\n // Fresh on every visit to the tab (authors fix content elsewhere), but no\n // background refetch on focus: the tab has an explicit \"Check again\".\n staleTime: 0,\n refetchOnWindowFocus: false,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiCourseAccessibilityReport: BuiCourseAccessibilityReport;\n }>(\n `query BuiCourseAccessibilityReport($courseId: ID!) {\n buiCourseAccessibilityReport(courseId: $courseId) {\n courseId passed errorCount warningCount itemsChecked itemsWithIssues truncated generatedAt\n items { targetType targetId parentId title report { ${REPORT_FIELDS} } }\n }\n }`,\n { courseId },\n apiGatewayUrl\n );\n return data.buiCourseAccessibilityReport;\n },\n });\n};\n"],"mappings":";;;;;;;AA0BA,IAAM,IAAgB,2HAQT,IAAoB;CAC/B,QAAQ,MACN;EAAC;EAAa;EAAiB;EAAS;CAAK;CAC/C,SAAS,MAAqB;EAAC;EAAa;EAAiB;EAAU;CAAQ;AACjF;AAOA,SAAgB,EAAqB,GAAU,GAAoB;CACjE,IAAM,IAAM,KAAK,UAAU,CAAK,KAAK,QAC/B,CAAC,GAAc,KAAmB,EAAS,CAAG;CAMpD,OALA,QAAgB;EACd,IAAM,IAAS,iBAAiB,EAAgB,CAAG,GAAG,CAAO;EAC7D,aAAa,aAAa,CAAM;CAClC,GAAG,CAAC,GAAK,CAAO,CAAC,GAEV,QAAc,KAAK,MAAM,CAAY,GAAQ,CAAC,CAAY,CAAC;AACpE;AAMA,IAAa,KAAmC,MAA6C;CAC3F,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAkB,MAAM,CAAK;EACvC,SAAS,EAAoB,CAAK;EAClC,kBAAkB,MAAa;EAG/B,QAAQ;EACR,OAAO;EAGP,WAAW;EACX,sBAAsB;EACtB,oBAAoB;EACpB,SAAS,aAQA,MAPY,EACjB;0DACkD,EAAc;YAEhE,EAAE,SAAM,GACR,CACF,GACY;CAEhB,CAAC;AACH,GAGa,KAAmC,MAAiC;CAC/E,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,IACN,EAAkB,OAAO,CAAQ,IACjC;GAAC;GAAa;GAAiB;GAAU;EAAK;EAClD,SAAS,EAAQ;EAGjB,WAAW;EACX,sBAAsB;EACtB,SAAS,aAaA,MAZY,EAGjB;;;kEAG0D,EAAc;;YAGxE,EAAE,YAAS,GACX,CACF,GACY;CAEhB,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"AssessmentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAwHnE,eAAO,MAAM,oBAAoB,EAAE,EAmgBlC,CAAC"}
1
+ {"version":3,"file":"AssessmentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAyHnE,eAAO,MAAM,oBAAoB,EAAE,EAkhBlC,CAAC"}