@burdenoff/microfe-buildmyiq 2026.1030.7 → 2026.1031.2

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/DataTable.d.ts +22 -1
  2. package/dist/buildmyiq/components/DataTable.d.ts.map +1 -1
  3. package/dist/buildmyiq/components/DataTable.js +66 -42
  4. package/dist/buildmyiq/components/DataTable.js.map +1 -1
  5. package/dist/buildmyiq/components/MiniBar.d.ts +7 -1
  6. package/dist/buildmyiq/components/MiniBar.d.ts.map +1 -1
  7. package/dist/buildmyiq/components/MiniBar.js +8 -7
  8. package/dist/buildmyiq/components/MiniBar.js.map +1 -1
  9. package/dist/buildmyiq/hooks/invalidateAfterWrite.d.ts +3 -0
  10. package/dist/buildmyiq/hooks/invalidateAfterWrite.d.ts.map +1 -0
  11. package/dist/buildmyiq/hooks/invalidateAfterWrite.js +11 -0
  12. package/dist/buildmyiq/hooks/invalidateAfterWrite.js.map +1 -0
  13. package/dist/buildmyiq/hooks/useBuiComments.d.ts.map +1 -1
  14. package/dist/buildmyiq/hooks/useBuiComments.js +24 -23
  15. package/dist/buildmyiq/hooks/useBuiComments.js.map +1 -1
  16. package/dist/buildmyiq/hooks/useBuiProctoring.d.ts.map +1 -1
  17. package/dist/buildmyiq/hooks/useBuiProctoring.js +67 -66
  18. package/dist/buildmyiq/hooks/useBuiProctoring.js.map +1 -1
  19. package/dist/buildmyiq/hooks/useBuiTags.d.ts.map +1 -1
  20. package/dist/buildmyiq/hooks/useBuiTags.js +52 -51
  21. package/dist/buildmyiq/hooks/useBuiTags.js.map +1 -1
  22. package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +1 -1
  23. package/dist/buildmyiq/hooks/useBuildmyiqApi.js +487 -486
  24. package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
  25. package/dist/buildmyiq/pages/DashboardPage.js +1 -0
  26. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  27. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
  28. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +4 -1
  29. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  30. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
  31. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +2 -1
  32. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  33. package/dist/buildmyiq/pages/insights/InsightsPage.d.ts.map +1 -1
  34. package/dist/buildmyiq/pages/insights/InsightsPage.js +3 -0
  35. package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
  36. package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
  37. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +1 -0
  38. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
  39. package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +1 -1
  40. package/dist/buildmyiq/pages/learning/MyLearningPage.js +1 -0
  41. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  42. package/dist/buildmyiq/pages/onboarding/GettingStartedChecklist.d.ts.map +1 -1
  43. package/dist/buildmyiq/pages/onboarding/GettingStartedChecklist.js +1 -0
  44. package/dist/buildmyiq/pages/onboarding/GettingStartedChecklist.js.map +1 -1
  45. package/package.json +1 -1
@@ -21,7 +21,28 @@ interface DataTableProps<T> {
21
21
  key: string;
22
22
  dir: 'asc' | 'desc';
23
23
  };
24
+ /**
25
+ * Accessible name of the scroll region, announced when the table is wider
26
+ * than its container and keyboard users tab into it (e.g. "Cohorts").
27
+ */
28
+ label?: string;
24
29
  }
25
- export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, className, defaultSort, }: DataTableProps<T>): import("react").JSX.Element;
30
+ /**
31
+ * Props for the table's horizontal scroll container. When the table is wider
32
+ * than the viewport (phones), the container becomes a named, focusable region
33
+ * so keyboard users can scroll it with the arrow keys (WCAG 2.1.1; axe
34
+ * `scrollable-region-focusable`). When nothing overflows it stays a plain div
35
+ * and adds no extra Tab stop.
36
+ */
37
+ export declare function scrollRegionProps(overflows: boolean, label: string): {
38
+ readonly role: "region";
39
+ readonly 'aria-label': string;
40
+ readonly tabIndex: 0;
41
+ } | {
42
+ readonly role?: undefined;
43
+ readonly 'aria-label'?: undefined;
44
+ readonly tabIndex?: undefined;
45
+ };
46
+ export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, className, defaultSort, label, }: DataTableProps<T>): import("react").JSX.Element;
26
47
  export {};
27
48
  //# sourceMappingURL=DataTable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/DataTable.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAK1D,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;CACpD;AAQD,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,SAAS,EACT,WAAW,GACZ,EAAE,cAAc,CAAC,CAAC,CAAC,+BAqGnB"}
