@alfakit/vue 0.1.9 → 0.1.10

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 (122) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/app/components.d.ts.map +1 -1
  3. package/dist/app/components.js +1 -0
  4. package/dist/app/components.js.map +1 -1
  5. package/dist/components/dev-tools/DevToolsInspector.js.map +1 -1
  6. package/dist/components/dev-tools/DevToolsInspector.vue.d.ts.map +1 -1
  7. package/dist/components/dev-tools/DevToolsInspector.vue_vue_type_script_setup_true_lang.js +1 -1
  8. package/dist/components/dev-tools/DevToolsInspector.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/fields/CheckboxListField.js.map +1 -1
  10. package/dist/components/fields/CheckboxListField.vue.d.ts.map +1 -1
  11. package/dist/components/fields/CheckboxListField.vue_vue_type_script_setup_true_lang.js +2 -2
  12. package/dist/components/fields/CheckboxListField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/fields/DatePickerField.js.map +1 -1
  14. package/dist/components/fields/DatePickerField.vue.d.ts.map +1 -1
  15. package/dist/components/fields/DatePickerField.vue_vue_type_script_setup_true_lang.js +34 -32
  16. package/dist/components/fields/DatePickerField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  17. package/dist/components/fields/RichEditorSuggestions.js.map +1 -1
  18. package/dist/components/fields/RichEditorSuggestions.vue.d.ts +1 -1
  19. package/dist/components/fields/RichEditorSuggestions.vue.d.ts.map +1 -1
  20. package/dist/components/fields/RichEditorSuggestions.vue_vue_type_script_setup_true_lang.js +29 -23
  21. package/dist/components/fields/RichEditorSuggestions.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/fields/RichEditorToolbarGroup.js.map +1 -1
  23. package/dist/components/fields/RichEditorToolbarGroup.vue.d.ts.map +1 -1
  24. package/dist/components/fields/RichEditorToolbarGroup.vue_vue_type_script_setup_true_lang.js +9 -4
  25. package/dist/components/fields/RichEditorToolbarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  26. package/dist/components/file-manager/FileManagerView.js.map +1 -1
  27. package/dist/components/file-manager/FileManagerView.vue.d.ts.map +1 -1
  28. package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js +5 -1
  29. package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/layout/CalloutComponent.js.map +1 -1
  31. package/dist/components/layout/CalloutComponent.vue.d.ts.map +1 -1
  32. package/dist/components/layout/CalloutComponent.vue_vue_type_script_setup_true_lang.js +43 -39
  33. package/dist/components/layout/CalloutComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  34. package/dist/components/layout/EmptyStateComponent.js.map +1 -1
  35. package/dist/components/layout/EmptyStateComponent.vue.d.ts.map +1 -1
  36. package/dist/components/layout/EmptyStateComponent.vue_vue_type_script_setup_true_lang.js +29 -26
  37. package/dist/components/layout/EmptyStateComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  38. package/dist/components/layout/MixedContent.js.map +1 -1
  39. package/dist/components/layout/MixedContent.vue.d.ts +5 -3
  40. package/dist/components/layout/MixedContent.vue.d.ts.map +1 -1
  41. package/dist/components/layout/MixedContent.vue_vue_type_script_setup_true_lang.js +5 -4
  42. package/dist/components/layout/MixedContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  43. package/dist/components/layout/SectionComponent.js.map +1 -1
  44. package/dist/components/layout/SectionComponent.vue.d.ts.map +1 -1
  45. package/dist/components/layout/SectionComponent.vue_vue_type_script_setup_true_lang.js +12 -8
  46. package/dist/components/layout/SectionComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  47. package/dist/components/notifications/NotificationsMenu.js.map +1 -1
  48. package/dist/components/notifications/NotificationsMenu.vue.d.ts.map +1 -1
  49. package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js +133 -104
  50. package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  51. package/dist/components/panel/ShortcutsOverlay.js.map +1 -1
  52. package/dist/components/panel/ShortcutsOverlay.vue.d.ts.map +1 -1
  53. package/dist/components/panel/ShortcutsOverlay.vue_vue_type_script_setup_true_lang.js +20 -19
  54. package/dist/components/panel/ShortcutsOverlay.vue_vue_type_script_setup_true_lang.js.map +1 -1
  55. package/dist/components/table/ColumnManager.js.map +1 -1
  56. package/dist/components/table/ColumnManager.vue.d.ts.map +1 -1
  57. package/dist/components/table/ColumnManager.vue_vue_type_script_setup_true_lang.js +6 -6
  58. package/dist/components/table/ColumnManager.vue_vue_type_script_setup_true_lang.js.map +1 -1
  59. package/dist/components/table/Spreadsheet.js.map +1 -1
  60. package/dist/components/table/Spreadsheet.vue.d.ts.map +1 -1
  61. package/dist/components/table/Spreadsheet.vue_vue_type_script_setup_true_lang.js +37 -33
  62. package/dist/components/table/Spreadsheet.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/translation-manager/TranslationManagerView.js.map +1 -1
  64. package/dist/components/translation-manager/TranslationManagerView.vue.d.ts.map +1 -1
  65. package/dist/components/translation-manager/TranslationManagerView.vue_vue_type_script_setup_true_lang.js +1 -1
  66. package/dist/components/translation-manager/TranslationManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
  67. package/dist/components/ui/SidebarMenuButton.js.map +1 -1
  68. package/dist/components/ui/SidebarMenuButton.vue.d.ts.map +1 -1
  69. package/dist/components/ui/SidebarMenuButton.vue_vue_type_script_setup_true_lang.js +12 -12
  70. package/dist/components/ui/SidebarMenuButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  71. package/dist/components/widgets/StatCard.js.map +1 -1
  72. package/dist/components/widgets/StatCard.vue.d.ts.map +1 -1
  73. package/dist/components/widgets/StatCard.vue_vue_type_script_setup_true_lang.js +30 -30
  74. package/dist/components/widgets/StatCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  75. package/dist/eject-manifest.json +2 -2
  76. package/dist/pages/AlfakitPage.js.map +1 -1
  77. package/dist/pages/AlfakitPage.vue.d.ts.map +1 -1
  78. package/dist/pages/AlfakitPage.vue_vue_type_script_setup_true_lang.js +16 -19
  79. package/dist/pages/AlfakitPage.vue_vue_type_script_setup_true_lang.js.map +1 -1
  80. package/dist/standalone/AlfakitComponent.js.map +1 -1
  81. package/dist/standalone/AlfakitComponent.vue.d.ts.map +1 -1
  82. package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js +2 -2
  83. package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  84. package/dist/standalone/runtime.d.ts.map +1 -1
  85. package/dist/standalone/runtime.js +25 -25
  86. package/dist/standalone/runtime.js.map +1 -1
  87. package/dist/standalone.css +2 -1
  88. package/dist/starter-kit/AlfakitStarterKitRoot.js.map +1 -1
  89. package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts.map +1 -1
  90. package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js +1 -1
  91. package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
  92. package/dist/starter-kit/navigation.d.ts +6 -1
  93. package/dist/starter-kit/navigation.d.ts.map +1 -1
  94. package/dist/starter-kit/navigation.js +11 -10
  95. package/dist/starter-kit/navigation.js.map +1 -1
  96. package/dist/styles.css +2 -1
  97. package/dist/themes.css +4 -0
  98. package/package.json +2 -2
  99. package/src/app/components.ts +3 -0
  100. package/src/components/dev-tools/DevToolsInspector.vue +1 -1
  101. package/src/components/fields/CheckboxListField.vue +2 -2
  102. package/src/components/fields/DatePickerField.vue +1 -0
  103. package/src/components/fields/RichEditorSuggestions.vue +13 -6
  104. package/src/components/fields/RichEditorToolbarGroup.vue +16 -1
  105. package/src/components/file-manager/FileManagerView.vue +3 -1
  106. package/src/components/layout/CalloutComponent.vue +15 -2
  107. package/src/components/layout/EmptyStateComponent.vue +10 -1
  108. package/src/components/layout/MixedContent.vue +12 -4
  109. package/src/components/layout/SectionComponent.vue +20 -4
  110. package/src/components/notifications/NotificationsMenu.vue +45 -1
  111. package/src/components/panel/ShortcutsOverlay.vue +27 -22
  112. package/src/components/table/ColumnManager.vue +6 -4
  113. package/src/components/table/Spreadsheet.vue +9 -4
  114. package/src/components/translation-manager/TranslationManagerView.vue +1 -1
  115. package/src/components/ui/SidebarMenuButton.vue +6 -0
  116. package/src/components/widgets/StatCard.vue +6 -3
  117. package/src/pages/AlfakitPage.vue +13 -3
  118. package/src/standalone/AlfakitComponent.vue +8 -4
  119. package/src/standalone/runtime.ts +3 -0
  120. package/src/starter-kit/AlfakitStarterKitRoot.vue +3 -2
  121. package/src/starter-kit/navigation.ts +12 -1
  122. package/src/styles.css +2 -1
@@ -27,12 +27,12 @@ var Se = {
27
27
  }, we = {
28
28
  key: 1,
29
29
  class: "text-destructive ms-2"
30
- }, j = [
30
+ }, Te = [
31
31
  "aria-label",
32
32
  "aria-rowcount",
33
33
  "aria-colcount",
34
34
  "aria-busy"
35
- ], Te = { "aria-rowindex": "1" }, Ee = ["aria-colindex"], De = ["data-index", "aria-rowindex"], Oe = [
35
+ ], Ee = { "aria-rowindex": "1" }, De = ["aria-colindex"], Oe = ["data-index", "aria-rowindex"], j = [
36
36
  "id",
37
37
  "tabindex",
38
38
  "aria-selected",
@@ -69,13 +69,17 @@ var Se = {
69
69
  "aria-atomic": "true",
70
70
  class: "sr-only",
71
71
  "data-slot": "spreadsheet-announcement"
72
- }, Ve = {
72
+ }, Ve = { class: "block" }, He = {
73
+ key: 0,
74
+ class: "block",
75
+ "data-slot": "spreadsheet-paste-skipped"
76
+ }, Ue = {
73
77
  "data-slot": "spreadsheet-paste",
74
78
  class: "flex flex-col gap-3"
75
- }, He = {
79
+ }, We = {
76
80
  key: 0,
77
81
  class: "text-muted-foreground flex items-center gap-2 text-sm"
78
- }, Ue = {
82
+ }, Ge = {
79
83
  key: 1,
80
84
  class: "text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm"
81
85
  }, M = /*@__PURE__*/ b({
@@ -87,9 +91,9 @@ var Se = {
87
91
  },
88
92
  emits: ["close"],
89
93
  setup(b, { emit: M }) {
90
- let N = b, We = M, P = r(), F = e(), Ge = t(), Ke = n(), I = `spreadsheet-${be()}`, L = p(() => me(N.table)), R = p(() => he(N.table.records ?? [], L.value)), z = new ue(R.value, L.value), B = ee(z.store), V = C(""), H = C(null), U = C(null), W = `${I}-help`;
94
+ let N = b, Ke = M, P = r(), F = e(), qe = t(), Je = n(), I = `spreadsheet-${be()}`, L = p(() => me(N.table)), R = p(() => he(N.table.records ?? [], L.value)), z = new ue(R.value, L.value), B = ee(z.store), V = C(""), H = C(null), U = C(null), W = `${I}-help`;
91
95
  D([R, L], ([e, t]) => z.setData(e, t));
92
- let G = ne(() => R.value.length, () => _e(N.table.virtualize, de.row)), K = p(() => Ke.value.endpoints.tableCells), q = p(() => (B.value, z.errorCells()));
96
+ let G = ne(() => R.value.length, () => _e(N.table.virtualize, de.row)), K = p(() => Je.value.endpoints.tableCells), q = p(() => (B.value, z.errorCells()));
93
97
  function J(e) {
94
98
  e && (V.value = F(e.key, e.count, {}, e.fallback));
95
99
  }
@@ -125,7 +129,7 @@ var Se = {
125
129
  }
126
130
  }
127
131
  }
128
- async function qe() {
132
+ async function Ye() {
129
133
  let e = z.undoSaveChanges();
130
134
  if (K.value && e.length !== 0) {
131
135
  z.setSaving(!0);
@@ -137,17 +141,17 @@ var Se = {
137
141
  }
138
142
  }
139
143
  }
140
- function Je() {
141
- z.dirtyCount > 0 && !window.confirm(P("table.spreadsheet.discard_confirm", {}, "Discard your unsaved changes?")) || (z.discard(), We("close"));
144
+ function Xe() {
145
+ z.dirtyCount > 0 && !window.confirm(P("table.spreadsheet.discard_confirm", {}, "Discard your unsaved changes?")) || (z.discard(), Ke("close"));
142
146
  }
143
- function Ye(e) {
144
- let t = z.handleKey(e, Ge.value);
147
+ function Ze(e) {
148
+ let t = z.handleKey(e, qe.value);
145
149
  t !== "ignored" && (e.preventDefault(), t === "save" ? X() : t === "undo" ? J(u("undo", z.undo())) : (t === "fill" || t === "clear") && J(u(t, z.lastApplied)), (t === "commit" || t === "cancel" || t === "toggle") && Y(z.state.active.row, z.state.active.col));
146
150
  }
147
- function Xe(e) {
151
+ function Qe(e) {
148
152
  !z.state.editing && e.clipboardData && (e.preventDefault(), e.clipboardData.setData("text/plain", z.copyText()), J(u("copy", ge(z.bounds()))));
149
153
  }
150
- function Ze(e) {
154
+ function $e(e) {
151
155
  if (z.state.editing || !e.clipboardData) return;
152
156
  e.preventDefault();
153
157
  let t = z.planPaste(e.clipboardData.getData("text/plain"));
@@ -174,13 +178,13 @@ var Se = {
174
178
  };
175
179
  });
176
180
  }
177
- function Qe() {
181
+ function et() {
178
182
  let e = H.value;
179
183
  if (!e) return;
180
184
  let t = z.apply(e.plan.changes);
181
185
  e.errors.length > 0 && z.setErrors(e.errors), J(u("paste", t)), H.value = null;
182
186
  }
183
- function $e(e) {
187
+ function tt(e) {
184
188
  let t = le[e.reason];
185
189
  return P(t.key, {
186
190
  text: e.text,
@@ -188,7 +192,7 @@ var Se = {
188
192
  row: e.row + 1
189
193
  }, t.fallback);
190
194
  }
191
- function et(e) {
195
+ function nt(e) {
192
196
  let t = z.position(e.record, e.column);
193
197
  return P("table.spreadsheet.cell_error", {
194
198
  row: (t?.row ?? -1) + 1,
@@ -203,10 +207,10 @@ var Se = {
203
207
  function Q(e, t) {
204
208
  return B.value.editing?.row === e && B.value.editing.col === t;
205
209
  }
206
- function tt(e, t, n) {
210
+ function rt(e, t, n) {
207
211
  z.updateDraft(e.target.value), z.commitEdit(), Y(t, n);
208
212
  }
209
- let nt = (e) => e.replace(/<[^>]*>/g, ""), $ = { mounted: (e) => e.focus() };
213
+ let it = (e) => e.replace(/<[^>]*>/g, ""), $ = { mounted: (e) => e.focus() };
210
214
  return (e, t) => (S(), g("div", Se, [
211
215
  _("div", Ce, [
212
216
  y(i, {
@@ -234,7 +238,7 @@ var Se = {
234
238
  size: "sm",
235
239
  variant: "outline",
236
240
  disabled: E(B).saving,
237
- onClick: qe
241
+ onClick: Ye
238
242
  }, {
239
243
  default: O(() => [v(T(E(P)("table.spreadsheet.undo_save", {}, "Undo last save")), 1)]),
240
244
  _: 1
@@ -255,7 +259,7 @@ var Se = {
255
259
  type: "button",
256
260
  size: "sm",
257
261
  variant: "outline",
258
- onClick: Je
262
+ onClick: Xe
259
263
  }, {
260
264
  default: O(() => [v(T(E(P)("table.spreadsheet.close", {}, "Done")), 1)]),
261
265
  _: 1
@@ -281,16 +285,16 @@ var Se = {
281
285
  "aria-busy": E(B).saving || void 0,
282
286
  "data-slot": "spreadsheet-grid",
283
287
  class: x(E(s).table),
284
- onKeydown: Ye,
285
- onCopy: Xe,
286
- onPaste: Ze
287
- }, [_("thead", null, [_("tr", Te, [(S(!0), g(f, null, w(L.value, (e, t) => (S(), g("th", {
288
+ onKeydown: Ze,
289
+ onCopy: Qe,
290
+ onPaste: $e
291
+ }, [_("thead", null, [_("tr", Ee, [(S(!0), g(f, null, w(L.value, (e, t) => (S(), g("th", {
288
292
  key: e.name,
289
293
  role: "columnheader",
290
294
  scope: "col",
291
295
  "aria-colindex": t + 1,
292
296
  class: x(E(s).header)
293
- }, T(e.label), 11, Ee))), 128))])]), _("tbody", { ref: E(G).attach }, [
297
+ }, T(e.label), 11, De))), 128))])]), _("tbody", { ref: E(G).attach }, [
294
298
  y(o, {
295
299
  height: E(G).range.value.before,
296
300
  "col-span": L.value.length
@@ -322,13 +326,13 @@ var Se = {
322
326
  class: x(E(s).editor),
323
327
  "aria-label": n.label,
324
328
  value: Z(),
325
- onChange: (t) => tt(t, e, r),
329
+ onChange: (t) => rt(t, e, r),
326
330
  onBlur: t[2] ||= (e) => E(z).cancelEdit()
327
331
  }, [_("option", Ae, T(E(P)("table.spreadsheet.no_value", {}, "—")), 1), (S(!0), g(f, null, w(n.options, (e) => (S(), g("option", {
328
332
  key: e.value,
329
333
  value: e.value,
330
334
  disabled: e.disabled
331
- }, T(nt(e.label)), 9, je))), 128))], 42, ke)), [[$]]) : Q(e, r) ? k((S(), g("input", {
335
+ }, T(it(e.label)), 9, je))), 128))], 42, ke)), [[$]]) : Q(e, r) ? k((S(), g("input", {
332
336
  key: 1,
333
337
  type: n.kind === "number" ? "text" : n.inputType ?? "text",
334
338
  inputmode: n.kind === "number" ? "decimal" : void 0,
@@ -344,12 +348,12 @@ var Se = {
344
348
  }, [E(z).value(e, r) ? (S(), m(E(xe), {
345
349
  key: 0,
346
350
  class: "size-3"
347
- })) : h("", !0)], 8, Pe), _("span", Fe, T(E(z).value(e, r) ? E(P)("table.spreadsheet.yes", {}, "Yes") : E(P)("table.spreadsheet.no", {}, "No")), 1)])) : (S(), g(f, { key: 3 }, [v(T(E(z).text(e, r)), 1)], 64)), E(z).errorsAt(e, r).length > 0 ? (S(), g("span", Ie, T(E(z).errorsAt(e, r).join(" ")), 1)) : h("", !0)], 16, Oe))), 128))], 8, De)) : h("", !0)], 64))), 128)),
351
+ })) : h("", !0)], 8, Pe), _("span", Fe, T(E(z).value(e, r) ? E(P)("table.spreadsheet.yes", {}, "Yes") : E(P)("table.spreadsheet.no", {}, "No")), 1)])) : (S(), g(f, { key: 3 }, [v(T(E(z).text(e, r)), 1)], 64)), E(z).errorsAt(e, r).length > 0 ? (S(), g("span", Ie, T(E(z).errorsAt(e, r).join(" ")), 1)) : h("", !0)], 16, j))), 128))], 8, Oe)) : h("", !0)], 64))), 128)),
348
352
  y(o, {
349
353
  height: E(G).range.value.after,
350
354
  "col-span": L.value.length
351
355
  }, null, 8, ["height", "col-span"])