1
+ {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/DataTable.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAwC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM7E,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;IACnD;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;;;;;;;;EAIlE;AAQD,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,SAAS,EACT,WAAW,EACX,KAAK,GACN,EAAE,cAAc,CAAC,CAAC,CAAC,+BA2GnB"}
@@ -1,30 +1,52 @@
1
- import { cn as e } from "../utils/cn.js";
2
- import { useMemo as t, useState as n } from "react";
3
- import { ChevronDown as r, ChevronUp as i, ChevronsUpDown as a } from "lucide-react";
4
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
- import { GlassCard as c } from "@burdenoff/fe-libs/ui";
1
+ import { useTr as e } from "../shared/hooks/useTr.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import { useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
4
+ import { ChevronDown as o, ChevronUp as s, ChevronsUpDown as c } from "lucide-react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
+ import { GlassCard as d } from "@burdenoff/fe-libs/ui";
6
7
  //#region src/buildmyiq/components/DataTable.tsx
7
- var l = {
8
+ function f() {
9
+ let e = i(null), [t, r] = a(!1);
10
+ return n(() => {
11
+ let t = e.current;
12
+ if (!t) return;
13
+ let n = () => r(t.scrollWidth > t.clientWidth + 1);
14
+ if (n(), typeof ResizeObserver > "u") return;
15
+ let i = new ResizeObserver(n);
16
+ return i.observe(t), t.firstElementChild && i.observe(t.firstElementChild), () => i.disconnect();
17
+ }, []), {
18
+ ref: e,
19
+ overflows: t
20
+ };
21
+ }
22
+ function p(e, t) {
23
+ return e ? {
24
+ role: "region",
25
+ "aria-label": t,
26
+ tabIndex: 0
27
+ } : {};
28
+ }
29
+ var m = {
8
30
  left: "text-start",
9
31
  right: "text-end",
10
32
  center: "text-center"
11
33
  };
12
- function u({ columns: u, rows: d, rowKey: f, onRowClick: p, className: m, defaultSort: h }) {
13
- let [g, _] = n(h ?? null), v = t(() => {
14
- if (!g) return d;
15
- let e = u.find((e) => e.key === g.key);
16
- if (!e?.sortValue) return d;
17
- let t = g.dir === "asc" ? 1 : -1;
18
- return [...d].sort((n, r) => {
34
+ function h({ columns: n, rows: i, rowKey: h, onRowClick: g, className: _, defaultSort: v, label: y }) {
35
+ let b = e(), { ref: x, overflows: S } = f(), [C, w] = a(v ?? null), T = r(() => {
36
+ if (!C) return i;
37
+ let e = n.find((e) => e.key === C.key);
38
+ if (!e?.sortValue) return i;
39
+ let t = C.dir === "asc" ? 1 : -1;
40
+ return [...i].sort((n, r) => {
19
41
  let i = e.sortValue(n), a = e.sortValue(r);
20
42
  return i < a ? -1 * t : i > a ? 1 * t : 0;
21
43
  });
22
44
  }, [
23
- d,
24
- g,
25
- u
26
- ]), y = (e) => {
27
- _((t) => t?.key === e ? {
45
+ i,
46
+ C,
47
+ n
48
+ ]), E = (e) => {
49
+ w((t) => t?.key === e ? {
28
50
  key: e,
29
51
  dir: t.dir === "asc" ? "desc" : "asc"
30
52
  } : {
@@ -32,40 +54,42 @@ function u({ columns: u, rows: d, rowKey: f, onRowClick: p, className: m, defaul
32
54
  dir: "asc"
33
55
  });
34
56
  };
35
- return /* @__PURE__ */ o(c, {
57
+ return /* @__PURE__ */ l(d, {
36
58
  treatment: "plain",
37
- className: e("overflow-hidden", m),
38
- children: /* @__PURE__ */ o("div", {
39
- className: "overflow-x-auto",
40
- children: /* @__PURE__ */ s("table", {
59
+ className: t("overflow-hidden", _),
60
+ children: /* @__PURE__ */ l("div", {
61
+ ref: x,
62
+ className: "overflow-x-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]",
63
+ ...p(S, y ?? b("buildmyiq.common.table", "Table")),
64
+ children: /* @__PURE__ */ u("table", {
41
65
  className: "w-full border-collapse text-sm",
42
- children: [/* @__PURE__ */ o("thead", { children: /* @__PURE__ */ o("tr", {
66
+ children: [/* @__PURE__ */ l("thead", { children: /* @__PURE__ */ l("tr", {
43
67
  className: "border-b border-border-seam",
44
- children: u.map((t) => {
45
- let n = !!t.sortValue, c = g?.key === t.key;
46
- return /* @__PURE__ */ o("th", {
47
- onClick: n ? () => y(t.key) : void 0,
48
- "aria-sort": c ? g?.dir === "asc" ? "ascending" : "descending" : void 0,
49
- className: e("px-4 py-3 font-medium text-text-secondary whitespace-nowrap", l[t.align ?? "left"], t.hideOnMobile && "hidden md:table-cell", n && "cursor-pointer select-none hover:text-text-primary"),
50
- children: /* @__PURE__ */ s("span", {
51
- className: e("inline-flex items-center gap-1", t.align === "right" && "flex-row-reverse"),
52
- children: [t.header, n && (c ? g?.dir === "asc" ? /* @__PURE__ */ o(i, { className: "h-3.5 w-3.5 text-[var(--color-accent-primary)]" }) : /* @__PURE__ */ o(r, { className: "h-3.5 w-3.5 text-[var(--color-accent-primary)]" }) : /* @__PURE__ */ o(a, { className: "h-3.5 w-3.5 opacity-40" }))]
68
+ children: n.map((e) => {
69
+ let n = !!e.sortValue, r = C?.key === e.key;
70
+ return /* @__PURE__ */ l("th", {
71
+ onClick: n ? () => E(e.key) : void 0,
72
+ "aria-sort": r ? C?.dir === "asc" ? "ascending" : "descending" : void 0,
73
+ className: t("px-4 py-3 font-medium text-text-secondary whitespace-nowrap", m[e.align ?? "left"], e.hideOnMobile && "hidden md:table-cell", n && "cursor-pointer select-none hover:text-text-primary"),
74
+ children: /* @__PURE__ */ u("span", {
75
+ className: t("inline-flex items-center gap-1", e.align === "right" && "flex-row-reverse"),
76
+ children: [e.header, n && (r ? C?.dir === "asc" ? /* @__PURE__ */ l(s, { className: "h-3.5 w-3.5 text-[var(--color-accent-primary)]" }) : /* @__PURE__ */ l(o, { className: "h-3.5 w-3.5 text-[var(--color-accent-primary)]" }) : /* @__PURE__ */ l(c, { className: "h-3.5 w-3.5 opacity-40" }))]
53
77
  })
54
- }, t.key);
78
+ }, e.key);
55
79
  })
56
- }) }), /* @__PURE__ */ o("tbody", { children: v.map((t) => /* @__PURE__ */ o("tr", {
57
- onClick: p ? () => p(t) : void 0,
58
- className: e("group border-b border-border-seam/60 last:border-0", p && "cursor-pointer transition-colors hover:bg-bg-sunken/60"),
59
- children: u.map((n) => /* @__PURE__ */ o("td", {
60
- className: e("px-4 py-3 text-text-primary align-middle", l[n.align ?? "left"], n.hideOnMobile && "hidden md:table-cell", n.className),
61
- children: n.render(t)
80
+ }) }), /* @__PURE__ */ l("tbody", { children: T.map((e) => /* @__PURE__ */ l("tr", {
81
+ onClick: g ? () => g(e) : void 0,
82
+ className: t("group border-b border-border-seam/60 last:border-0", g && "cursor-pointer transition-colors hover:bg-bg-sunken/60"),
83
+ children: n.map((n) => /* @__PURE__ */ l("td", {
84
+ className: t("px-4 py-3 text-text-primary align-middle", m[n.align ?? "left"], n.hideOnMobile && "hidden md:table-cell", n.className),
85
+ children: n.render(e)
62
86
  }, n.key))
63
- }, f(t))) })]
87
+ }, h(e))) })]
64
88
  })
65
89
  })
66
90
  });
67
91
  }
68
92
  //#endregion
69
- export { u as DataTable };
93
+ export { h as DataTable };
70
94
 
71
95
  //# sourceMappingURL=DataTable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/buildmyiq/components/DataTable.tsx"],"sourcesContent":["/**\n * DataTable — a lightweight, semantic-token table for list pages.\n * @module buildmyiq/components\n *\n * Deliberately minimal (no external table lib). Renders a responsive,\n * horizontally-scrollable table wrapped in a GlassCard. Columns declare a\n * header, a render fn, optional alignment, and optionally a `sortValue` to make\n * the column click-sortable.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (row: T) => ReactNode;\n align?: 'left' | 'right' | 'center';\n className?: string;\n /** Hide below the `md` breakpoint for a mobile-friendly layout. */\n hideOnMobile?: boolean;\n /** Provide to make this column click-sortable (returns a comparable value). */\n sortValue?: (row: T) => string | number;\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n onRowClick?: (row: T) => void;\n className?: string;\n /** Default sort: a column key + direction. */\n defaultSort?: { key: string; dir: 'asc' | 'desc' };\n}\n\nconst alignClass = {\n left: 'text-start',\n right: 'text-end',\n center: 'text-center',\n} as const;\n\nexport function DataTable<T>({\n columns,\n rows,\n rowKey,\n onRowClick,\n className,\n defaultSort,\n}: DataTableProps<T>) {\n const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' } | null>(\n defaultSort ?? null\n );\n\n const sortedRows = useMemo(() => {\n if (!sort) return rows;\n const col = columns.find((c) => c.key === sort.key);\n if (!col?.sortValue) return rows;\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const va = col.sortValue!(a);\n const vb = col.sortValue!(b);\n if (va < vb) return -1 * dir;\n if (va > vb) return 1 * dir;\n return 0;\n });\n }, [rows, sort, columns]);\n\n const toggleSort = (key: string) => {\n setSort((prev) =>\n prev?.key === key ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' }\n );\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('overflow-hidden', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam\">\n {columns.map((col) => {\n const sortable = Boolean(col.sortValue);\n const active = sort?.key === col.key;\n return (\n <th\n key={col.key}\n onClick={sortable ? () => toggleSort(col.key) : undefined}\n aria-sort={\n active ? (sort?.dir === 'asc' ? 'ascending' : 'descending') : undefined\n }\n className={cn(\n 'px-4 py-3 font-medium text-text-secondary whitespace-nowrap',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n sortable && 'cursor-pointer select-none hover:text-text-primary'\n )}\n >\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'flex-row-reverse'\n )}\n >\n {col.header}\n {sortable &&\n (active ? (\n sort?.dir === 'asc' ? (\n <ChevronUp className=\"h-3.5 w-3.5 text-[var(--color-accent-primary)]\" />\n ) : (\n <ChevronDown className=\"h-3.5 w-3.5 text-[var(--color-accent-primary)]\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3.5 w-3.5 opacity-40\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((row) => (\n <tr\n key={rowKey(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={cn(\n 'group border-b border-border-seam/60 last:border-0',\n onRowClick && 'cursor-pointer transition-colors hover:bg-bg-sunken/60'\n )}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-3 text-text-primary align-middle',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n col.className\n )}\n >\n {col.render(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;AAqCA,IAAM,IAAa;CACjB,MAAM;CACN,OAAO;CACP,QAAQ;AACV;AAEA,SAAgB,EAAa,EAC3B,YACA,SACA,WACA,eACA,cACA,kBACoB;CACpB,IAAM,CAAC,GAAM,KAAW,EACtB,KAAe,IACjB,GAEM,IAAa,QAAc;EAC/B,IAAI,CAAC,GAAM,OAAO;EAClB,IAAM,IAAM,EAAQ,MAAM,MAAM,EAAE,QAAQ,EAAK,GAAG;EAClD,IAAI,CAAC,GAAK,WAAW,OAAO;EAC5B,IAAM,IAAM,EAAK,QAAQ,QAAQ,IAAI;EACrC,OAAO,CAAC,GAAG,CAAI,EAAE,MAAM,GAAG,MAAM;GAC9B,IAAM,IAAK,EAAI,UAAW,CAAC,GACrB,IAAK,EAAI,UAAW,CAAC;GAG3B,OAFI,IAAK,IAAW,KAAK,IACrB,IAAK,IAAW,IAAI,IACjB;EACT,CAAC;CACH,GAAG;EAAC;EAAM;EAAM;CAAO,CAAC,GAElB,KAAc,MAAgB;EAClC,GAAS,MACP,GAAM,QAAQ,IAAM;GAAE;GAAK,KAAK,EAAK,QAAQ,QAAQ,SAAS;EAAM,IAAI;GAAE;GAAK,KAAK;EAAM,CAC5F;CACF;CAEA,OACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAW,EAAG,mBAAmB,CAAS;YACrE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAQ,KAAK,MAAQ;MACpB,IAAM,IAAW,EAAQ,EAAI,WACvB,IAAS,GAAM,QAAQ,EAAI;MACjC,OACE,kBAAC,MAAD;OAEE,SAAS,UAAiB,EAAW,EAAI,GAAG,IAAI,KAAA;OAChD,aACE,IAAU,GAAM,QAAQ,QAAQ,cAAc,eAAgB,KAAA;OAEhE,WAAW,EACT,+DACA,EAAW,EAAI,SAAS,SACxB,EAAI,gBAAgB,wBACpB,KAAY,oDACd;iBAEA,kBAAC,QAAD;QACE,WAAW,EACT,kCACA,EAAI,UAAU,WAAW,kBAC3B;kBAJF,CAMG,EAAI,QACJ,MACE,IACC,GAAM,QAAQ,QACZ,kBAAC,GAAD,EAAW,WAAU,iDAAkD,CAAA,IAEvE,kBAAC,GAAD,EAAa,WAAU,iDAAkD,CAAA,IAG3E,kBAAC,GAAD,EAAgB,WAAU,yBAA0B,CAAA,EAEpD;;MACJ,GA9BG,EAAI,GA8BP;KAER,CAAC;IACC,CAAA,EACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAW,KAAK,MACf,kBAAC,MAAD;KAEE,SAAS,UAAmB,EAAW,CAAG,IAAI,KAAA;KAC9C,WAAW,EACT,sDACA,KAAc,wDAChB;eAEC,EAAQ,KAAK,MACZ,kBAAC,MAAD;MAEE,WAAW,EACT,4CACA,EAAW,EAAI,SAAS,SACxB,EAAI,gBAAgB,wBACpB,EAAI,SACN;gBAEC,EAAI,OAAO,CAAG;KACb,GATG,EAAI,GASP,CACL;IACC,GApBG,EAAO,CAAG,CAoBb,CACL,EACI,CAAA,CACF;;EACJ,CAAA;CACI,CAAA;AAEf"}
1
+ {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/buildmyiq/components/DataTable.tsx"],"sourcesContent":["/**\n * DataTable — a lightweight, semantic-token table for list pages.\n * @module buildmyiq/components\n *\n * Deliberately minimal (no external table lib). Renders a responsive,\n * horizontally-scrollable table wrapped in a GlassCard. Columns declare a\n * header, a render fn, optional alignment, and optionally a `sortValue` to make\n * the column click-sortable.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useTr } from '../shared/hooks/useTr';\n\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (row: T) => ReactNode;\n align?: 'left' | 'right' | 'center';\n className?: string;\n /** Hide below the `md` breakpoint for a mobile-friendly layout. */\n hideOnMobile?: boolean;\n /** Provide to make this column click-sortable (returns a comparable value). */\n sortValue?: (row: T) => string | number;\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n onRowClick?: (row: T) => void;\n className?: string;\n /** Default sort: a column key + direction. */\n defaultSort?: { key: string; dir: 'asc' | 'desc' };\n /**\n * Accessible name of the scroll region, announced when the table is wider\n * than its container and keyboard users tab into it (e.g. \"Cohorts\").\n */\n label?: string;\n}\n\n/**\n * Tracks whether an element scrolls horizontally, re-checking whenever it or\n * its content resizes.\n */\nfunction useHorizontalOverflow<E extends HTMLElement>() {\n const ref = useRef<E>(null);\n const [overflows, setOverflows] = useState(false);\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const check = () => setOverflows(el.scrollWidth > el.clientWidth + 1);\n check();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(check);\n observer.observe(el);\n if (el.firstElementChild) observer.observe(el.firstElementChild);\n return () => observer.disconnect();\n }, []);\n return { ref, overflows };\n}\n\n/**\n * Props for the table's horizontal scroll container. When the table is wider\n * than the viewport (phones), the container becomes a named, focusable region\n * so keyboard users can scroll it with the arrow keys (WCAG 2.1.1; axe\n * `scrollable-region-focusable`). When nothing overflows it stays a plain div\n * and adds no extra Tab stop.\n */\nexport function scrollRegionProps(overflows: boolean, label: string) {\n return overflows\n ? ({ role: 'region', 'aria-label': label, tabIndex: 0 } as const)\n : ({} as const);\n}\n\nconst alignClass = {\n left: 'text-start',\n right: 'text-end',\n center: 'text-center',\n} as const;\n\nexport function DataTable<T>({\n columns,\n rows,\n rowKey,\n onRowClick,\n className,\n defaultSort,\n label,\n}: DataTableProps<T>) {\n const tr = useTr();\n const { ref: scrollRef, overflows } = useHorizontalOverflow<HTMLDivElement>();\n const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' } | null>(\n defaultSort ?? null\n );\n\n const sortedRows = useMemo(() => {\n if (!sort) return rows;\n const col = columns.find((c) => c.key === sort.key);\n if (!col?.sortValue) return rows;\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const va = col.sortValue!(a);\n const vb = col.sortValue!(b);\n if (va < vb) return -1 * dir;\n if (va > vb) return 1 * dir;\n return 0;\n });\n }, [rows, sort, columns]);\n\n const toggleSort = (key: string) => {\n setSort((prev) =>\n prev?.key === key ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' }\n );\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('overflow-hidden', className)}>\n <div\n ref={scrollRef}\n className=\"overflow-x-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]\"\n {...scrollRegionProps(overflows, label ?? tr('buildmyiq.common.table', 'Table'))}\n >\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam\">\n {columns.map((col) => {\n const sortable = Boolean(col.sortValue);\n const active = sort?.key === col.key;\n return (\n <th\n key={col.key}\n onClick={sortable ? () => toggleSort(col.key) : undefined}\n aria-sort={\n active ? (sort?.dir === 'asc' ? 'ascending' : 'descending') : undefined\n }\n className={cn(\n 'px-4 py-3 font-medium text-text-secondary whitespace-nowrap',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n sortable && 'cursor-pointer select-none hover:text-text-primary'\n )}\n >\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'flex-row-reverse'\n )}\n >\n {col.header}\n {sortable &&\n (active ? (\n sort?.dir === 'asc' ? (\n <ChevronUp className=\"h-3.5 w-3.5 text-[var(--color-accent-primary)]\" />\n ) : (\n <ChevronDown className=\"h-3.5 w-3.5 text-[var(--color-accent-primary)]\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3.5 w-3.5 opacity-40\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((row) => (\n <tr\n key={rowKey(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={cn(\n 'group border-b border-border-seam/60 last:border-0',\n onRowClick && 'cursor-pointer transition-colors hover:bg-bg-sunken/60'\n )}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-3 text-text-primary align-middle',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n col.className\n )}\n >\n {col.render(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -5,12 +5,18 @@
5
5
  interface MiniBarProps {
6
6
  /** 0–100. */
7
7
  value: number;
8
+ /**
9
+ * Accessible name of the bar, e.g. the course title or the metric it shows.
10
+ * Required: a `progressbar` without a name fails WCAG 4.1.2 (axe
11
+ * `aria-progressbar-name`), and screen readers would only say "progress".
12
+ */
13
+ label: string;
8
14
  className?: string;
9
15
  /** Bar fill token class (defaults to accent). */
10
16
  barClassName?: string;
11
17
  /** Height in px. */
12
18
  height?: number;
13
19
  }
14
- export declare function MiniBar({ value, className, barClassName, height, }: MiniBarProps): import("react").JSX.Element;
20
+ export declare function MiniBar({ value, label, className, barClassName, height, }: MiniBarProps): import("react").JSX.Element;
15
21
  export {};
16
22
  //# sourceMappingURL=MiniBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MiniBar.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/MiniBar.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,UAAU,YAAY;IACpB,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,SAAS,EACT,YAAiD,EACjD,MAAU,GACX,EAAE,YAAY,+BAiBd"}
1
+ {"version":3,"file":"MiniBar.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/MiniBar.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,UAAU,YAAY;IACpB,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,KAAK,EACL,SAAS,EACT,YAAiD,EACjD,MAAU,GACX,EAAE,YAAY,+BAkBd"}
@@ -1,18 +1,19 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/buildmyiq/components/MiniBar.tsx
4
- function n({ value: n, className: r, barClassName: i = "bg-[var(--color-accent-primary)]", height: a = 6 }) {
5
- let o = Math.max(0, Math.min(100, n));
4
+ function n({ value: n, label: r, className: i, barClassName: a = "bg-[var(--color-accent-primary)]", height: o = 6 }) {
5
+ let s = Math.max(0, Math.min(100, n));
6
6
  return /* @__PURE__ */ t("div", {
7
- className: e("w-full overflow-hidden rounded-full bg-bg-sunken", r),
8
- style: { height: a },
7
+ className: e("w-full overflow-hidden rounded-full bg-bg-sunken", i),
8
+ style: { height: o },
9
9
  role: "progressbar",
10
- "aria-valuenow": Math.round(o),
10
+ "aria-label": r,
11
+ "aria-valuenow": Math.round(s),
11
12
  "aria-valuemin": 0,
12
13
  "aria-valuemax": 100,
13
14
  children: /* @__PURE__ */ t("div", {
14
- className: e("h-full rounded-full transition-[width] duration-700 ease-out", i),
15
- style: { width: `${o}%` }
15
+ className: e("h-full rounded-full transition-[width] duration-700 ease-out", a),
16
+ style: { width: `${s}%` }
16
17
  })
17
18
  });
18
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MiniBar.js","names":[],"sources":["../../../src/buildmyiq/components/MiniBar.tsx"],"sourcesContent":["/**\n * MiniBar — a thin inline progress bar (target progress, completion, breach %).\n * @module buildmyiq/components\n */\n\nimport { cn } from '../utils/cn';\n\ninterface MiniBarProps {\n /** 0–100. */\n value: number;\n className?: string;\n /** Bar fill token class (defaults to accent). */\n barClassName?: string;\n /** Height in px. */\n height?: number;\n}\n\nexport function MiniBar({\n value,\n className,\n barClassName = 'bg-[var(--color-accent-primary)]',\n height = 6,\n}: MiniBarProps) {\n const clamped = Math.max(0, Math.min(100, value));\n return (\n <div\n className={cn('w-full overflow-hidden rounded-full bg-bg-sunken', className)}\n style={{ height }}\n role=\"progressbar\"\n aria-valuenow={Math.round(clamped)}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div\n className={cn('h-full rounded-full transition-[width] duration-700 ease-out', barClassName)}\n style={{ width: `${clamped}%` }}\n />\n </div>\n );\n}\n"],"mappings":";;;AAiBA,SAAgB,EAAQ,EACtB,UACA,cACA,kBAAe,oCACf,YAAS,KACM;CACf,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAK,CAAC;CAChD,OACE,kBAAC,OAAD;EACE,WAAW,EAAG,oDAAoD,CAAS;EAC3E,OAAO,EAAE,UAAO;EAChB,MAAK;EACL,iBAAe,KAAK,MAAM,CAAO;EACjC,iBAAe;EACf,iBAAe;YAEf,kBAAC,OAAD;GACE,WAAW,EAAG,gEAAgE,CAAY;GAC1F,OAAO,EAAE,OAAO,GAAG,EAAQ,GAAG;EAC/B,CAAA;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"MiniBar.js","names":[],"sources":["../../../src/buildmyiq/components/MiniBar.tsx"],"sourcesContent":["/**\n * MiniBar — a thin inline progress bar (target progress, completion, breach %).\n * @module buildmyiq/components\n */\n\nimport { cn } from '../utils/cn';\n\ninterface MiniBarProps {\n /** 0–100. */\n value: number;\n /**\n * Accessible name of the bar, e.g. the course title or the metric it shows.\n * Required: a `progressbar` without a name fails WCAG 4.1.2 (axe\n * `aria-progressbar-name`), and screen readers would only say \"progress\".\n */\n label: string;\n className?: string;\n /** Bar fill token class (defaults to accent). */\n barClassName?: string;\n /** Height in px. */\n height?: number;\n}\n\nexport function MiniBar({\n value,\n label,\n className,\n barClassName = 'bg-[var(--color-accent-primary)]',\n height = 6,\n}: MiniBarProps) {\n const clamped = Math.max(0, Math.min(100, value));\n return (\n <div\n className={cn('w-full overflow-hidden rounded-full bg-bg-sunken', className)}\n style={{ height }}\n role=\"progressbar\"\n aria-label={label}\n aria-valuenow={Math.round(clamped)}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div\n className={cn('h-full rounded-full transition-[width] duration-700 ease-out', barClassName)}\n style={{ width: `${clamped}%` }}\n />\n </div>\n );\n}\n"],"mappings":";;;AAuBA,SAAgB,EAAQ,EACtB,UACA,UACA,cACA,kBAAe,oCACf,YAAS,KACM;CACf,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAK,CAAC;CAChD,OACE,kBAAC,OAAD;EACE,WAAW,EAAG,oDAAoD,CAAS;EAC3E,OAAO,EAAE,UAAO;EAChB,MAAK;EACL,cAAY;EACZ,iBAAe,KAAK,MAAM,CAAO;EACjC,iBAAe;EACf,iBAAe;YAEf,kBAAC,OAAD;GACE,WAAW,EAAG,gEAAgE,CAAY;GAC1F,OAAO,EAAE,OAAO,GAAG,EAAQ,GAAG;EAC/B,CAAA;CACE,CAAA;AAET"}
@@ -0,0 +1,3 @@
1
+ import { QueryClient, QueryKey } from '@tanstack/react-query';
2
+ export declare const invalidateAfterWrite: (queryClient: QueryClient, queryKey: QueryKey) => Promise<void>;
3
+ //# sourceMappingURL=invalidateAfterWrite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidateAfterWrite.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/invalidateAfterWrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EACxB,UAAU,QAAQ,KACjB,OAAO,CAAC,IAAI,CAMd,CAAC"}
@@ -0,0 +1,11 @@
1
+ //#region src/buildmyiq/hooks/invalidateAfterWrite.ts
2
+ var e = async (e, t) => {
3
+ await e.cancelQueries({
4
+ queryKey: t,
5
+ predicate: (e) => e.state.fetchStatus === "fetching" && e.state.data === void 0
6
+ }), await e.invalidateQueries({ queryKey: t });
7
+ };
8
+ //#endregion
9
+ export { e as invalidateAfterWrite };
10
+
11
+ //# sourceMappingURL=invalidateAfterWrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidateAfterWrite.js","names":[],"sources":["../../../src/buildmyiq/hooks/invalidateAfterWrite.ts"],"sourcesContent":["/**\n * Refresh the React Query cache after a BuildMyIQ write.\n *\n * `queryClient.invalidateQueries` alone is not enough when a query matching the\n * key is still on its FIRST load. React Query only cancels and restarts an\n * in-flight fetch when the query already holds data; a query with no data yet\n * has the refetch joined onto the request that is already on the wire. That\n * request was sent before the write committed, so it resolves with the\n * pre-write result and no second read follows. In practice: open a course's\n * Curriculum tab and add a module before the empty module list has loaded, and\n * the tab keeps showing \"No modules yet\" even though the module exists.\n *\n * So first cancel any matching query that is still loading without data (React\n * Query reverts it to its pre-fetch state, so nothing errors), then invalidate,\n * which issues a fresh read for every active query under the key.\n */\nimport type { QueryClient, QueryKey } from '@tanstack/react-query';\n\nexport const invalidateAfterWrite = async (\n queryClient: QueryClient,\n queryKey: QueryKey\n): Promise<void> => {\n await queryClient.cancelQueries({\n queryKey,\n predicate: (query) => query.state.fetchStatus === 'fetching' && query.state.data === undefined,\n });\n await queryClient.invalidateQueries({ queryKey });\n};\n"],"mappings":";AAkBA,IAAa,IAAuB,OAClC,GACA,MACkB;CAKlB,AAJA,MAAM,EAAY,cAAc;EAC9B;EACA,YAAY,MAAU,EAAM,MAAM,gBAAgB,cAAc,EAAM,MAAM,SAAS,KAAA;CACvF,CAAC,GACD,MAAM,EAAY,kBAAkB,EAAE,YAAS,CAAC;AAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBuiComments.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,uEAAuE;AACvE,eAAO,MAAM,cAAc,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS;;;UAwC9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM;QAKN,MAAM;0BAmBvE,CAAC"}
1
+ {"version":3,"file":"useBuiComments.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,uEAAuE;AACvE,eAAO,MAAM,cAAc,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS;;;UAwC9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM;QAKN,MAAM;0BAmBvE,CAAC"}
@@ -1,45 +1,46 @@
1
1
  import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
2
  import "../context/index.js";
3
- import { wspaceFetch as t } from "./useBuiFiles.js";
4
- import { useMutation as n, useQuery as r, useQueryClient as i } from "@tanstack/react-query";
3
+ import { invalidateAfterWrite as t } from "./invalidateAfterWrite.js";
4
+ import { wspaceFetch as n } from "./useBuiFiles.js";
5
+ import { useMutation as r, useQuery as i, useQueryClient as a } from "@tanstack/react-query";
5
6
  //#region src/buildmyiq/hooks/useBuiComments.ts
6
- var a = (e, t) => [
7
+ var o = (e, t) => [
7
8
  "buildmyiq",
8
9
  "comments",
9
10
  e,
10
11
  t
11
- ], o = (n, i) => {
12
- let { apiGatewayUrl: o } = e();
13
- return r({
14
- queryKey: a(n, i ?? ""),
15
- enabled: !!i,
12
+ ], s = (t, r) => {
13
+ let { apiGatewayUrl: a } = e();
14
+ return i({
15
+ queryKey: o(t, r ?? ""),
16
+ enabled: !!r,
16
17
  queryFn: async () => {
17
- let e = await t("query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }", { input: {
18
- entityType: n,
19
- entityId: i,
18
+ let e = await n("query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }", { input: {
19
+ entityType: t,
20
+ entityId: r,
20
21
  take: 100,
21
22
  parentCommentId: null
22
- } }, o), r = e.getEntityComments.items.filter((e) => !e.parentCommentId);
23
- return r.sort((e, t) => new Date(e.createdAt).getTime() - new Date(t.createdAt).getTime()), {
24
- items: r,
23
+ } }, a), i = e.getEntityComments.items.filter((e) => !e.parentCommentId);
24
+ return i.sort((e, t) => new Date(e.createdAt).getTime() - new Date(t.createdAt).getTime()), {
25
+ items: i,
25
26
  total: e.getEntityComments.total
26
27
  };
27
28
  }
28
29
  });
29
- }, s = (r, o) => {
30
- let { apiGatewayUrl: s } = e(), c = i();
31
- return n({
32
- mutationFn: async (e) => (await t("mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }", { input: {
33
- entityType: r,
34
- entityId: o,
30
+ }, c = (i, s) => {
31
+ let { apiGatewayUrl: c } = e(), l = a();
32
+ return r({
33
+ mutationFn: async (e) => (await n("mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }", { input: {
34
+ entityType: i,
35
+ entityId: s,
35
36
  content: { text: e }
36
- } }, s)).createEntityComment,
37
+ } }, c)).createEntityComment,
37
38
  onSuccess: () => {
38
- c.invalidateQueries({ queryKey: a(r, o) });
39
+ t(l, o(i, s));
39
40
  }
40
41
  });
41
42
  };
42
43
  //#endregion
43
- export { o as useBuiComments, s as useEcoCreateComment };
44
+ export { s as useBuiComments, c as useEcoCreateComment };
44
45
 
45
46
  //# sourceMappingURL=useBuiComments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBuiComments.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module buildmyiq/hooks/useBuiComments\n *\n * Adds a discussion surface to Buildmyiq entities WITHOUT any buildmyiq-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useBuildmyiqApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an Buildmyiq entity, oldest first. */\nexport const useBuiComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: BuiComment[]; total: number };\n }>(\n `query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: commentsKey(entityType, entityId) });\n },\n });\n};\n"],"mappings":";;;;;AAkCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAa;CAAY;CAAY;AAAQ,GAGnC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,EAAE;EAChD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;GACnB,EACF,GACA,CACF,GAEM,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,eAAe;GAE3E,OADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,GAC/E;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;GAAM;EAC7D;CACF,CAAC;AACH,GAEa,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAcV,MAbY,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,QAAK;EAClB,EACF,GACA,CACF,GACY;EAEd,iBAAiB;GACf,EAAG,kBAAkB,EAAE,UAAU,EAAY,GAAY,CAAQ,EAAE,CAAC;EACtE;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"useBuiComments.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module buildmyiq/hooks/useBuiComments\n *\n * Adds a discussion surface to Buildmyiq entities WITHOUT any buildmyiq-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useBuildmyiqApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { invalidateAfterWrite } from './invalidateAfterWrite';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an Buildmyiq entity, oldest first. */\nexport const useBuiComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: BuiComment[]; total: number };\n }>(\n `query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n invalidateAfterWrite(qc, commentsKey(entityType, entityId));\n },\n });\n};\n"],"mappings":";;;;;;AAmCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAa;CAAY;CAAY;AAAQ,GAGnC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,EAAE;EAChD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;GACnB,EACF,GACA,CACF,GAEM,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,eAAe;GAE3E,OADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,GAC/E;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;GAAM;EAC7D;CACF,CAAC;AACH,GAEa,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAcV,MAbY,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,QAAK;EAClB,EACF,GACA,CACF,GACY;EAEd,iBAAiB;GACf,EAAqB,GAAI,EAAY,GAAY,CAAQ,CAAC;EAC5D;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,+BAA+B,EAC/B,gCAAgC,EACjC,MAAM,UAAU,CAAC;AAkClB;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,MAAM,GAAG,SAAS,EAAE,iBAAc,gGAwBzF,CAAC;AAEF,eAAO,MAAM,4BAA4B,uGAkBxC,CAAC;AAEF,eAAO,MAAM,8BAA8B;QAIR,MAAM;WAAS,gCAAgC;WAejF,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,+BAA+B,uGAkB3C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B;QAIP,MAAM;aAAW,wBAAwB,EAAE;WAc7E,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,8BAA8B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,4JA2B3F,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,wBAAwB,GACnC,MAAM;IAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,iBAAc,wGAuBf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,qGAuBzF,CAAC;AAEF,eAAO,MAAM,6BAA6B;QAIP,MAAM;WAAS,+BAA+B;WAYhF,CAAC"}
1
+ {"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EACV,aAAa,EACb,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,+BAA+B,EAC/B,gCAAgC,EACjC,MAAM,UAAU,CAAC;AAkClB;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,MAAM,GAAG,SAAS,EAAE,iBAAc,gGAwBzF,CAAC;AAEF,eAAO,MAAM,4BAA4B,uGAkBxC,CAAC;AAEF,eAAO,MAAM,8BAA8B;QAIR,MAAM;WAAS,gCAAgC;WAejF,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,+BAA+B,uGAkB3C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B;QAIP,MAAM;aAAW,wBAAwB,EAAE;WAc7E,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,8BAA8B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,4JA2B3F,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,wBAAwB,GACnC,MAAM;IAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,iBAAc,wGAuBf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,qGAuBzF,CAAC;AAEF,eAAO,MAAM,6BAA6B;QAIP,MAAM;WAAS,+BAA+B;WAYhF,CAAC"}