352
- ], 512)], 42, j), R.value.length === 0 ? (S(), g("p", Le, T(E(P)("table.spreadsheet.empty", {}, "No records to edit.")), 1)) : h("", !0)], 2),
356
+ ], 512)], 42, Te), R.value.length === 0 ? (S(), g("p", Le, T(E(P)("table.spreadsheet.empty", {}, "No records to edit.")), 1)) : h("", !0)], 2),
353
357
  q.value.length > 0 ? (S(), g("ul", Re, [(S(!0), g(f, null, w(q.value, (e) => (S(), g("li", { key: e.key }, [_("button", {
354
358
  type: "button",
355
359
  class: "text-start hover:underline",
@@ -373,12 +377,12 @@ var Se = {
373
377
  default: O(() => [v(T(E(P)("table.spreadsheet.paste_title", {}, "Paste cells")), 1)]),
374
378
  _: 1
375
379
  }), y(ae, null, {
376
- default: O(() => [v(T(E(F)("table.spreadsheet.paste_summary", H.value?.plan.changes.length ?? 0, {}, ":count cell will change|:count cells will change")) + " ", 1), (H.value?.plan.skipped ?? 0) > 0 ? (S(), g(f, { key: 0 }, [v(T(E(F)("table.spreadsheet.paste_skipped", H.value?.plan.skipped ?? 0, {}, ":count read-only cell is skipped|:count read-only cells are skipped")), 1)], 64)) : h("", !0)]),
380
+ default: O(() => [_("span", Ve, T(E(F)("table.spreadsheet.paste_summary", H.value?.plan.changes.length ?? 0, {}, ":count cell will change|:count cells will change")), 1), (H.value?.plan.skipped ?? 0) > 0 ? (S(), g("span", He, T(E(F)("table.spreadsheet.paste_skipped", H.value?.plan.skipped ?? 0, {}, ":count read-only cell is skipped|:count read-only cells are skipped")), 1)) : h("", !0)]),
377
381
  _: 1
378
382
  })]),
379
383
  _: 1
380
384
  }),
381
- _("div", Ve, [H.value?.validating ? (S(), g("p", He, [y(a), v(" " + T(E(P)("table.spreadsheet.paste_validating", {}, "Checking the values…")), 1)])) : h("", !0), H.value && (H.value.plan.issues.length > 0 || H.value.errors.length > 0) ? (S(), g("ul", Ue, [(S(!0), g(f, null, w(H.value.plan.issues, (e) => (S(), g("li", { key: `issue-${e.row}-${e.col}` }, T($e(e)), 1))), 128)), (S(!0), g(f, null, w(H.value.errors, (e) => (S(), g("li", { key: `error-${String(e.record)}-${e.column}` }, T(et(e)), 1))), 128))])) : h("", !0)]),
385
+ _("div", Ue, [H.value?.validating ? (S(), g("p", We, [y(a), v(" " + T(E(P)("table.spreadsheet.paste_validating", {}, "Checking the values…")), 1)])) : h("", !0), H.value && (H.value.plan.issues.length > 0 || H.value.errors.length > 0) ? (S(), g("ul", Ge, [(S(!0), g(f, null, w(H.value.plan.issues, (e) => (S(), g("li", { key: `issue-${e.row}-${e.col}` }, T(tt(e)), 1))), 128)), (S(!0), g(f, null, w(H.value.errors, (e) => (S(), g("li", { key: `error-${String(e.record)}-${e.column}` }, T(nt(e)), 1))), 128))])) : h("", !0)]),
382
386
  y(ce, null, {
383
387
  default: O(() => [y(i, {
384
388
  type: "button",
@@ -390,7 +394,7 @@ var Se = {
390
394
  }), y(i, {
391
395
  type: "button",
392
396
  disabled: !H.value || H.value.plan.changes.length === 0,
393
- onClick: Qe
397
+ onClick: et
394
398
  }, {
395
399
  default: O(() => [v(T(E(P)("table.spreadsheet.paste_apply", {}, "Apply")), 1)]),
396
400
  _: 1
@@ -1 +1 @@
1
- {"version":3,"file":"Spreadsheet.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","Button","_unref","_withCtx","_createBlock","Spinner","_createCommentVNode","_createTextVNode","_toDisplayString","_Fragment","_normalizeClass","_renderList","VirtualSpacerRow","_mergeProps","_withDirectives","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","Button_default","Spinner_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","DialogFooter_default","VirtualSpacerRow_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","Spreadsheet_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","t","useT","choice","useChoice","direction","useDirection","panel","usePanel","baseId","useId","columns","computed","spreadsheetColumns","rows","spreadsheetRows","grid","SpreadsheetGrid","state","useStore","announcement","ref","preview","gridElement","helpId","nextRows","nextColumns","virtual","useVirtualList","virtualizeOptions","VIRTUAL_ESTIMATES","endpoint","errorList","announce","message","focusCell","cell","spreadsheetCellId","row","col","active","editing","save","changes","result","saveSpreadsheetChanges","spreadsheetAnnouncement","inertiaReload","error","undoSave","close","onKeyDown","event","onCopy","spreadsheetSelectionSize","onPaste","plan","url","change","applyPaste","current","count","issueText","SPREADSHEET_ISSUE_MESSAGES","issue","errorText","position","record","column","draft","value","isEditing","onSelectChange","optionText","label","vFocus","element","openBlock","createElementBlock","createElementVNode","createVNode","unref","withCtx","createBlock","createCommentVNode","createTextVNode","toDisplayString","_cache","Fragment","normalizeClass","SPREADSHEET_CLASSES","renderList","mergeProps","spreadsheetAria","spreadsheetCellClass","$event","withDirectives","option","CheckIcon"],"sources":["../../../src/components/table/Spreadsheet.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from '@lucide/vue'\nimport { computed, nextTick, ref, useId, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useVirtualList } from '../../composables/use-virtual-list'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { showToast } from '../notifications/flash-toasts'\nimport VirtualSpacerRow from '../schema/VirtualSpacerRow.vue'\nimport Button from '../ui/Button.vue'\nimport Dialog from '../ui/Dialog.vue'\nimport DialogContent from '../ui/DialogContent.vue'\nimport DialogDescription from '../ui/DialogDescription.vue'\nimport DialogFooter from '../ui/DialogFooter.vue'\nimport DialogHeader from '../ui/DialogHeader.vue'\nimport DialogTitle from '../ui/DialogTitle.vue'\nimport Spinner from '../ui/Spinner.vue'\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nconst props = defineProps<{ table: TableSchema; token: string; component: string }>()\nconst emit = defineEmits<{ close: [] }>()\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\nconst t = useT()\nconst choice = useChoice()\nconst direction = useDirection()\nconst panel = usePanel()\nconst baseId = `spreadsheet-${useId()}`\nconst columns = computed(() => spreadsheetColumns(props.table))\nconst rows = computed(() => spreadsheetRows(props.table.records ?? [], columns.value))\nconst grid = new SpreadsheetGrid(rows.value, columns.value)\nconst state = useStore(grid.store)\nconst announcement = ref('')\nconst preview = ref<PastePreview | null>(null)\nconst gridElement = ref<HTMLTableElement | null>(null)\nconst helpId = `${baseId}-help`\n\n// A reload (save, live update, polling) brings new records; edits of rows still on the\n// page are kept.\nwatch([rows, columns], ([nextRows, nextColumns]) => grid.setData(nextRows, nextColumns))\n\nconst virtual = useVirtualList(\n () => rows.value.length,\n () => virtualizeOptions(props.table.virtualize, VIRTUAL_ESTIMATES.row),\n)\n\nconst endpoint = computed(() => panel.value.endpoints.tableCells)\nconst errorList = computed(() => {\n void state.value\n\n return grid.errorCells()\n})\n\nfunction announce(message: SpreadsheetAnnouncement | null) {\n if (message) announcement.value = choice(message.key, message.count, {}, message.fallback)\n}\n\nfunction focusCell(row: number, col: number) {\n void nextTick(() => {\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus()\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n virtual.list.scrollToIndex(row)\n }\n })\n}\n\n// Keep focus on the active cell (roving tabindex).\nwatch(\n () => [state.value.active, state.value.editing] as const,\n ([active, editing]) => {\n if (editing || !gridElement.value?.contains(document.activeElement)) return\n\n focusCell(active.row, active.col)\n },\n)\n\nasync function save() {\n const changes = grid.changes()\n\n if (!endpoint.value || changes.length === 0 || state.value.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(\n endpoint.value,\n props.token,\n props.component,\n changes,\n )\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) announce(spreadsheetAnnouncement('failed', result.errors.length))\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n}\n\nasync function undoSave() {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint.value || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(\n endpoint.value,\n props.token,\n props.component,\n changes,\n )\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n}\n\nfunction close() {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n emit('close')\n}\n\nfunction onKeyDown(event: KeyboardEvent) {\n const result = grid.handleKey(event, direction.value)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n focusCell(grid.state.active.row, grid.state.active.col)\n }\n}\n\nfunction onCopy(event: ClipboardEvent) {\n if (grid.state.editing || !event.clipboardData) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n}\n\nfunction onPaste(event: ClipboardEvent) {\n if (grid.state.editing || !event.clipboardData) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n const url = endpoint.value\n\n preview.value = { plan, errors: [], validating: Boolean(url && plan.changes.length > 0) }\n\n if (!url || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n saveSpreadsheetChanges(\n url,\n props.token,\n props.component,\n plan.changes.map((change) => ({\n record: grid.getRows()[change.row]?.key ?? '',\n column: grid.getColumns()[change.col]?.name ?? '',\n state: change.value,\n })),\n { dryRun: true },\n )\n .then((result) => {\n if (preview.value)\n preview.value = { ...preview.value, errors: result.errors, validating: false }\n })\n .catch(() => {\n if (preview.value) preview.value = { ...preview.value, validating: false }\n })\n}\n\nfunction applyPaste() {\n const current = preview.value\n\n if (!current) return\n\n const count = grid.apply(current.plan.changes)\n\n if (current.errors.length > 0) grid.setErrors(current.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n preview.value = null\n}\n\nfunction issueText(issue: PastePlan['issues'][number]): string {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns.value[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n}\n\nfunction errorText(record: SpreadsheetSaveResult['errors'][number]): string {\n const position = grid.position(record.record, record.column)\n\n return t(\n 'table.spreadsheet.cell_error',\n {\n row: (position?.row ?? -1) + 1,\n column: columns.value.find((column) => column.name === record.column)?.label ?? record.column,\n message: record.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )\n}\n\nfunction draft(): string {\n const value = state.value.editing?.value\n\n return value === null || value === undefined ? '' : String(value)\n}\n\nfunction isEditing(row: number, col: number): boolean {\n return state.value.editing?.row === row && state.value.editing.col === col\n}\n\nfunction onSelectChange(event: Event, row: number, col: number) {\n grid.updateDraft((event.target as HTMLSelectElement).value)\n grid.commitEdit()\n focusCell(row, col)\n}\n\nconst optionText = (label: string) => label.replace(/<[^>]*>/g, '')\n\n/** Focus an editor as soon as it is shown. */\nconst vFocus = { mounted: (element: HTMLElement) => element.focus() }\n</script>\n\n<template>\n <div data-slot=\"spreadsheet\" class=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" class=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n :disabled=\"grid.dirtyCount === 0 || state.saving\"\n @click=\"save\"\n >\n <Spinner v-if=\"state.saving\" />\n {{ t('table.spreadsheet.save', {}, 'Save changes') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n :disabled=\"!grid.canUndo || state.saving\"\n @click=\"announce(spreadsheetAnnouncement('undo', grid.undo()))\"\n >\n {{ t('table.spreadsheet.undo', {}, 'Undo') }}\n </Button>\n <Button\n v-if=\"grid.canUndoSave\"\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n :disabled=\"state.saving\"\n @click=\"undoSave\"\n >\n {{ t('table.spreadsheet.undo_save', {}, 'Undo last save') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n :disabled=\"grid.dirtyCount === 0 || state.saving\"\n @click=\"grid.discard()\"\n >\n {{ t('table.spreadsheet.discard', {}, 'Discard') }}\n </Button>\n <span class=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n <template v-if=\"grid.dirtyCount > 0\">{{\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )\n }}</template>\n <span v-if=\"grid.errorCount > 0\" class=\"text-destructive ms-2\">{{\n choice('table.spreadsheet.errors', grid.errorCount, {}, ':count error|:count errors')\n }}</span>\n </span>\n <span class=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" @click=\"close\">\n {{ t('table.spreadsheet.close', {}, 'Done') }}\n </Button>\n </div>\n <p :id=\"helpId\" class=\"sr-only\">\n {{\n t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )\n }}\n </p>\n <div data-surface=\"table\" :class=\"SPREADSHEET_CLASSES.root\" style=\"max-height: 70vh\">\n <table\n ref=\"gridElement\"\n role=\"grid\"\n :aria-label=\"props.table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')\"\n :aria-describedby=\"helpId\"\n :aria-rowcount=\"rows.length + 1\"\n :aria-colcount=\"columns.length\"\n aria-multiselectable=\"true\"\n :aria-busy=\"state.saving || undefined\"\n data-slot=\"spreadsheet-grid\"\n :class=\"SPREADSHEET_CLASSES.table\"\n @keydown=\"onKeyDown\"\n @copy=\"onCopy\"\n @paste=\"onPaste\"\n >\n <thead>\n <tr aria-rowindex=\"1\">\n <th\n v-for=\"(column, col) in columns\"\n :key=\"column.name\"\n role=\"columnheader\"\n scope=\"col\"\n :aria-colindex=\"col + 1\"\n :class=\"SPREADSHEET_CLASSES.header\"\n >\n {{ column.label }}\n </th>\n </tr>\n </thead>\n <tbody :ref=\"virtual.attach\">\n <VirtualSpacerRow :height=\"virtual.range.value.before\" :col-span=\"columns.length\" />\n <template v-for=\"row in virtual.indexes.value\" :key=\"String(rows[row]?.key ?? row)\">\n <tr v-if=\"rows[row]\" :ref=\"virtual.item\" :data-index=\"row\" :aria-rowindex=\"row + 2\">\n <td\n v-for=\"(column, col) in columns\"\n :id=\"spreadsheetCellId(baseId, row, col)\"\n :key=\"column.name\"\n role=\"gridcell\"\n v-bind=\"spreadsheetAria(row, col)\"\n :tabindex=\"grid.isActive(row, col) ? 0 : -1\"\n :aria-selected=\"grid.isSelected(row, col)\"\n :aria-readonly=\"!grid.isEditable(row, col) || undefined\"\n :aria-invalid=\"grid.errorsAt(row, col).length > 0 || undefined\"\n :title=\"grid.errorsAt(row, col).join(' ') || undefined\"\n data-slot=\"spreadsheet-cell\"\n :data-dirty=\"grid.isDirty(row, col) || undefined\"\n :data-active=\"grid.isActive(row, col) || undefined\"\n :class=\"spreadsheetCellClass(grid, row, col)\"\n @mousedown=\"!isEditing(row, col) && grid.moveTo(row, col, $event.shiftKey)\"\n @dblclick=\"grid.beginEdit()\"\n >\n <select\n v-if=\"isEditing(row, col) && column.kind === 'select'\"\n v-focus\n :class=\"SPREADSHEET_CLASSES.editor\"\n :aria-label=\"column.label\"\n :value=\"draft()\"\n @change=\"onSelectChange($event, row, col)\"\n @blur=\"grid.cancelEdit()\"\n >\n <option value=\"\">{{ t('table.spreadsheet.no_value', {}, '—') }}</option>\n <option\n v-for=\"option in column.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ optionText(option.label) }}\n </option>\n </select>\n <input\n v-else-if=\"isEditing(row, col)\"\n v-focus\n :type=\"column.kind === 'number' ? 'text' : (column.inputType ?? 'text')\"\n :inputmode=\"column.kind === 'number' ? 'decimal' : undefined\"\n :class=\"SPREADSHEET_CLASSES.editor\"\n :aria-label=\"column.label\"\n :value=\"draft()\"\n @input=\"grid.updateDraft(($event.target as HTMLInputElement).value)\"\n @blur=\"grid.commitEdit()\"\n />\n <span v-else-if=\"column.kind === 'boolean'\" class=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n class=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n :data-checked=\"Boolean(grid.value(row, col))\"\n >\n <CheckIcon v-if=\"grid.value(row, col)\" class=\"size-3\" />\n </span>\n <span class=\"sr-only\">{{\n grid.value(row, col)\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')\n }}</span>\n </span>\n <template v-else>{{ grid.text(row, col) }}</template>\n <span v-if=\"grid.errorsAt(row, col).length > 0\" class=\"sr-only\">{{\n grid.errorsAt(row, col).join(' ')\n }}</span>\n </td>\n </tr>\n </template>\n <VirtualSpacerRow :height=\"virtual.range.value.after\" :col-span=\"columns.length\" />\n </tbody>\n </table>\n <p v-if=\"rows.length === 0\" class=\"text-muted-foreground p-6 text-center text-sm\">\n {{ t('table.spreadsheet.empty', {}, 'No records to edit.') }}\n </p>\n </div>\n <ul\n v-if=\"errorList.length > 0\"\n data-slot=\"spreadsheet-errors\"\n class=\"text-destructive flex flex-col gap-1 text-sm\"\n >\n <li v-for=\"error in errorList\" :key=\"error.key\">\n <button\n type=\"button\"\n class=\"text-start hover:underline\"\n @click=\"\n () => {\n grid.moveTo(error.row, error.col)\n focusCell(error.row, error.col)\n }\n \"\n >\n {{\n t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )\n }}\n </button>\n </li>\n </ul>\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n class=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >{{ announcement }}</span\n >\n\n <Dialog :open=\"preview !== null\" @update:open=\"(value) => !value && (preview = null)\">\n <DialogContent class=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{{ t('table.spreadsheet.paste_title', {}, 'Paste cells') }}</DialogTitle>\n <DialogDescription>\n {{\n choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )\n }}\n <template v-if=\"(preview?.plan.skipped ?? 0) > 0\">\n {{\n choice(\n 'table.spreadsheet.paste_skipped',\n preview?.plan.skipped ?? 0,\n {},\n ':count read-only cell is skipped|:count read-only cells are skipped',\n )\n }}\n </template>\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" class=\"flex flex-col gap-3\">\n <p\n v-if=\"preview?.validating\"\n class=\"text-muted-foreground flex items-center gap-2 text-sm\"\n >\n <Spinner />\n {{ t('table.spreadsheet.paste_validating', {}, 'Checking the values…') }}\n </p>\n <ul\n v-if=\"preview && (preview.plan.issues.length > 0 || preview.errors.length > 0)\"\n class=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\"\n >\n <li v-for=\"issue in preview.plan.issues\" :key=\"`issue-${issue.row}-${issue.col}`\">\n {{ issueText(issue) }}\n </li>\n <li\n v-for=\"error in preview.errors\"\n :key=\"`error-${String(error.record)}-${error.column}`\"\n >\n {{ errorText(error) }}\n </li>\n </ul>\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" @click=\"preview = null\">{{\n t('cancel', {}, 'Cancel')\n }}</Button>\n <Button\n type=\"button\"\n :disabled=\"!preview || preview.plan.changes.length === 0\"\n @click=\"applyPaste\"\n >\n {{ t('table.spreadsheet.paste_apply', {}, 'Apply') }}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"Spreadsheet.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","Button","_unref","_withCtx","_createBlock","Spinner","_createCommentVNode","_createTextVNode","_toDisplayString","_Fragment","_normalizeClass","_renderList","VirtualSpacerRow","_mergeProps","_withDirectives","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","Button_default","Spinner_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","DialogFooter_default","VirtualSpacerRow_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","Spreadsheet_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","t","useT","choice","useChoice","direction","useDirection","panel","usePanel","baseId","useId","columns","computed","spreadsheetColumns","rows","spreadsheetRows","grid","SpreadsheetGrid","state","useStore","announcement","ref","preview","gridElement","helpId","nextRows","nextColumns","virtual","useVirtualList","virtualizeOptions","VIRTUAL_ESTIMATES","endpoint","errorList","announce","message","focusCell","cell","spreadsheetCellId","row","col","active","editing","save","changes","result","saveSpreadsheetChanges","spreadsheetAnnouncement","inertiaReload","error","undoSave","close","onKeyDown","event","onCopy","spreadsheetSelectionSize","onPaste","plan","url","change","applyPaste","current","count","issueText","SPREADSHEET_ISSUE_MESSAGES","issue","errorText","position","record","column","draft","value","isEditing","onSelectChange","optionText","label","vFocus","element","openBlock","createElementBlock","createElementVNode","createVNode","unref","withCtx","createBlock","createCommentVNode","createTextVNode","toDisplayString","_cache","Fragment","normalizeClass","SPREADSHEET_CLASSES","renderList","mergeProps","spreadsheetAria","spreadsheetCellClass","$event","withDirectives","option","CheckIcon"],"sources":["../../../src/components/table/Spreadsheet.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from '@lucide/vue'\nimport { computed, nextTick, ref, useId, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useVirtualList } from '../../composables/use-virtual-list'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { showToast } from '../notifications/flash-toasts'\nimport VirtualSpacerRow from '../schema/VirtualSpacerRow.vue'\nimport Button from '../ui/Button.vue'\nimport Dialog from '../ui/Dialog.vue'\nimport DialogContent from '../ui/DialogContent.vue'\nimport DialogDescription from '../ui/DialogDescription.vue'\nimport DialogFooter from '../ui/DialogFooter.vue'\nimport DialogHeader from '../ui/DialogHeader.vue'\nimport DialogTitle from '../ui/DialogTitle.vue'\nimport Spinner from '../ui/Spinner.vue'\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nconst props = defineProps<{ table: TableSchema; token: string; component: string }>()\nconst emit = defineEmits<{ close: [] }>()\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\nconst t = useT()\nconst choice = useChoice()\nconst direction = useDirection()\nconst panel = usePanel()\nconst baseId = `spreadsheet-${useId()}`\nconst columns = computed(() => spreadsheetColumns(props.table))\nconst rows = computed(() => spreadsheetRows(props.table.records ?? [], columns.value))\nconst grid = new SpreadsheetGrid(rows.value, columns.value)\nconst state = useStore(grid.store)\nconst announcement = ref('')\nconst preview = ref<PastePreview | null>(null)\nconst gridElement = ref<HTMLTableElement | null>(null)\nconst helpId = `${baseId}-help`\n\n// A reload (save, live update, polling) brings new records; edits of rows still on the\n// page are kept.\nwatch([rows, columns], ([nextRows, nextColumns]) => grid.setData(nextRows, nextColumns))\n\nconst virtual = useVirtualList(\n () => rows.value.length,\n () => virtualizeOptions(props.table.virtualize, VIRTUAL_ESTIMATES.row),\n)\n\nconst endpoint = computed(() => panel.value.endpoints.tableCells)\nconst errorList = computed(() => {\n void state.value\n\n return grid.errorCells()\n})\n\nfunction announce(message: SpreadsheetAnnouncement | null) {\n if (message) announcement.value = choice(message.key, message.count, {}, message.fallback)\n}\n\nfunction focusCell(row: number, col: number) {\n void nextTick(() => {\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus()\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n virtual.list.scrollToIndex(row)\n }\n })\n}\n\n// Keep focus on the active cell (roving tabindex).\nwatch(\n () => [state.value.active, state.value.editing] as const,\n ([active, editing]) => {\n if (editing || !gridElement.value?.contains(document.activeElement)) return\n\n focusCell(active.row, active.col)\n },\n)\n\nasync function save() {\n const changes = grid.changes()\n\n if (!endpoint.value || changes.length === 0 || state.value.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(\n endpoint.value,\n props.token,\n props.component,\n changes,\n )\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) announce(spreadsheetAnnouncement('failed', result.errors.length))\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n}\n\nasync function undoSave() {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint.value || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(\n endpoint.value,\n props.token,\n props.component,\n changes,\n )\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n}\n\nfunction close() {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n emit('close')\n}\n\nfunction onKeyDown(event: KeyboardEvent) {\n const result = grid.handleKey(event, direction.value)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n focusCell(grid.state.active.row, grid.state.active.col)\n }\n}\n\nfunction onCopy(event: ClipboardEvent) {\n if (grid.state.editing || !event.clipboardData) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n}\n\nfunction onPaste(event: ClipboardEvent) {\n if (grid.state.editing || !event.clipboardData) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n const url = endpoint.value\n\n preview.value = { plan, errors: [], validating: Boolean(url && plan.changes.length > 0) }\n\n if (!url || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n saveSpreadsheetChanges(\n url,\n props.token,\n props.component,\n plan.changes.map((change) => ({\n record: grid.getRows()[change.row]?.key ?? '',\n column: grid.getColumns()[change.col]?.name ?? '',\n state: change.value,\n })),\n { dryRun: true },\n )\n .then((result) => {\n if (preview.value)\n preview.value = { ...preview.value, errors: result.errors, validating: false }\n })\n .catch(() => {\n if (preview.value) preview.value = { ...preview.value, validating: false }\n })\n}\n\nfunction applyPaste() {\n const current = preview.value\n\n if (!current) return\n\n const count = grid.apply(current.plan.changes)\n\n if (current.errors.length > 0) grid.setErrors(current.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n preview.value = null\n}\n\nfunction issueText(issue: PastePlan['issues'][number]): string {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns.value[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n}\n\nfunction errorText(record: SpreadsheetSaveResult['errors'][number]): string {\n const position = grid.position(record.record, record.column)\n\n return t(\n 'table.spreadsheet.cell_error',\n {\n row: (position?.row ?? -1) + 1,\n column: columns.value.find((column) => column.name === record.column)?.label ?? record.column,\n message: record.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )\n}\n\nfunction draft(): string {\n const value = state.value.editing?.value\n\n return value === null || value === undefined ? '' : String(value)\n}\n\nfunction isEditing(row: number, col: number): boolean {\n return state.value.editing?.row === row && state.value.editing.col === col\n}\n\nfunction onSelectChange(event: Event, row: number, col: number) {\n grid.updateDraft((event.target as HTMLSelectElement).value)\n grid.commitEdit()\n focusCell(row, col)\n}\n\nconst optionText = (label: string) => label.replace(/<[^>]*>/g, '')\n\n/** Focus an editor as soon as it is shown. */\nconst vFocus = { mounted: (element: HTMLElement) => element.focus() }\n</script>\n\n<template>\n <div data-slot=\"spreadsheet\" class=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" class=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n :disabled=\"grid.dirtyCount === 0 || state.saving\"\n @click=\"save\"\n >\n <Spinner v-if=\"state.saving\" />\n {{ t('table.spreadsheet.save', {}, 'Save changes') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n :disabled=\"!grid.canUndo || state.saving\"\n @click=\"announce(spreadsheetAnnouncement('undo', grid.undo()))\"\n >\n {{ t('table.spreadsheet.undo', {}, 'Undo') }}\n </Button>\n <Button\n v-if=\"grid.canUndoSave\"\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n :disabled=\"state.saving\"\n @click=\"undoSave\"\n >\n {{ t('table.spreadsheet.undo_save', {}, 'Undo last save') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n :disabled=\"grid.dirtyCount === 0 || state.saving\"\n @click=\"grid.discard()\"\n >\n {{ t('table.spreadsheet.discard', {}, 'Discard') }}\n </Button>\n <span class=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n <template v-if=\"grid.dirtyCount > 0\">{{\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )\n }}</template>\n <span v-if=\"grid.errorCount > 0\" class=\"text-destructive ms-2\">{{\n choice('table.spreadsheet.errors', grid.errorCount, {}, ':count error|:count errors')\n }}</span>\n </span>\n <span class=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" @click=\"close\">\n {{ t('table.spreadsheet.close', {}, 'Done') }}\n </Button>\n </div>\n <p :id=\"helpId\" class=\"sr-only\">\n {{\n t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )\n }}\n </p>\n <div data-surface=\"table\" :class=\"SPREADSHEET_CLASSES.root\" style=\"max-height: 70vh\">\n <table\n ref=\"gridElement\"\n role=\"grid\"\n :aria-label=\"props.table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')\"\n :aria-describedby=\"helpId\"\n :aria-rowcount=\"rows.length + 1\"\n :aria-colcount=\"columns.length\"\n aria-multiselectable=\"true\"\n :aria-busy=\"state.saving || undefined\"\n data-slot=\"spreadsheet-grid\"\n :class=\"SPREADSHEET_CLASSES.table\"\n @keydown=\"onKeyDown\"\n @copy=\"onCopy\"\n @paste=\"onPaste\"\n >\n <thead>\n <tr aria-rowindex=\"1\">\n <th\n v-for=\"(column, col) in columns\"\n :key=\"column.name\"\n role=\"columnheader\"\n scope=\"col\"\n :aria-colindex=\"col + 1\"\n :class=\"SPREADSHEET_CLASSES.header\"\n >\n {{ column.label }}\n </th>\n </tr>\n </thead>\n <tbody :ref=\"virtual.attach\">\n <VirtualSpacerRow :height=\"virtual.range.value.before\" :col-span=\"columns.length\" />\n <template v-for=\"row in virtual.indexes.value\" :key=\"String(rows[row]?.key ?? row)\">\n <tr v-if=\"rows[row]\" :ref=\"virtual.item\" :data-index=\"row\" :aria-rowindex=\"row + 2\">\n <td\n v-for=\"(column, col) in columns\"\n :id=\"spreadsheetCellId(baseId, row, col)\"\n :key=\"column.name\"\n role=\"gridcell\"\n v-bind=\"spreadsheetAria(row, col)\"\n :tabindex=\"grid.isActive(row, col) ? 0 : -1\"\n :aria-selected=\"grid.isSelected(row, col)\"\n :aria-readonly=\"!grid.isEditable(row, col) || undefined\"\n :aria-invalid=\"grid.errorsAt(row, col).length > 0 || undefined\"\n :title=\"grid.errorsAt(row, col).join(' ') || undefined\"\n data-slot=\"spreadsheet-cell\"\n :data-dirty=\"grid.isDirty(row, col) || undefined\"\n :data-active=\"grid.isActive(row, col) || undefined\"\n :class=\"spreadsheetCellClass(grid, row, col)\"\n @mousedown=\"!isEditing(row, col) && grid.moveTo(row, col, $event.shiftKey)\"\n @dblclick=\"grid.beginEdit()\"\n >\n <select\n v-if=\"isEditing(row, col) && column.kind === 'select'\"\n v-focus\n :class=\"SPREADSHEET_CLASSES.editor\"\n :aria-label=\"column.label\"\n :value=\"draft()\"\n @change=\"onSelectChange($event, row, col)\"\n @blur=\"grid.cancelEdit()\"\n >\n <option value=\"\">{{ t('table.spreadsheet.no_value', {}, '—') }}</option>\n <option\n v-for=\"option in column.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ optionText(option.label) }}\n </option>\n </select>\n <input\n v-else-if=\"isEditing(row, col)\"\n v-focus\n :type=\"column.kind === 'number' ? 'text' : (column.inputType ?? 'text')\"\n :inputmode=\"column.kind === 'number' ? 'decimal' : undefined\"\n :class=\"SPREADSHEET_CLASSES.editor\"\n :aria-label=\"column.label\"\n :value=\"draft()\"\n @input=\"grid.updateDraft(($event.target as HTMLInputElement).value)\"\n @blur=\"grid.commitEdit()\"\n />\n <span v-else-if=\"column.kind === 'boolean'\" class=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n class=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n :data-checked=\"Boolean(grid.value(row, col))\"\n >\n <CheckIcon v-if=\"grid.value(row, col)\" class=\"size-3\" />\n </span>\n <span class=\"sr-only\">{{\n grid.value(row, col)\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')\n }}</span>\n </span>\n <template v-else>{{ grid.text(row, col) }}</template>\n <span v-if=\"grid.errorsAt(row, col).length > 0\" class=\"sr-only\">{{\n grid.errorsAt(row, col).join(' ')\n }}</span>\n </td>\n </tr>\n </template>\n <VirtualSpacerRow :height=\"virtual.range.value.after\" :col-span=\"columns.length\" />\n </tbody>\n </table>\n <p v-if=\"rows.length === 0\" class=\"text-muted-foreground p-6 text-center text-sm\">\n {{ t('table.spreadsheet.empty', {}, 'No records to edit.') }}\n </p>\n </div>\n <ul\n v-if=\"errorList.length > 0\"\n data-slot=\"spreadsheet-errors\"\n class=\"text-destructive flex flex-col gap-1 text-sm\"\n >\n <li v-for=\"error in errorList\" :key=\"error.key\">\n <button\n type=\"button\"\n class=\"text-start hover:underline\"\n @click=\"\n () => {\n grid.moveTo(error.row, error.col)\n focusCell(error.row, error.col)\n }\n \"\n >\n {{\n t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )\n }}\n </button>\n </li>\n </ul>\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n class=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >{{ announcement }}</span\n >\n\n <Dialog :open=\"preview !== null\" @update:open=\"(value) => !value && (preview = null)\">\n <DialogContent class=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{{ t('table.spreadsheet.paste_title', {}, 'Paste cells') }}</DialogTitle>\n <DialogDescription>\n <!-- One line per count: translations can't join two sentences. -->\n <span class=\"block\">{{\n choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )\n }}</span>\n <span\n v-if=\"(preview?.plan.skipped ?? 0) > 0\"\n class=\"block\"\n data-slot=\"spreadsheet-paste-skipped\"\n >\n {{\n choice(\n 'table.spreadsheet.paste_skipped',\n preview?.plan.skipped ?? 0,\n {},\n ':count read-only cell is skipped|:count read-only cells are skipped',\n )\n }}\n </span>\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" class=\"flex flex-col gap-3\">\n <p\n v-if=\"preview?.validating\"\n class=\"text-muted-foreground flex items-center gap-2 text-sm\"\n >\n <Spinner />\n {{ t('table.spreadsheet.paste_validating', {}, 'Checking the values…') }}\n </p>\n <ul\n v-if=\"preview && (preview.plan.issues.length > 0 || preview.errors.length > 0)\"\n class=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\"\n >\n <li v-for=\"issue in preview.plan.issues\" :key=\"`issue-${issue.row}-${issue.col}`\">\n {{ issueText(issue) }}\n </li>\n <li\n v-for=\"error in preview.errors\"\n :key=\"`error-${String(error.record)}-${error.column}`\"\n >\n {{ errorText(error) }}\n </li>\n </ul>\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" @click=\"preview = null\">{{\n t('cancel', {}, 'Cancel')\n }}</Button>\n <Button\n type=\"button\"\n :disabled=\"!preview || preview.plan.changes.length === 0\"\n @click=\"applyPaste\"\n >\n {{ t('table.spreadsheet.paste_apply', {}, 'Apply') }}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CA,IAAM6D,IAAQC,GACRC,KAAOH,GAQPI,IAAIC,EAAK,GACTC,IAASC,EAAU,GACnBC,KAAYC,EAAa,GACzBC,KAAQC,EAAS,GACjBC,IAAS,eAAeC,GAAM,KAC9BC,IAAUC,QAAeC,GAAmBf,EAAM,KAAK,CAAC,GACxDgB,IAAOF,QAAeG,GAAgBjB,EAAM,MAAM,WAAW,CAAC,GAAGa,EAAQ,KAAK,CAAC,GAC/EK,IAAO,IAAIC,GAAgBH,EAAK,OAAOH,EAAQ,KAAK,GACpDO,IAAQC,GAASH,EAAK,KAAK,GAC3BI,IAAeC,EAAI,EAAE,GACrBC,IAAUD,EAAyB,IAAI,GACvCE,IAAcF,EAA6B,IAAI,GAC/CG,IAAS,GAAGf,EAAO;EAIzB,EAAM,CAACK,GAAMH,CAAO,IAAI,CAACc,GAAUC,OAAiBV,EAAK,QAAQS,GAAUC,CAAW,CAAC;EAEvF,IAAMC,IAAUC,SACRd,EAAK,MAAM,cACXe,GAAkB/B,EAAM,MAAM,YAAYgC,GAAkB,GAAG,CACvE,GAEMC,IAAWnB,QAAeL,GAAM,MAAM,UAAU,UAAU,GAC1DyB,IAAYpB,SAChBM,EAAW,OAEJF,EAAK,WAAW,EACxB;EAED,SAASiB,EAAS,GAAyC;GACzD,AAAIC,MAAS,EAAa,QAAQ/B,EAAO+B,EAAQ,KAAKA,EAAQ,OAAO,CAAC,GAAGA,EAAQ,QAAQ;EAC3F;EAEA,SAASC,EAAU,GAAa,GAAa;GAC3C,SAAoB;IAClB,IAAMC,IAAO,SAAS,eAAeC,EAAkB5B,GAAQ6B,GAAKC,CAAG,CAAC;IAExE,AAAIH,KACFA,EAAK,MAAM,GACXA,EAAK,iBAAiB;KAAE,OAAO;KAAW,QAAQ;IAAU,CAAC,KAE7DT,EAAQ,KAAK,cAAcW,CAAG;GAElC,CAAC;EACH;EAGA,QACQ,CAACpB,EAAM,MAAM,QAAQA,EAAM,MAAM,OAAO,IAC7C,CAACsB,GAAQC,OAAa;GACrB,AAAI,MAAYlB,EAAY,OAAO,SAAS,SAAS,aAAa,KAElEY,EAAUK,EAAO,KAAKA,EAAO,GAAG;EAClC,CACF;EAEA,eAAeE,IAAO;GACpB,IAAMC,IAAU3B,EAAK,QAAQ;GAE7B,IAAKe,EAAS,SAASY,EAAQ,WAAW,KAAK,GAAM,MAAM,QAE3D;MAAK,UAAU,EAAI;IAEnB,IAAI;KACF,IAAMC,IAAS,MAAMC,EACnBd,EAAS,OACTjC,EAAM,OACNA,EAAM,WACN6C,CACF;KASA,AAPA3B,EAAK,gBAAgB4B,CAAM,GAEvBA,EAAO,MAAM,SAAS,MACxBX,EAASa,EAAwB,SAASF,EAAO,MAAM,MAAM,CAAC,GAC9DG,EAAc,IAGZH,EAAO,OAAO,SAAS,KAAGX,EAASa,EAAwB,UAAUF,EAAO,OAAO,MAAM,CAAC;IAChG,SAASI,GAAO;KACd,GAAU;MACR,IAAI;MACJ,OAAO/C,EAAE,oCAAoC,CAAC,GAAG,2BAA2B;MAC5E,MAAM+C,aAAiB,QAAQA,EAAM,UAAU,OAAOA,CAAK;MAC3D,QAAQ;MACR,QAAQ;KACV,CAAC;IACH,UAAU;KACR,EAAK,UAAU,EAAK;IACtB;GA5BmB;EA6BrB;EAEA,eAAeC,KAAW;GACxB,IAAMN,IAAU3B,EAAK,gBAAgB;GAErC,IAAKe,EAAS,SAASY,EAAQ,WAAW,GAE1C;MAAK,UAAU,EAAI;IAEnB,IAAI;KACF,IAAMC,IAAS,MAAMC,EACnBd,EAAS,OACTjC,EAAM,OACNA,EAAM,WACN6C,CACF;KAKA,AAHA3B,EAAK,gBAAgB4B,CAAM,GAC3BX,EAASa,EAAwB,QAAQF,EAAO,MAAM,MAAM,CAAC,GAEzDA,EAAO,MAAM,SAAS,KAAGG,EAAc;IAC7C,UAAU;KACR,EAAK,UAAU,EAAK;IACtB;GAhBmB;EAiBrB;EAEA,SAASG,KAAQ;GACf,AACElC,EAAK,aAAa,KAClB,CAAC,OAAO,QAAQf,EAAE,qCAAqC,CAAC,GAAG,+BAA+B,CAAC,MAK7Fe,EAAK,QAAQ,GACbhB,GAAK,OAAO;EACd;EAEA,SAASmD,GAAU,GAAsB;GACvC,IAAMP,IAAS5B,EAAK,UAAUoC,GAAO/C,GAAU,KAAK;GAEpD,AAAIuC,MAAW,cAEfQ,EAAM,eAAe,GAEjBR,MAAW,SACbF,EAAU,IACDE,MAAW,SACpBX,EAASa,EAAwB,QAAQ9B,EAAK,KAAK,CAAC,CAAC,KAC5C4B,MAAW,UAAUA,MAAW,YACzCX,EAASa,EAAwBF,GAAQ5B,EAAK,WAAW,CAAC,IAGxD4B,MAAW,YAAYA,MAAW,YAAYA,MAAW,aAC3DT,EAAUnB,EAAK,MAAM,OAAO,KAAKA,EAAK,MAAM,OAAO,GAAG;EAE1D;EAEA,SAASqC,GAAO,GAAuB;GACrC,AAAI,GAAK,MAAM,WAAYD,EAAM,kBAEjCA,EAAM,eAAe,GACrBA,EAAM,cAAc,QAAQ,cAAcpC,EAAK,SAAS,CAAC,GACzDiB,EAASa,EAAwB,QAAQQ,GAAyBtC,EAAK,OAAO,CAAC,CAAC,CAAC;EACnF;EAEA,SAASuC,GAAQ,GAAuB;GACtC,IAAIvC,EAAK,MAAM,WAAW,CAACoC,EAAM,eAAe;GAEhD,EAAM,eAAe;GAErB,IAAMI,IAAOxC,EAAK,UAAUoC,EAAM,cAAc,QAAQ,YAAY,CAAC;GAErE,IAAII,EAAK,QAAQ,WAAW,KAAKA,EAAK,OAAO,WAAW,GAAG;GAE3D,IAAMC,IAAM1B,EAAS;GAIrB,AAFA,EAAQ,QAAQ;IAAE;IAAM,QAAQ,CAAC;IAAG,YAAY,GAAQ0B,KAAOD,EAAK,QAAQ,SAAS;GAAG,GAEnFC,KAAOD,EAAK,QAAQ,WAAW,KAGpCX,EACEY,GACA3D,EAAM,OACNA,EAAM,WACN0D,EAAK,QAAQ,KAAK,OAAY;IAC5B,QAAQxC,EAAK,QAAQ,CAAC,CAAC0C,EAAO,IAAI,EAAE,OAAO;IAC3C,QAAQ1C,EAAK,WAAW,CAAC,CAAC0C,EAAO,IAAI,EAAE,QAAQ;IAC/C,OAAOA,EAAO;GAChB,EAAE,GACF,EAAE,QAAQ,GAAK,CACjB,CAAA,CACG,MAAM,MAAW;IAChB,AACEpC,EAAQ,UAAQ;KAAE,GAAGA,EAAQ;KAAO,QAAQsB,EAAO;KAAQ,YAAY;IAAM;GACjF,CAAC,CAAA,CACA,YAAY;IACX,AAAmBtB,EAAQ,UAAQ;KAAE,GAAGA,EAAQ;KAAO,YAAY;IAAM;GAC3E,CAAC;EACL;EAEA,SAASqC,KAAa;GACpB,IAAMC,IAAUtC,EAAQ;GAExB,IAAI,CAACsC,GAAS;GAEd,IAAMC,IAAQ7C,EAAK,MAAM4C,EAAQ,KAAK,OAAO;GAK7C,AAHIA,EAAQ,OAAO,SAAS,KAAG5C,EAAK,UAAU4C,EAAQ,MAAM,GAE5D3B,EAASa,EAAwB,SAASe,CAAK,CAAC,GAChD,EAAQ,QAAQ;EAClB;EAEA,SAASC,GAAU,GAA4C;GAC7D,IAAM5B,IAAU6B,GAA2BC,EAAM;GAEjD,OAAO/D,EACLiC,EAAQ,KACR;IAAE,MAAM8B,EAAM;IAAM,QAAQrD,EAAQ,MAAMqD,EAAM,IAAI,EAAE,SAAS;IAAI,KAAKA,EAAM,MAAM;GAAE,GACtF9B,EAAQ,QACV;EACF;EAEA,SAAS+B,GAAU,GAAyD;GAC1E,IAAMC,IAAWlD,EAAK,SAASmD,EAAO,QAAQA,EAAO,MAAM;GAE3D,OAAOlE,EACL,gCACA;IACE,MAAMiE,GAAU,OAAO,MAAM;IAC7B,QAAQvD,EAAQ,MAAM,MAAM,MAAWyD,EAAO,SAASD,EAAO,MAAM,CAAC,EAAE,SAASA,EAAO;IACvF,SAASA,EAAO,SAAS,KAAK,GAAG;GACnC,GACA,6BACF;EACF;EAEA,SAASE,IAAgB;GACvB,IAAMC,IAAQpD,EAAM,MAAM,SAAS;GAEnC,OAAOoD,KAAU,OAA8B,KAAK,OAAOA,CAAK;EAClE;EAEA,SAASC,EAAU,GAAa,GAAsB;GACpD,OAAOrD,EAAM,MAAM,SAAS,QAAQoB,KAAOpB,EAAM,MAAM,QAAQ,QAAQqB;EACzE;EAEA,SAASiC,GAAe,GAAc,GAAa,GAAa;GAG9D,AAFAxD,EAAK,YAAaoC,EAAM,OAA6B,KAAK,GAC1DpC,EAAK,WAAW,GAChBmB,EAAUG,GAAKC,CAAG;EACpB;EAEA,IAAMkC,MAAc,MAAkBC,EAAM,QAAQ,YAAY,EAAE,GAG5DC,IAAS,EAAE,UAAU,MAAyBC,EAAQ,MAAM,EAAE;oBAIlE3I,EAAA,GAAAC,EAyRM,OAzRNgC,IAyRM;GAxRJ/B,EAuDM,OAvDNgC,IAuDM;IAtDJ/B,EAQSC,GAAA;KAPP,MAAK;KACL,MAAK;KACJ,UAAUC,EAAA0E,CAAA,CAAI,CAAC,eAAU,KAAU1E,EAAA4E,CAAA,CAAK,CAAC;KACzC,SAAOwB;;KAER,SAAAnG,QAA+B,CAAhBD,EAAA4E,CAAA,CAAK,CAAC,UAArBjF,EAAA,GAAAO,EAA+BC,GAAA,EAAA,KAAA,EAAA,CAAA,KAAA2I,EAAA,IAAA,EAAA,GAAAzI,EAAA,MAC/BC,EAAGN,EAAA2D,CAAA,CAAC,CAAA,0BAAA,CAAA,GAAA,cAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;IAEN7D,EAQSC,GAAA;KAPP,MAAK;KACL,MAAK;KACL,SAAQ;KACP,UAAQ,CAAGC,EAAA0E,CAAA,CAAI,CAAC,WAAW1E,EAAA4E,CAAA,CAAK,CAAC;KACjC,SAAK,AAAAqE,EAAA,QAAA,MAAEtD,EAAS3F,EAAAwG,CAAA,CAAuB,CAAA,QAASxG,EAAA0E,CAAA,CAAI,CAAC,KAAI,CAAA,CAAA;;KAE1D,SAAAzE,QAA6C,CAA1CI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,0BAAA,CAAA,GAAA,MAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;IAGE3D,EAAA0E,CAAA,CAAI,CAAC,eADb/E,EAAA,GAAAO,EASSH,GAAA;;KAPP,MAAK;KACL,MAAK;KACL,SAAQ;KACP,UAAUC,EAAA4E,CAAA,CAAK,CAAC;KAChB,SAAO+B;;KAER,SAAA1G,QAA4D,CAAzDI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,+BAAA,CAAA,GAAA,gBAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;IAEN7D,EAQSC,GAAA;KAPP,MAAK;KACL,MAAK;KACL,SAAQ;KACP,UAAUC,EAAA0E,CAAA,CAAI,CAAC,eAAU,KAAU1E,EAAA4E,CAAA,CAAK,CAAC;KACzC,SAAK,AAAAqE,EAAA,QAAA,MAAEjJ,EAAA0E,CAAA,CAAI,CAAC,QAAO;;KAEpB,SAAAzE,QAAmD,CAAhDI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,6BAAA,CAAA,GAAA,SAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;IAEN9D,EAYO,QAZPiC,GAYO,CAXW9B,EAAA0E,CAAA,CAAI,CAAC,aAAU,KAA/B/E,EAAA,GAAAC,EAOaW,GAAA,EAAA,KAAA,EAAA,GAAA,CANXF,EAAAC,EAAAN,EAAA6D,CAAA,CAAM,CAAA,6BAAuD7D,EAAA0E,CAAA,CAAI,CAAC,YAAA,CAAA,GAAA,8CAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAAoE,EAAA,IAAA,EAAA,GAOxD9I,EAAA0E,CAAA,CAAI,CAAC,aAAU,KAA3B/E,EAAA,GAAAC,EAES,QAFTmC,IAESzB,EADPN,EAAA6D,CAAA,CAAM,CAAA,4BAA6B7D,EAAA0E,CAAA,CAAI,CAAC,YAAU,CAAA,GAAA,4BAAA,CAAA,GAAA,CAAA,KAAAoE,EAAA,IAAA,EAAA,CAAA,CAAA;IAGtD,AAAAG,EAAA,OAAApJ,EAAuB,QAAA,EAAjB,OAAM,SAAQ,GAAA,MAAA,EAAA;IACpBC,EAESC,GAAA;KAFD,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAW,SAAO6G;;KACxD,SAAA3G,QAA8C,CAA3CI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,2BAAA,CAAA,GAAA,MAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;;GAGR9D,EAQI,KAAA;IARA,IAAIqF;IAAQ,OAAM;GAElB,GAAA5E,EAAAN,EAAA2D,CAAA,CAAC,CAAA,0BAAA,CAAA,GAAA,uLAAA,CAAA,GAAA,CAAA;GAOL9D,EA6GM,OAAA;IA7GD,gBAAa;IAAS,OAAKW,EAAER,EAAAoJ,CAAA,CAAmB,CAAC,IAAI;IAAE,OAAA,EAAA,cAAA,OAAA;GAC1DvJ,GAAAA,CAAAA,EAwGQ,SAAA;IAvGF,SAAA;IAAJ,KAAIoF;IACJ,MAAK;IACJ,cAAYzB,EAAM,MAAM,WAAWxD,EAAA2D,CAAA,CAAC,CAAA,2BAAA,CAAA,GAAA,aAAA;IACpC,oBAAkBuB;IAClB,iBAAeV,EAAA,MAAK,SAAM;IAC1B,iBAAeH,EAAA,MAAQ;IACxB,wBAAqB;IACpB,aAAWrE,EAAA4E,CAAA,CAAK,CAAC,UAAU,KAAA;IAC5B,aAAU;IACT,OAAKpE,EAAER,EAAAoJ,CAAA,CAAmB,CAAC,KAAK;IAChC,WAASvC;IACH;IACC;GAERhH,GAAAA,CAAAA,EAaQ,SAAA,MAAA,CAZNA,EAWK,MAXLoC,IAWK,EAVHtC,EAAA,EAAA,GAAAC,EASKW,GAAA,MAAAE,EARqB4D,EAAA,QAAhB,GAAQ,OADlB1E,EAAA,GAAAC,EASK,MAAA;IAPF,KAAKkI,EAAO;IACb,MAAK;IACL,OAAM;IACL,iBAAe7B,IAAG;IAClB,OAAKzF,EAAER,EAAAoJ,CAAA,CAAmB,CAAC,MAAM;GAE/B,GAAA9I,EAAAwH,EAAO,KAAK,GAAA,IAAA5F,EAAA,EAIrBrC,GAAAA,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,GAAAA,EA0EQ,SAAA,EA1EA,KAAKG,EAAAqF,CAAA,CAAO,CAAC,OAAA,GAAA;IACnBvF,EAAoFY,GAAA;KAAjE,QAAQV,EAAAqF,CAAA,CAAO,CAAC,MAAM,MAAM;KAAS,YAAUhB,EAAA,MAAQ;;KAC1E1E,EAAA,EAAA,GAAAC,EAsEWW,GAAA,MAAAE,EAtEaT,EAAAqF,CAAA,CAAO,CAAC,QAAQ,QAAvB,OAAoCkD,EAAA,GAAAC,EAAAU,GAAA,EAAA,KAAA,OAAO1E,EAAA,MAAKwB,EAAG,EAAG,OAAOA,CAAG,EAAA,GAAA,CACrExB,EAAA,MAAKwB,MAAfrG,EAAA,GAAAC,EAoEK,MAAA;;;KApEiB,KAAKI,EAAAqF,CAAA,CAAO,CAAC;KAAO,cAAYW;KAAM,iBAAeA,IAAG;IAC5ErG,GAAAA,EAAAA,EAAA,EAAA,GAAAC,EAkEKW,GAAA,MAAAE,EAjEqB4D,EAAA,QAAhB,GAAQ,OADlB1E,EAAA,GAAAC,EAkEK,MAlELe,GAkEK;KAhEF,IAAIX,EAAA+F,CAAA,CAAiB,CAAC5B,GAAQ6B,GAAKC,CAAG;KACtC,KAAK6B,EAAO;KACb,MAAK;IACG,GAAA,EAAA,SAAA,GAAA,GAAA9H,EAAAuJ,EAAA,CAAe,CAACvD,GAAKC,CAAG,GAAA;KAC/B,UAAUjG,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,IAAA,IAAA;KAChC,iBAAejG,EAAA0E,CAAA,CAAI,CAAC,WAAWsB,GAAKC,CAAG;KACvC,iBAAa,CAAGjG,EAAA0E,CAAA,CAAI,CAAC,WAAWsB,GAAKC,CAAG,KAAK,KAAA;KAC7C,gBAAcjG,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,CAAA,CAAE,SAAM,KAAQ,KAAA;KACpD,OAAOjG,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,CAAA,CAAE,KAAI,GAAA,KAAS,KAAA;KAC7C,aAAU;KACT,cAAYjG,EAAA0E,CAAA,CAAI,CAAC,QAAQsB,GAAKC,CAAG,KAAK,KAAA;KACtC,eAAajG,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,KAAK,KAAA;KACxC,OAAOjG,EAAAwJ,EAAA,CAAoB,CAACxJ,EAAA0E,CAAA,GAAMsB,GAAKC,CAAG;KAC1C,cAAS,MAAA,CAAGgC,EAAUjC,GAAKC,CAAG,KAAKjG,EAAA0E,CAAA,CAAI,CAAC,OAAOsB,GAAKC,GAAKwD,EAAO,QAAQ;KACxE,YAAQ,AAAAR,EAAA,QAAA,MAAEjJ,EAAA0E,CAAA,CAAI,CAAC,UAAS;IAGjB,CAAA,GAAA,CAAAuD,EAAUjC,GAAKC,CAAG,KAAK6B,EAAO,SAAI,WAD1ClH,GAAAjB,EAAA,GAAAC,EAkBS,UAAA;;KAfN,OAAKY,EAAER,EAAAoJ,CAAA,CAAmB,CAAC,MAAM;KACjC,cAAYtB,EAAO;KACnB,OAAOC,EAAK;KACZ,WAAM,MAAEG,GAAeuB,GAAQzD,GAAKC,CAAG;KACvC,QAAI,AAAAgD,EAAA,QAAA,MAAEjJ,EAAA0E,CAAA,CAAI,CAAC,WAAU;IAEtB7E,GAAAA,CAAAA,EAAwE,UAAxEyC,IAAwEhC,EAApDN,EAAA2D,CAAA,CAAC,CAAA,8BAAA,CAAA,GAAA,GAAA,CAAA,GAAA,CAAA,IACrBhE,EAAA,EAAA,GAAAC,EAOSW,GAAA,MAAAE,EANUqH,EAAO,UAAjB,OADTnI,EAAA,GAAAC,EAOS,UAAA;KALN,KAAK+J,EAAO;KACZ,OAAOA,EAAO;KACd,UAAUA,EAAO;IAEf,GAAArJ,EAAA6H,GAAWwB,EAAO,KAAK,CAAA,GAAA,GAAApH,EAAA,EAIjB,GAAA,GAAA,EAAA,GAAA,IAAAF,EAAA,IAAA,CAAA,CAAAgG,CAAA,CAAA,CAAA,IAAAJ,EAAUjC,GAAKC,CAAG,IAD/BrF,GAAAjB,EAAA,GAAAC,EAUE,SAAA;;KAPC,MAAMkI,EAAO,SAAI,WAAA,SAA0BA,EAAO,aAAS;KAC3D,WAAWA,EAAO,SAAI,WAAA,YAA4B,KAAA;KAClD,OAAKtH,EAAER,EAAAoJ,CAAA,CAAmB,CAAC,MAAM;KACjC,cAAYtB,EAAO;KACnB,OAAOC,EAAK;KACZ,SAAK,AAAAkB,EAAA,QAAA,MAAEjJ,EAAA0E,CAAA,CAAI,CAAC,YAAa+E,EAAO,OAA4B,KAAK;KACjE,QAAI,AAAAR,EAAA,QAAA,MAAEjJ,EAAA0E,CAAA,CAAI,CAAC,WAAU;IAEP,GAAA,MAAA,IAAAlC,EAAA,IAAA,CAAA,CAAA6F,CAAA,CAAA,CAAA,IAAAP,EAAO,SAAI,aAA5BnI,EAAA,GAAAC,EAaO,QAbP6C,IAaO,CAZL5C,EAMO,QAAA;KALL,eAAY;KACZ,OAAM;KACL,gBAAc,EAAQG,EAAA0E,CAAA,CAAI,CAAC,MAAMsB,GAAKC,CAAG;IAEzBjG,GAAAA,CAAAA,EAAA0E,CAAA,CAAI,CAAC,MAAMsB,GAAKC,CAAG,KAApCtG,EAAA,GAAAO,EAAwDF,EAAA4J,EAAA,GAAA;;KAAjB,OAAM;IAE/C/J,CAAAA,KAAAA,EAAAA,IAAAA,EAAAA,CAAAA,GAAAA,GAAAA,EAAAA,GAAAA,EAIS,QAJT8C,IAISrC,EAHPN,EAAA0E,CAAA,CAAI,CAAC,MAAMsB,GAAKC,CAAG,IAA0BjG,EAAA2D,CAAA,CAAC,CAAA,yBAAA,CAAA,GAAA,KAAA,IAA6D3D,EAAA2D,CAAA,CAAC,CAAA,wBAAA,CAAA,GAAA,IAAA,CAAA,GAAA,CAAA,CAAA,CAAA,MAKhHhE,EAAA,GAAAC,EAAqDW,GAAA,EAAA,KAAA,EAAA,GAAA,CAAjCF,EAAAC,EAAAN,EAAA0E,CAAA,CAAI,CAAC,KAAKsB,GAAKC,CAAG,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,IAC1BjG,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,CAAA,CAAE,SAAM,KAA1CtG,EAAA,GAAAC,EAES,QAFTgD,IAEStC,EADPN,EAAA0E,CAAA,CAAI,CAAC,SAASsB,GAAKC,CAAG,CAAA,CAAE,KAAI,GAAA,CAAA,GAAA,CAAA,KAAA6C,EAAA,IAAA,EAAA,CAAA,GAAA,IAAA1G,CAAA;IAKpCtC,EAAmFY,GAAA;KAAhE,QAAQV,EAAAqF,CAAA,CAAO,CAAC,MAAM,MAAM;KAAQ,YAAUhB,EAAA,MAAQ;;GAGpE,GAAA,GAAA,CAAA,GAAA,IAAArC,EAAA,GAAAwC,EAAA,MAAK,WAAM,KAApB7E,EAAA,GAAAC,EAEI,KAFJiD,IAEIvC,EADCN,EAAA2D,CAAA,CAAC,CAAA,2BAAA,CAAA,GAAA,qBAAA,CAAA,GAAA,CAAA,KAAAmF,EAAA,IAAA,EAAA,CAAA,GAAA,CAAA;GAIApD,EAAA,MAAU,SAAM,KADxB/F,EAAA,GAAAC,EA6BK,MA7BLkD,IA6BK,EAxBHnD,EAAA,EAAA,GAAAC,EAuBKW,GAAA,MAAAE,EAvBeiF,EAAA,QAAT,OAAX/F,EAAA,GAAAC,EAuBK,MAAA,EAvB2B,KAAK8G,EAAM,IAAA,GAAA,CACzC7G,EAqBS,UAAA;IApBP,MAAK;IACL,OAAM;IACL,eAAA;KAA0F,AAAhD8I,EAAAjE,CAAA,CAAI,CAAC,OAAOgC,EAAM,KAAKA,EAAM,GAAG,GAAgBb,EAAUa,EAAM,KAAKA,EAAM,GAAG;;GAQvH,GAAApG,EAAAN,EAAA2D,CAAA,CAAC,CAAA,gCAAA;IAAqF,KAAA+C,EAAM,MAAG;IAA8B,QAAArC,EAAA,MAAQqC,EAAM,IAAG,EAAG,SAAK;IAAiC,SAAAA,EAAM,SAAS,KAAI,GAAA;;GAalN7G,EAMC,QANDmD,IAMC1C,EADKwE,EAAA,KAAY,GAAA,CAAA;GAGlBhF,EAkESe,IAAA;IAlEA,MAAMmE,EAAA,UAAO;IAAY,iBAAW,AAAAiE,EAAA,QAAG,MAAK,CAAMjB,MAAU,EAAA,QAAO;;IAC1E,SAAA/H,QAgEgB,CAhEhBH,EAgEgBgB,IAAA,EAhED,OAAM,cAAa,GAAA;KAChC,SAAAb,QA2Be;MA3BfH,EA2BeiB,IAAA,MAAA;OA1Bb,SAAAd,QAAsF,CAAtFH,EAAsFkB,IAAA,MAAA;QAAzE,SAAAf,QAA2D,CAAxDI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,iCAAA,CAAA,GAAA,aAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;OACjB7D,CAAAA,GAAAA,EAwBoBmB,IAAA,MAAA;QAtBlB,SAAAhB,QAOS,CAPTJ,EAOS,QAPToD,IAOS3C,EANPN,EAAA6D,CAAA,CAAM,CAAA,mCAAqEmB,EAAA,OAAS,KAAK,QAAQ,UAAM,GAAA,CAAA,GAAA,kDAAA,CAAA,GAAA,CAAA,IAQhGA,EAAA,OAAS,KAAK,WAAO,KAAA,KAD9BrF,EAAA,GAAAC,EAaO,QAbPsD,IAaO5C,EAPHN,EAAA6D,CAAA,CAAM,CAAA,mCAAyEmB,EAAA,OAAS,KAAK,WAAO,GAAA,CAAA,GAAA,qEAAA,CAAA,GAAA,CAAA,KAAA8D,EAAA,IAAA,EAAA,CAAA,CAAA;;;;;MAU5GjJ,EAsBM,OAtBNsD,IAsBM,CApBI6B,EAAA,OAAS,cADjBrF,EAAA,GAAAC,EAMI,KANJwD,IAMI,CAFFtD,EAAWK,CAAA,GAAAE,EAAA,MACXC,EAAGN,EAAA2D,CAAA,CAAC,CAAA,sCAAA,CAAA,GAAA,sBAAA,CAAA,GAAA,CAAA,CAAA,CAAA,KAAAmF,EAAA,IAAA,EAAA,GAGE9D,EAAA,UAAYA,EAAA,MAAQ,KAAK,OAAO,SAAM,KAAQA,EAAA,MAAQ,OAAO,SAAM,MAD3ErF,EAAA,GAAAC,EAaK,MAbLyD,IAaK,EATH1D,EAAA,EAAA,GAAAC,EAEKW,GAAA,MAAAE,EAFeuE,EAAA,MAAQ,KAAK,SAAtB,OAAXrF,EAAA,GAAAC,EAEK,MAAA,EAFqC,KAAG,SAAW8H,EAAM,IAAG,GAAIA,EAAM,MACtE,GAAApH,EAAAkH,GAAUE,CAAK,CAAA,GAAA,CAAA,EAEpB/H,GAAAA,GAAAA,KAAAA,EAAA,EAAA,GAAAC,EAKKW,GAAA,MAAAE,EAJauE,EAAA,MAAQ,SAAjB,OADTrF,EAAA,GAAAC,EAKK,MAAA,EAHF,KAAG,SAAW,OAAO8G,EAAM,MAAM,EAAA,GAAKA,EAAM,SAE1C,GAAApG,EAAAqH,GAAUjB,CAAK,CAAA,GAAA,CAAA;MAIxB5G,EAWeoB,IAAA,MAAA;OAVb,SAAAjB,QAEW,CAFXH,EAEWC,GAAA;QAFH,MAAK;QAAS,SAAQ;QAAW,SAAK,AAAAkJ,EAAA,QAAA,MAAE,EAAA,QAAO;;QAAS,SAAAhJ,QAE9D,CADAI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,UAAA,CAAA,GAAA,QAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;OAEH7D,CAAAA,GAAAA,EAMSC,GAAA;QALP,MAAK;QACJ,UAAQ,CAAGiF,EAAA,SAAWA,EAAA,MAAQ,KAAK,QAAQ,WAAM;QACjD,SAAOqC;;QAER,SAAApH,QAAqD,CAAlDI,EAAAC,EAAAN,EAAA2D,CAAA,CAAC,CAAA,iCAAA,CAAA,GAAA,OAAA,CAAA,GAAA,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationManagerView.js","names":["_sfc_main","TranslationManagerView_vue_vue_type_script_setup_true_lang_default","TranslationManagerView_default"],"sources":["../../../src/components/translation-manager/TranslationManagerView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n TranslationCellRef,\n TranslationFilter,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from '@lucide/vue'\nimport { computed, nextTick, onBeforeUnmount, toRaw, useId, watch } from 'vue'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../composables/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Card from '../ui/Card.vue'\nimport CardContent from '../ui/CardContent.vue'\nimport CardHeader from '../ui/CardHeader.vue'\nimport DropdownMenu from '../ui/DropdownMenu.vue'\nimport DropdownMenuCheckboxItem from '../ui/DropdownMenuCheckboxItem.vue'\nimport DropdownMenuContent from '../ui/DropdownMenuContent.vue'\nimport DropdownMenuTrigger from '../ui/DropdownMenuTrigger.vue'\nimport Input from '../ui/Input.vue'\nimport Textarea from '../ui/Textarea.vue'\n\n/**\n * Keys by locale with search, filters, inline editing (plural forms, right-to-left\n * inputs), keyboard navigation, AI suggestion review and history. Everything is computed\n * and saved by the server.\n */\nconst props = defineProps<{ schema: TranslationManagerSchema }>()\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst runner = useActionRunner()\nconst page = usePageActionContext()\nconst t = useT()\nconst direction = useDirection()\nconst formatter = useFormatter()\nconst labelId = useId()\nconst hintId = useId()\nconst errorId = useId()\n\nconst environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n})\n\nconst controller = new TranslationManagerController(toRaw(props.schema), environment())\nconst state = useStore(controller.store)\nconst cells = new Map<string, HTMLElement>()\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\nwatch(\n () => props.schema,\n (schema) => controller.setSchema(toRaw(schema)),\n)\n\nonBeforeUnmount(() => controller.dispose())\n\nconst locales = computed(() => {\n void state.value.visibleLocales\n\n return controller.visibleLocales()\n})\nconst active = computed<TranslationCellRef | null>(() => {\n const current = state.value.active\n\n if (current && state.value.rows.some((row) => row.id === current.row)) return current\n\n const row = state.value.rows[0]\n const locale = locales.value[0]\n\n return row && locale ? { row: row.id, locale: locale.code } : null\n})\nconst range = computed(() => {\n void state.value.rows\n\n return controller.range()\n})\nconst suggestionLocales = computed(() => {\n void state.value.rows\n\n return controller.suggestionLocales()\n})\nconst activeRow = computed(() =>\n active.value ? state.value.rows.find((row) => row.id === active.value?.row) : undefined,\n)\nconst activeLocale = computed(() =>\n active.value ? controller.locale(active.value.locale) : undefined,\n)\nconst activeCell = computed(() =>\n activeRow.value && activeLocale.value\n ? activeRow.value.cells[activeLocale.value.code]\n : undefined,\n)\n\nconst isActive = (ref: TranslationCellRef) =>\n active.value?.row === ref.row && active.value.locale === ref.locale\nconst isEditing = (ref: TranslationCellRef) =>\n state.value.editing?.row === ref.row && state.value.editing.locale === ref.locale\nconst editable = (ref: TranslationCellRef) => {\n void state.value.rows\n\n return controller.isEditable(ref)\n}\n\nconst setCell = (ref: TranslationCellRef) => (element: unknown) => {\n if (element instanceof HTMLElement) cells.set(cellKey(ref), element)\n else cells.delete(cellKey(ref))\n}\n\nconst focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n void nextTick(() => cells.get(cellKey(ref))?.focus())\n}\n\nwatch(\n () => state.value.editing,\n (editing) => {\n if (editing) {\n void nextTick(() => cells.get(cellKey(editing))?.querySelector('textarea')?.focus())\n }\n },\n)\n\nconst onCellKeyDown = (ref: TranslationCellRef, event: KeyboardEvent) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction.value)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n}\n\nconst onEditorKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' || (event.key === 'Enter' && !event.shiftKey) || event.key === 'Tab') {\n event.preventDefault()\n void controller\n .handleEditorKey({ key: event.key, shiftKey: event.shiftKey, isComposing: event.isComposing })\n .then((result) => focusCell(result.focus))\n }\n}\n\nconst onCellClick = (ref: TranslationCellRef) => {\n if (isEditing(ref)) return\n const wasActive = isActive(ref)\n controller.focus(ref)\n if (wasActive) controller.startEdit(ref)\n}\n\nconst save = (ref: TranslationCellRef) => {\n void controller.save().then((saved) => saved && focusCell(ref))\n}\n\nconst cancel = (ref: TranslationCellRef) => {\n controller.cancelEdit()\n focusCell(ref)\n}\n\nconst editLabel = (key: string, locale: string) =>\n t('translation-manager.edit', { key, locale }, 'Edit :key in :locale')\n</script>\n\n<template>\n <Card class=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader class=\"flex flex-col gap-4 px-5\">\n <span :id=\"labelId\" class=\"sr-only\">{{\n t('translation-manager.label', {}, 'Translations')\n }}</span>\n <ul\n class=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n <li\n v-for=\"locale in schema.locales\"\n :key=\"locale.code\"\n class=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-2 text-sm\">\n <span class=\"truncate font-medium\" :lang=\"locale.code\">{{ locale.label }}</span>\n <span class=\"text-muted-foreground font-mono text-xs\">{{ locale.code }}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"Math.round((state.progress[locale.code]?.percent ?? 0) * 100)\"\n :aria-label=\"\n t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: state.progress[locale.code]?.translated ?? 0,\n total: state.progress[locale.code]?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )\n \"\n class=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n class=\"bg-primary h-full rounded-full transition-[width]\"\n :style=\"{ width: `${(state.progress[locale.code]?.percent ?? 0) * 100}%` }\"\n />\n </div>\n <span class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.progress',\n { percent: formatter.percent(state.progress[locale.code]?.percent ?? 0) },\n ':percent translated',\n )\n }}\n </span>\n </li>\n </ul>\n <div class=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div class=\"relative min-w-48 flex-1\">\n <SearchIcon\n class=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n :model-value=\"state.search\"\n :placeholder=\"t('translation-manager.search', {}, 'Search keys and texts…')\"\n :aria-label=\"t('translation-manager.search_label', {}, 'Search translations')\"\n class=\"ps-8\"\n @update:model-value=\"(value: string) => controller.setSearch(value)\"\n />\n </div>\n <label class=\"flex items-center gap-2 text-sm\">\n <span class=\"sr-only\">{{ t('translation-manager.filter_label', {}, 'Show') }}</span>\n <select\n :class=\"SELECT\"\n :value=\"state.filter\"\n @change=\"\n controller.setFilter(($event.target as HTMLSelectElement).value as TranslationFilter)\n \"\n >\n <option v-for=\"filter in schema.filters\" :key=\"filter\" :value=\"filter\">\n {{ t(`translation-manager.filters.${filter}`, {}, filter) }}\n </option>\n </select>\n </label>\n <label class=\"flex items-center gap-2 text-sm\">\n <span class=\"sr-only\">{{ t('translation-manager.group_label', {}, 'Group') }}</span>\n <select\n :class=\"SELECT\"\n :value=\"state.group\"\n @change=\"controller.setGroup(($event.target as HTMLSelectElement).value)\"\n >\n <option value=\"\">{{ t('translation-manager.all_groups', {}, 'All groups') }}</option>\n <option v-for=\"group in schema.groups\" :key=\"group.value\" :value=\"group.value\">\n {{ group.label }}\n </option>\n </select>\n </label>\n <DropdownMenu :dir=\"direction\">\n <DropdownMenuTrigger as-child>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" class=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {{ t('translation-manager.locales_label', {}, 'Languages') }}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuCheckboxItem\n v-for=\"locale in schema.locales\"\n :key=\"locale.code\"\n :model-value=\"state.visibleLocales.includes(locale.code)\"\n @update:model-value=\"\n (checked: boolean) => controller.toggleLocale(locale.code, checked)\n \"\n @select=\"(event: Event) => event.preventDefault()\"\n >\n {{ locale.label }}\n <span class=\"text-muted-foreground ms-auto font-mono text-xs\">{{ locale.code }}</span>\n </DropdownMenuCheckboxItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div\n v-if=\"suggestionLocales.length > 0\"\n class=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n <div\n v-for=\"code in suggestionLocales\"\n :key=\"code\"\n class=\"flex flex-wrap items-center gap-2\"\n >\n <SparklesIcon class=\"text-primary size-4\" aria-hidden=\"true\" />\n <span class=\"flex-1 font-medium\">\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }} ·\n {{ controller.locale(code)?.label }}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n @click=\"controller.decideAll(code, 'reject')\"\n >\n {{ t('translation-manager.reject_all', {}, 'Reject all') }}\n </Button>\n <Button type=\"button\" size=\"sm\" @click=\"controller.decideAll(code, 'accept')\">\n {{ t('translation-manager.accept_all', {}, 'Accept all without warnings') }}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent class=\"flex flex-col gap-4 px-5\">\n <p :id=\"hintId\" class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )\n }}\n </p>\n <p v-if=\"state.rows.length === 0\" class=\"text-muted-foreground py-10 text-center text-sm\">\n {{ t('translation-manager.empty', {}, 'No translations match your search.') }}\n </p>\n <div v-else class=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n :aria-labelledby=\"labelId\"\n :aria-describedby=\"hintId\"\n :aria-busy=\"state.loading || undefined\"\n :aria-rowcount=\"state.total + 1\"\n :class=\"cn('w-full border-collapse text-sm', state.loading && 'opacity-60')\"\n data-slot=\"translation-grid\"\n >\n <thead class=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n class=\"bg-muted sticky start-0 z-10 min-w-56 border-b px-3 py-2 text-start font-medium\"\n >\n {{ t('translation-manager.key', {}, 'Key') }}\n </th>\n <th\n v-for=\"locale in locales\"\n :key=\"locale.code\"\n scope=\"col\"\n class=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span class=\"flex items-center gap-2\">\n <span :lang=\"locale.code\">{{ locale.label }}</span>\n <span class=\"text-muted-foreground font-mono text-xs\">{{ locale.code }}</span>\n <Badge v-if=\"locale.base\" variant=\"secondary\">\n {{ t('translation-manager.base', {}, 'Base') }}\n </Badge>\n <Badge v-if=\"!locale.editable\" variant=\"outline\">\n {{ t('translation-manager.read_only', {}, 'Read only') }}\n </Badge>\n </span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"row in state.rows\"\n :key=\"row.id\"\n class=\"border-b last:border-b-0\"\n data-slot=\"translation-row\"\n >\n <th\n scope=\"row\"\n class=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <div class=\"flex flex-col gap-1\">\n <code class=\"font-mono text-xs break-all\">{{ row.key }}</code>\n <div class=\"flex flex-wrap gap-1\">\n <span class=\"text-muted-foreground text-xs\">{{ row.groupLabel }}</span>\n <Badge v-if=\"row.undefined\" variant=\"destructive\">\n {{ t('translation-manager.undefined', {}, 'Not defined') }}\n </Badge>\n <Badge v-if=\"row.unused\" variant=\"outline\">\n {{ t('translation-manager.unused', {}, 'Unused') }}\n </Badge>\n </div>\n </div>\n </th>\n <td\n v-for=\"locale in locales\"\n :key=\"locale.code\"\n :ref=\"setCell({ row: row.id, locale: locale.code })\"\n role=\"gridcell\"\n :tabindex=\"\n isActive({ row: row.id, locale: locale.code }) &&\n !isEditing({ row: row.id, locale: locale.code })\n ? 0\n : -1\n \"\n :aria-selected=\"isActive({ row: row.id, locale: locale.code })\"\n :aria-readonly=\"!editable({ row: row.id, locale: locale.code }) || undefined\"\n :aria-label=\"\n isEditing({ row: row.id, locale: locale.code })\n ? undefined\n : editLabel(row.key, locale.label)\n \"\n data-slot=\"translation-cell\"\n :data-missing=\"row.cells[locale.code]?.value ? undefined : ''\"\n :data-changed=\"row.cells[locale.code]?.changed ? '' : undefined\"\n :data-active=\"isActive({ row: row.id, locale: locale.code }) ? '' : undefined\"\n :class=\"\n cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive({ row: row.id, locale: locale.code }) &&\n 'ring-ring/50 ring-2 ring-inset',\n editable({ row: row.id, locale: locale.code }) && 'cursor-text',\n )\n \"\n @focus=\"\n $event.target === $event.currentTarget &&\n controller.focus({ row: row.id, locale: locale.code })\n \"\n @click=\"onCellClick({ row: row.id, locale: locale.code })\"\n @dblclick=\"controller.startEdit({ row: row.id, locale: locale.code })\"\n @keydown=\"onCellKeyDown({ row: row.id, locale: locale.code }, $event)\"\n >\n <div\n v-if=\"isEditing({ row: row.id, locale: locale.code })\"\n class=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n @click.stop\n >\n <template v-if=\"state.draftForms\">\n <label\n v-for=\"(form, index) in state.draftForms\"\n :key=\"index\"\n class=\"flex flex-col gap-1\"\n >\n <span class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.plural_form',\n {\n numbers:\n row.cells[locale.code]?.formLabels?.[index] ?? String(index + 1),\n },\n 'For :numbers',\n )\n }}\n </span>\n <Textarea\n :model-value=\"form\"\n rows=\"1\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n :aria-label=\"`${editLabel(row.key, locale.label)} (${row.cells[locale.code]?.formLabels?.[index] ?? index + 1})`\"\n :aria-invalid=\"state.error ? true : undefined\"\n :aria-describedby=\"state.error ? errorId : undefined\"\n class=\"min-h-9\"\n @update:model-value=\"\n (value: string) => controller.setDraftForm(index, value)\n \"\n @keydown=\"onEditorKeyDown\"\n />\n </label>\n </template>\n <Textarea\n v-else\n :model-value=\"state.draft\"\n :rows=\"Math.min(8, Math.max(2, state.draft.split('\\n').length))\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n :aria-label=\"editLabel(row.key, locale.label)\"\n :aria-invalid=\"state.error ? true : undefined\"\n :aria-describedby=\"state.error ? errorId : undefined\"\n @update:model-value=\"(value: string) => controller.setDraft(value)\"\n @keydown=\"onEditorKeyDown\"\n />\n <p v-if=\"state.error\" :id=\"errorId\" class=\"text-destructive text-xs\">\n {{ state.error }}\n </p>\n <div class=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n size=\"sm\"\n :disabled=\"state.saving\"\n @click=\"save({ row: row.id, locale: locale.code })\"\n >\n {{\n state.saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')\n }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n :disabled=\"state.saving\"\n @click=\"cancel({ row: row.id, locale: locale.code })\"\n >\n {{ t('translation-manager.cancel', {}, 'Cancel') }}\n </Button>\n <Button\n v-if=\"!locale.base\"\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n class=\"ms-auto\"\n :disabled=\"state.saving\"\n @click=\"controller.copyBase()\"\n >\n <CopyIcon aria-hidden=\"true\" />\n {{ t('translation-manager.copy_base', {}, 'Copy the base text') }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex flex-col gap-1\">\n <span\n v-if=\"row.cells[locale.code]?.value\"\n class=\"line-clamp-4 whitespace-pre-wrap\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n >{{ row.cells[locale.code]?.value }}</span\n >\n <span v-else class=\"text-muted-foreground italic\">\n {{ t('translation-manager.missing', {}, 'Missing') }}\n </span>\n <span\n v-if=\"\n row.cells[locale.code]?.changed ||\n row.cells[locale.code]?.warnings ||\n row.cells[locale.code]?.suggestion\n \"\n class=\"flex flex-wrap gap-1\"\n >\n <Badge v-if=\"row.cells[locale.code]?.changed\" variant=\"secondary\">\n {{ t('translation-manager.changed', {}, 'Changed in the panel') }}\n </Badge>\n <Badge\n v-if=\"row.cells[locale.code]?.warnings\"\n variant=\"destructive\"\n :title=\"row.cells[locale.code]?.warnings?.join('\\n')\"\n >\n <TriangleAlertIcon aria-hidden=\"true\" />\n {{ t('translation-manager.warnings', {}, 'Warnings') }}\n </Badge>\n <Badge v-if=\"row.cells[locale.code]?.suggestion\" variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }}\n </Badge>\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <div\n class=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span class=\"text-muted-foreground\">\n {{\n t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )\n }}\n </span>\n <div class=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"t('translation-manager.previous', {}, 'Previous page')\"\n :disabled=\"state.page <= 1 || state.loading\"\n @click=\"controller.goToPage(state.page - 1)\"\n >\n <ChevronLeftIcon class=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span class=\"px-2 tabular-nums\">\n {{\n t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )\n }}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"t('translation-manager.next', {}, 'Next page')\"\n :disabled=\"state.page >= state.lastPage || state.loading\"\n @click=\"controller.goToPage(state.page + 1)\"\n >\n <ChevronRightIcon class=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n <section\n v-if=\"activeRow && activeLocale\"\n class=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n :aria-label=\"t('translation-manager.details', {}, 'Details')\"\n >\n <div class=\"flex flex-wrap items-baseline gap-2\">\n <code class=\"font-mono text-xs break-all\">{{ activeRow.key }}</code>\n <span class=\"text-muted-foreground\">· {{ activeLocale.label }}</span>\n </div>\n <div v-if=\"activeRow.placeholders?.length\" class=\"flex flex-wrap items-center gap-1\">\n <span class=\"text-muted-foreground me-1\">\n {{ t('translation-manager.placeholders', {}, 'Placeholders') }}\n </span>\n <Badge\n v-for=\"placeholder in activeRow.placeholders\"\n :key=\"placeholder\"\n variant=\"outline\"\n class=\"font-mono\"\n >\n {{ placeholder }}\n </Badge>\n </div>\n <p v-if=\"activeRow.files?.length\" class=\"text-muted-foreground\">\n {{ t('translation-manager.used_in', {}, 'Used in') }}:\n <span class=\"font-mono text-xs\">{{ activeRow.files.join(', ') }}</span>\n </p>\n <p\n v-for=\"warning in activeCell?.warnings ?? []\"\n :key=\"warning\"\n class=\"text-destructive flex items-start gap-2\"\n >\n <TriangleAlertIcon class=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {{ warning }}\n </p>\n <div\n v-if=\"activeCell?.suggestion\"\n class=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span class=\"flex items-center gap-2 font-medium\">\n <SparklesIcon class=\"text-primary size-4\" aria-hidden=\"true\" />\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }}\n </span>\n <p class=\"whitespace-pre-wrap\" :dir=\"activeLocale.direction\" :lang=\"activeLocale.code\">\n {{ activeCell.suggestion.value }}\n </p>\n <p\n v-for=\"warning in activeCell.suggestion.warnings ?? []\"\n :key=\"warning\"\n class=\"text-destructive text-xs\"\n >\n {{ warning }}\n </p>\n <div v-if=\"activeLocale.editable\" class=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n @click=\"controller.decide({ row: activeRow.id, locale: activeLocale.code }, 'accept')\"\n >\n <CheckIcon aria-hidden=\"true\" />\n {{ t('translation-manager.accept', {}, 'Accept') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n @click=\"controller.decide({ row: activeRow.id, locale: activeLocale.code }, 'reject')\"\n >\n <XIcon aria-hidden=\"true\" />\n {{ t('translation-manager.reject', {}, 'Reject') }}\n </Button>\n </div>\n </div>\n <div\n v-if=\"state.history?.row === activeRow.id && state.history.locale === activeLocale.code\"\n class=\"flex flex-col gap-2\"\n data-slot=\"translation-history\"\n >\n <span class=\"font-medium\">{{ t('translation-manager.history', {}, 'History') }}</span>\n <p v-if=\"state.history.entries?.length === 0\" class=\"text-muted-foreground\">\n {{ t('translation-manager.history_empty', {}, 'No changes yet.') }}\n </p>\n <ol class=\"flex flex-col gap-2\">\n <li\n v-for=\"entry in state.history.entries ?? []\"\n :key=\"entry.id\"\n class=\"border-s-2 ps-3\"\n >\n <span class=\"text-muted-foreground text-xs\">\n {{ t(`translation-manager.sources.${entry.source}`, {}, entry.source) }}\n <template v-if=\"entry.user\">\n · {{ t('translation-manager.by', { name: entry.user }, 'by :name') }}\n </template>\n <template v-if=\"entry.at\"> · {{ formatter.relative(entry.at) }}</template>\n </span>\n <p\n class=\"whitespace-pre-wrap\"\n :dir=\"activeLocale.direction\"\n :lang=\"activeLocale.code\"\n >\n <template v-if=\"entry.new !== null\">{{ entry.new }}</template>\n <em v-else class=\"text-muted-foreground\">\n {{ t('translation-manager.empty_value', {}, 'Empty') }}\n </em>\n </p>\n </li>\n </ol>\n </div>\n <div v-else>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n @click=\"controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\"\n >\n <HistoryIcon aria-hidden=\"true\" />\n {{ t('translation-manager.show_history', {}, 'Show history') }}\n </Button>\n </div>\n </section>\n <div class=\"sr-only\" role=\"status\" aria-live=\"polite\">{{ state.announcement }}</div>\n </CardContent>\n </Card>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TranslationManagerView.js","names":["_sfc_main","TranslationManagerView_vue_vue_type_script_setup_true_lang_default","TranslationManagerView_default"],"sources":["../../../src/components/translation-manager/TranslationManagerView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n TranslationCellRef,\n TranslationFilter,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from '@lucide/vue'\nimport { computed, nextTick, onBeforeUnmount, toRaw, useId, watch } from 'vue'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../composables/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Card from '../ui/Card.vue'\nimport CardContent from '../ui/CardContent.vue'\nimport CardHeader from '../ui/CardHeader.vue'\nimport DropdownMenu from '../ui/DropdownMenu.vue'\nimport DropdownMenuCheckboxItem from '../ui/DropdownMenuCheckboxItem.vue'\nimport DropdownMenuContent from '../ui/DropdownMenuContent.vue'\nimport DropdownMenuTrigger from '../ui/DropdownMenuTrigger.vue'\nimport Input from '../ui/Input.vue'\nimport Textarea from '../ui/Textarea.vue'\n\n/**\n * Keys by locale with search, filters, inline editing (plural forms, right-to-left\n * inputs), keyboard navigation, AI suggestion review and history. Everything is computed\n * and saved by the server.\n */\nconst props = defineProps<{ schema: TranslationManagerSchema }>()\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst runner = useActionRunner()\nconst page = usePageActionContext()\nconst t = useT()\nconst direction = useDirection()\nconst formatter = useFormatter()\nconst labelId = useId()\nconst hintId = useId()\nconst errorId = useId()\n\nconst environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n})\n\nconst controller = new TranslationManagerController(toRaw(props.schema), environment())\nconst state = useStore(controller.store)\nconst cells = new Map<string, HTMLElement>()\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\nwatch(\n () => props.schema,\n (schema) => controller.setSchema(toRaw(schema)),\n)\n\nonBeforeUnmount(() => controller.dispose())\n\nconst locales = computed(() => {\n void state.value.visibleLocales\n\n return controller.visibleLocales()\n})\nconst active = computed<TranslationCellRef | null>(() => {\n const current = state.value.active\n\n if (current && state.value.rows.some((row) => row.id === current.row)) return current\n\n const row = state.value.rows[0]\n const locale = locales.value[0]\n\n return row && locale ? { row: row.id, locale: locale.code } : null\n})\nconst range = computed(() => {\n void state.value.rows\n\n return controller.range()\n})\nconst suggestionLocales = computed(() => {\n void state.value.rows\n\n return controller.suggestionLocales()\n})\nconst activeRow = computed(() =>\n active.value ? state.value.rows.find((row) => row.id === active.value?.row) : undefined,\n)\nconst activeLocale = computed(() =>\n active.value ? controller.locale(active.value.locale) : undefined,\n)\nconst activeCell = computed(() =>\n activeRow.value && activeLocale.value\n ? activeRow.value.cells[activeLocale.value.code]\n : undefined,\n)\n\nconst isActive = (ref: TranslationCellRef) =>\n active.value?.row === ref.row && active.value.locale === ref.locale\nconst isEditing = (ref: TranslationCellRef) =>\n state.value.editing?.row === ref.row && state.value.editing.locale === ref.locale\nconst editable = (ref: TranslationCellRef) => {\n void state.value.rows\n\n return controller.isEditable(ref)\n}\n\nconst setCell = (ref: TranslationCellRef) => (element: unknown) => {\n if (element instanceof HTMLElement) cells.set(cellKey(ref), element)\n else cells.delete(cellKey(ref))\n}\n\nconst focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n void nextTick(() => cells.get(cellKey(ref))?.focus())\n}\n\nwatch(\n () => state.value.editing,\n (editing) => {\n if (editing) {\n void nextTick(() => cells.get(cellKey(editing))?.querySelector('textarea')?.focus())\n }\n },\n)\n\nconst onCellKeyDown = (ref: TranslationCellRef, event: KeyboardEvent) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction.value)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n}\n\nconst onEditorKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' || (event.key === 'Enter' && !event.shiftKey) || event.key === 'Tab') {\n event.preventDefault()\n void controller\n .handleEditorKey({ key: event.key, shiftKey: event.shiftKey, isComposing: event.isComposing })\n .then((result) => focusCell(result.focus))\n }\n}\n\nconst onCellClick = (ref: TranslationCellRef) => {\n if (isEditing(ref)) return\n const wasActive = isActive(ref)\n controller.focus(ref)\n if (wasActive) controller.startEdit(ref)\n}\n\nconst save = (ref: TranslationCellRef) => {\n void controller.save().then((saved) => saved && focusCell(ref))\n}\n\nconst cancel = (ref: TranslationCellRef) => {\n controller.cancelEdit()\n focusCell(ref)\n}\n\nconst editLabel = (key: string, locale: string) =>\n t('translation-manager.edit', { key, locale }, 'Edit :key in :locale')\n</script>\n\n<template>\n <Card class=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader class=\"flex flex-col gap-4 px-5\">\n <span :id=\"labelId\" class=\"sr-only\">{{\n t('translation-manager.label', {}, 'Translations')\n }}</span>\n <ul\n class=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n <li\n v-for=\"locale in schema.locales\"\n :key=\"locale.code\"\n class=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-2 text-sm\">\n <span class=\"truncate font-medium\" :lang=\"locale.code\">{{ locale.label }}</span>\n <span class=\"text-muted-foreground font-mono text-xs\">{{ locale.code }}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"Math.round((state.progress[locale.code]?.percent ?? 0) * 100)\"\n :aria-label=\"\n t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: state.progress[locale.code]?.translated ?? 0,\n total: state.progress[locale.code]?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )\n \"\n class=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n class=\"bg-primary h-full rounded-full transition-[width]\"\n :style=\"{ width: `${(state.progress[locale.code]?.percent ?? 0) * 100}%` }\"\n />\n </div>\n <span class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.progress',\n { percent: formatter.percent(state.progress[locale.code]?.percent ?? 0) },\n ':percent translated',\n )\n }}\n </span>\n </li>\n </ul>\n <div class=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div class=\"relative min-w-48 flex-1\">\n <SearchIcon\n class=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n :model-value=\"state.search\"\n :placeholder=\"t('translation-manager.search', {}, 'Search keys and texts…')\"\n :aria-label=\"t('translation-manager.search_label', {}, 'Search translations')\"\n class=\"ps-8\"\n @update:model-value=\"(value: string) => controller.setSearch(value)\"\n />\n </div>\n <label class=\"flex items-center gap-2 text-sm\">\n <span class=\"sr-only\">{{ t('translation-manager.filter_label', {}, 'Show') }}</span>\n <select\n :class=\"SELECT\"\n :value=\"state.filter\"\n @change=\"\n controller.setFilter(($event.target as HTMLSelectElement).value as TranslationFilter)\n \"\n >\n <option v-for=\"filter in schema.filters\" :key=\"filter\" :value=\"filter\">\n {{ t(`translation-manager.filters.${filter}`, {}, filter) }}\n </option>\n </select>\n </label>\n <label class=\"flex items-center gap-2 text-sm\">\n <span class=\"sr-only\">{{ t('translation-manager.group_label', {}, 'Group') }}</span>\n <select\n :class=\"SELECT\"\n :value=\"state.group\"\n @change=\"controller.setGroup(($event.target as HTMLSelectElement).value)\"\n >\n <option value=\"\">{{ t('translation-manager.all_groups', {}, 'All groups') }}</option>\n <option v-for=\"group in schema.groups\" :key=\"group.value\" :value=\"group.value\">\n {{ group.label }}\n </option>\n </select>\n </label>\n <DropdownMenu :dir=\"direction\">\n <DropdownMenuTrigger as-child>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" class=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {{ t('translation-manager.locales_label', {}, 'Languages') }}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuCheckboxItem\n v-for=\"locale in schema.locales\"\n :key=\"locale.code\"\n :model-value=\"state.visibleLocales.includes(locale.code)\"\n @update:model-value=\"\n (checked: boolean) => controller.toggleLocale(locale.code, checked)\n \"\n @select=\"(event: Event) => event.preventDefault()\"\n >\n {{ locale.label }}\n <span class=\"text-muted-foreground ms-auto font-mono text-xs\">{{ locale.code }}</span>\n </DropdownMenuCheckboxItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div\n v-if=\"suggestionLocales.length > 0\"\n class=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n <div\n v-for=\"code in suggestionLocales\"\n :key=\"code\"\n class=\"flex flex-wrap items-center gap-2\"\n >\n <SparklesIcon class=\"text-primary size-4\" aria-hidden=\"true\" />\n <span class=\"flex-1 font-medium\">\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }} ·\n {{ controller.locale(code)?.label }}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n @click=\"controller.decideAll(code, 'reject')\"\n >\n {{ t('translation-manager.reject_all', {}, 'Reject all') }}\n </Button>\n <Button type=\"button\" size=\"sm\" @click=\"controller.decideAll(code, 'accept')\">\n {{ t('translation-manager.accept_all', {}, 'Accept all without warnings') }}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent class=\"flex flex-col gap-4 px-5\">\n <p :id=\"hintId\" class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )\n }}\n </p>\n <p v-if=\"state.rows.length === 0\" class=\"text-muted-foreground py-10 text-center text-sm\">\n {{ t('translation-manager.empty', {}, 'No translations match your search.') }}\n </p>\n <div v-else class=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n :aria-labelledby=\"labelId\"\n :aria-describedby=\"hintId\"\n :aria-busy=\"state.loading || undefined\"\n :aria-rowcount=\"state.total + 1\"\n :class=\"cn('w-full border-collapse text-sm', state.loading && 'opacity-60')\"\n data-slot=\"translation-grid\"\n >\n <thead class=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n class=\"bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium\"\n >\n {{ t('translation-manager.key', {}, 'Key') }}\n </th>\n <th\n v-for=\"locale in locales\"\n :key=\"locale.code\"\n scope=\"col\"\n class=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span class=\"flex items-center gap-2\">\n <span :lang=\"locale.code\">{{ locale.label }}</span>\n <span class=\"text-muted-foreground font-mono text-xs\">{{ locale.code }}</span>\n <Badge v-if=\"locale.base\" variant=\"secondary\">\n {{ t('translation-manager.base', {}, 'Base') }}\n </Badge>\n <Badge v-if=\"!locale.editable\" variant=\"outline\">\n {{ t('translation-manager.read_only', {}, 'Read only') }}\n </Badge>\n </span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"row in state.rows\"\n :key=\"row.id\"\n class=\"border-b last:border-b-0\"\n data-slot=\"translation-row\"\n >\n <th\n scope=\"row\"\n class=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <div class=\"flex flex-col gap-1\">\n <code class=\"font-mono text-xs break-all\">{{ row.key }}</code>\n <div class=\"flex flex-wrap gap-1\">\n <span class=\"text-muted-foreground text-xs\">{{ row.groupLabel }}</span>\n <Badge v-if=\"row.undefined\" variant=\"destructive\">\n {{ t('translation-manager.undefined', {}, 'Not defined') }}\n </Badge>\n <Badge v-if=\"row.unused\" variant=\"outline\">\n {{ t('translation-manager.unused', {}, 'Unused') }}\n </Badge>\n </div>\n </div>\n </th>\n <td\n v-for=\"locale in locales\"\n :key=\"locale.code\"\n :ref=\"setCell({ row: row.id, locale: locale.code })\"\n role=\"gridcell\"\n :tabindex=\"\n isActive({ row: row.id, locale: locale.code }) &&\n !isEditing({ row: row.id, locale: locale.code })\n ? 0\n : -1\n \"\n :aria-selected=\"isActive({ row: row.id, locale: locale.code })\"\n :aria-readonly=\"!editable({ row: row.id, locale: locale.code }) || undefined\"\n :aria-label=\"\n isEditing({ row: row.id, locale: locale.code })\n ? undefined\n : editLabel(row.key, locale.label)\n \"\n data-slot=\"translation-cell\"\n :data-missing=\"row.cells[locale.code]?.value ? undefined : ''\"\n :data-changed=\"row.cells[locale.code]?.changed ? '' : undefined\"\n :data-active=\"isActive({ row: row.id, locale: locale.code }) ? '' : undefined\"\n :class=\"\n cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive({ row: row.id, locale: locale.code }) &&\n 'ring-ring/50 ring-2 ring-inset',\n editable({ row: row.id, locale: locale.code }) && 'cursor-text',\n )\n \"\n @focus=\"\n $event.target === $event.currentTarget &&\n controller.focus({ row: row.id, locale: locale.code })\n \"\n @click=\"onCellClick({ row: row.id, locale: locale.code })\"\n @dblclick=\"controller.startEdit({ row: row.id, locale: locale.code })\"\n @keydown=\"onCellKeyDown({ row: row.id, locale: locale.code }, $event)\"\n >\n <div\n v-if=\"isEditing({ row: row.id, locale: locale.code })\"\n class=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n @click.stop\n >\n <template v-if=\"state.draftForms\">\n <label\n v-for=\"(form, index) in state.draftForms\"\n :key=\"index\"\n class=\"flex flex-col gap-1\"\n >\n <span class=\"text-muted-foreground text-xs\">\n {{\n t(\n 'translation-manager.plural_form',\n {\n numbers:\n row.cells[locale.code]?.formLabels?.[index] ?? String(index + 1),\n },\n 'For :numbers',\n )\n }}\n </span>\n <Textarea\n :model-value=\"form\"\n rows=\"1\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n :aria-label=\"`${editLabel(row.key, locale.label)} (${row.cells[locale.code]?.formLabels?.[index] ?? index + 1})`\"\n :aria-invalid=\"state.error ? true : undefined\"\n :aria-describedby=\"state.error ? errorId : undefined\"\n class=\"min-h-9\"\n @update:model-value=\"\n (value: string) => controller.setDraftForm(index, value)\n \"\n @keydown=\"onEditorKeyDown\"\n />\n </label>\n </template>\n <Textarea\n v-else\n :model-value=\"state.draft\"\n :rows=\"Math.min(8, Math.max(2, state.draft.split('\\n').length))\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n :aria-label=\"editLabel(row.key, locale.label)\"\n :aria-invalid=\"state.error ? true : undefined\"\n :aria-describedby=\"state.error ? errorId : undefined\"\n @update:model-value=\"(value: string) => controller.setDraft(value)\"\n @keydown=\"onEditorKeyDown\"\n />\n <p v-if=\"state.error\" :id=\"errorId\" class=\"text-destructive text-xs\">\n {{ state.error }}\n </p>\n <div class=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n size=\"sm\"\n :disabled=\"state.saving\"\n @click=\"save({ row: row.id, locale: locale.code })\"\n >\n {{\n state.saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')\n }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n :disabled=\"state.saving\"\n @click=\"cancel({ row: row.id, locale: locale.code })\"\n >\n {{ t('translation-manager.cancel', {}, 'Cancel') }}\n </Button>\n <Button\n v-if=\"!locale.base\"\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n class=\"ms-auto\"\n :disabled=\"state.saving\"\n @click=\"controller.copyBase()\"\n >\n <CopyIcon aria-hidden=\"true\" />\n {{ t('translation-manager.copy_base', {}, 'Copy the base text') }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex flex-col gap-1\">\n <span\n v-if=\"row.cells[locale.code]?.value\"\n class=\"line-clamp-4 whitespace-pre-wrap\"\n :dir=\"locale.direction\"\n :lang=\"locale.code\"\n >{{ row.cells[locale.code]?.value }}</span\n >\n <span v-else class=\"text-muted-foreground italic\">\n {{ t('translation-manager.missing', {}, 'Missing') }}\n </span>\n <span\n v-if=\"\n row.cells[locale.code]?.changed ||\n row.cells[locale.code]?.warnings ||\n row.cells[locale.code]?.suggestion\n \"\n class=\"flex flex-wrap gap-1\"\n >\n <Badge v-if=\"row.cells[locale.code]?.changed\" variant=\"secondary\">\n {{ t('translation-manager.changed', {}, 'Changed in the panel') }}\n </Badge>\n <Badge\n v-if=\"row.cells[locale.code]?.warnings\"\n variant=\"destructive\"\n :title=\"row.cells[locale.code]?.warnings?.join('\\n')\"\n >\n <TriangleAlertIcon aria-hidden=\"true\" />\n {{ t('translation-manager.warnings', {}, 'Warnings') }}\n </Badge>\n <Badge v-if=\"row.cells[locale.code]?.suggestion\" variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }}\n </Badge>\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <div\n class=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span class=\"text-muted-foreground\">\n {{\n t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )\n }}\n </span>\n <div class=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"t('translation-manager.previous', {}, 'Previous page')\"\n :disabled=\"state.page <= 1 || state.loading\"\n @click=\"controller.goToPage(state.page - 1)\"\n >\n <ChevronLeftIcon class=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span class=\"px-2 tabular-nums\">\n {{\n t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )\n }}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"t('translation-manager.next', {}, 'Next page')\"\n :disabled=\"state.page >= state.lastPage || state.loading\"\n @click=\"controller.goToPage(state.page + 1)\"\n >\n <ChevronRightIcon class=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n <section\n v-if=\"activeRow && activeLocale\"\n class=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n :aria-label=\"t('translation-manager.details', {}, 'Details')\"\n >\n <div class=\"flex flex-wrap items-baseline gap-2\">\n <code class=\"font-mono text-xs break-all\">{{ activeRow.key }}</code>\n <span class=\"text-muted-foreground\">· {{ activeLocale.label }}</span>\n </div>\n <div v-if=\"activeRow.placeholders?.length\" class=\"flex flex-wrap items-center gap-1\">\n <span class=\"text-muted-foreground me-1\">\n {{ t('translation-manager.placeholders', {}, 'Placeholders') }}\n </span>\n <Badge\n v-for=\"placeholder in activeRow.placeholders\"\n :key=\"placeholder\"\n variant=\"outline\"\n class=\"font-mono\"\n >\n {{ placeholder }}\n </Badge>\n </div>\n <p v-if=\"activeRow.files?.length\" class=\"text-muted-foreground\">\n {{ t('translation-manager.used_in', {}, 'Used in') }}:\n <span class=\"font-mono text-xs\">{{ activeRow.files.join(', ') }}</span>\n </p>\n <p\n v-for=\"warning in activeCell?.warnings ?? []\"\n :key=\"warning\"\n class=\"text-destructive flex items-start gap-2\"\n >\n <TriangleAlertIcon class=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {{ warning }}\n </p>\n <div\n v-if=\"activeCell?.suggestion\"\n class=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span class=\"flex items-center gap-2 font-medium\">\n <SparklesIcon class=\"text-primary size-4\" aria-hidden=\"true\" />\n {{ t('translation-manager.suggestion', {}, 'AI suggestion') }}\n </span>\n <p class=\"whitespace-pre-wrap\" :dir=\"activeLocale.direction\" :lang=\"activeLocale.code\">\n {{ activeCell.suggestion.value }}\n </p>\n <p\n v-for=\"warning in activeCell.suggestion.warnings ?? []\"\n :key=\"warning\"\n class=\"text-destructive text-xs\"\n >\n {{ warning }}\n </p>\n <div v-if=\"activeLocale.editable\" class=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n @click=\"controller.decide({ row: activeRow.id, locale: activeLocale.code }, 'accept')\"\n >\n <CheckIcon aria-hidden=\"true\" />\n {{ t('translation-manager.accept', {}, 'Accept') }}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n @click=\"controller.decide({ row: activeRow.id, locale: activeLocale.code }, 'reject')\"\n >\n <XIcon aria-hidden=\"true\" />\n {{ t('translation-manager.reject', {}, 'Reject') }}\n </Button>\n </div>\n </div>\n <div\n v-if=\"state.history?.row === activeRow.id && state.history.locale === activeLocale.code\"\n class=\"flex flex-col gap-2\"\n data-slot=\"translation-history\"\n >\n <span class=\"font-medium\">{{ t('translation-manager.history', {}, 'History') }}</span>\n <p v-if=\"state.history.entries?.length === 0\" class=\"text-muted-foreground\">\n {{ t('translation-manager.history_empty', {}, 'No changes yet.') }}\n </p>\n <ol class=\"flex flex-col gap-2\">\n <li\n v-for=\"entry in state.history.entries ?? []\"\n :key=\"entry.id\"\n class=\"border-s-2 ps-3\"\n >\n <span class=\"text-muted-foreground text-xs\">\n {{ t(`translation-manager.sources.${entry.source}`, {}, entry.source) }}\n <template v-if=\"entry.user\">\n · {{ t('translation-manager.by', { name: entry.user }, 'by :name') }}\n </template>\n <template v-if=\"entry.at\"> · {{ formatter.relative(entry.at) }}</template>\n </span>\n <p\n class=\"whitespace-pre-wrap\"\n :dir=\"activeLocale.direction\"\n :lang=\"activeLocale.code\"\n >\n <template v-if=\"entry.new !== null\">{{ entry.new }}</template>\n <em v-else class=\"text-muted-foreground\">\n {{ t('translation-manager.empty_value', {}, 'Empty') }}\n </em>\n </p>\n </li>\n </ol>\n </div>\n <div v-else>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n @click=\"controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\"\n >\n <HistoryIcon aria-hidden=\"true\" />\n {{ t('translation-manager.show_history', {}, 'Show history') }}\n </Button>\n </div>\n </section>\n <div class=\"sr-only\" role=\"status\" aria-live=\"polite\">{{ state.announcement }}</div>\n </CardContent>\n </Card>\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationManagerView.vue.d.ts","sourceRoot":"","sources":["../../../src/components/translation-manager/TranslationManagerView.vue"],"names":[],"mappings":"AA2uBA,OAAO,KAAK,EAIV,wBAAwB,EACzB,MAAM,eAAe,CAAA;AAgCtB;;;;GAIG;AACH,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,wBAAwB,CAAA;CAAE,CAAC;;AA4oDxD,wBAMG"}
1
+ {"version":3,"file":"TranslationManagerView.vue.d.ts","sourceRoot":"","sources":["../../../src/components/translation-manager/TranslationManagerView.vue"],"names":[],"mappings":"AA2uBA,OAAO,KAAK,EAIV,wBAAwB,EACzB,MAAM,eAAe,CAAA;AAgCtB;;;;GAIG;AACH,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,wBAAwB,CAAA;CAAE,CAAC;;AA6oDxD,wBAMG"}
@@ -42,7 +42,7 @@ var Ce = ["id"], we = {
42
42
  "aria-rowcount"
43
43
  ], Je = { class: "bg-muted/50" }, Ye = {
44
44
  scope: "col",
45
- class: "bg-muted sticky start-0 z-10 min-w-56 border-b px-3 py-2 text-start font-medium"
45
+ class: "bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium"
46
46
  }, Xe = { class: "flex items-center gap-2" }, Ze = ["lang"], Qe = { class: "text-muted-foreground font-mono text-xs" }, $e = {
47
47
  scope: "row",
48
48
  class: "bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal"