@vincent99/vlib 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/vlib.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* global process */
2
3
  // CLI entry point — delegates to compiled TypeScript
3
4
  import('../dist/cli.js').catch((err) => {
4
5
  // Fallback: run via tsx if not built yet
@@ -0,0 +1,762 @@
1
+ import { defineComponent as K, ref as b, resolveComponent as q, openBlock as t, createElementBlock as a, Fragment as D, renderList as M, createElementVNode as e, toDisplayString as f, createCommentVNode as k, normalizeClass as F, withDirectives as I, createBlock as Q, withCtx as z, createTextVNode as j, vShow as ae, onMounted as H, onUnmounted as le, renderSlot as O, createVNode as A, computed as x, watch as ne, withModifiers as Y, vModelCheckbox as W, vModelText as J } from "vue";
2
+ import { useRouter as Z } from "vue-router";
3
+ const se = { class: "vl-nav" }, oe = {
4
+ key: 0,
5
+ class: "vl-nav__group"
6
+ }, ie = ["onClick"], ue = {
7
+ key: 0,
8
+ class: "vl-nav__group-icon"
9
+ }, re = { class: "vl-nav__group-label" }, ve = { class: "vl-nav__group-children" }, de = {
10
+ key: 0,
11
+ class: "vl-nav__link-icon"
12
+ }, ce = {
13
+ key: 0,
14
+ class: "vl-nav__link-icon"
15
+ }, me = /* @__PURE__ */ K({
16
+ __name: "NavSidebar",
17
+ props: {
18
+ items: {}
19
+ },
20
+ emits: ["navigate"],
21
+ setup(v) {
22
+ const p = v, C = b(
23
+ new Set(
24
+ p.items.filter((s) => s.children && s.defaultOpen !== !1).map((s) => s.label)
25
+ )
26
+ );
27
+ function y(s) {
28
+ return C.value.has(s);
29
+ }
30
+ function u(s) {
31
+ C.value.has(s) ? C.value.delete(s) : C.value.add(s);
32
+ }
33
+ return (s, h) => {
34
+ const w = q("RouterLink");
35
+ return t(), a("div", se, [
36
+ (t(!0), a(D, null, M(v.items, (m) => (t(), a(D, {
37
+ key: m.label
38
+ }, [
39
+ m.children ? (t(), a("div", oe, [
40
+ e("button", {
41
+ class: "vl-nav__group-header",
42
+ onClick: (_) => u(m.label)
43
+ }, [
44
+ m.icon ? (t(), a("span", ue, f(m.icon), 1)) : k("", !0),
45
+ e("span", re, f(m.label), 1),
46
+ e("span", {
47
+ class: F(["vl-nav__group-caret", { "vl-nav__group-caret--open": y(m.label) }])
48
+ }, "▸", 2)
49
+ ], 8, ie),
50
+ I(e("div", ve, [
51
+ (t(!0), a(D, null, M(m.children, (_) => (t(), Q(w, {
52
+ key: _.to,
53
+ to: _.to,
54
+ class: "vl-nav__link vl-nav__link--child",
55
+ "active-class": "vl-nav__link--active",
56
+ "exact-active-class": "vl-nav__link--exact-active",
57
+ onClick: h[0] || (h[0] = (d) => s.$emit("navigate"))
58
+ }, {
59
+ default: z(() => [
60
+ _.icon ? (t(), a("span", de, f(_.icon), 1)) : k("", !0),
61
+ j(" " + f(_.label), 1)
62
+ ]),
63
+ _: 2
64
+ }, 1032, ["to"]))), 128))
65
+ ], 512), [
66
+ [ae, y(m.label)]
67
+ ])
68
+ ])) : (t(), Q(w, {
69
+ key: 1,
70
+ to: m.to,
71
+ class: "vl-nav__link",
72
+ "active-class": "vl-nav__link--active",
73
+ "exact-active-class": "vl-nav__link--exact-active",
74
+ onClick: h[1] || (h[1] = (_) => s.$emit("navigate"))
75
+ }, {
76
+ default: z(() => [
77
+ m.icon ? (t(), a("span", ce, f(m.icon), 1)) : k("", !0),
78
+ j(" " + f(m.label), 1)
79
+ ]),
80
+ _: 2
81
+ }, 1032, ["to"]))
82
+ ], 64))), 128))
83
+ ]);
84
+ };
85
+ }
86
+ }), _e = { class: "vl-app" }, fe = { class: "vl-header" }, be = { class: "vl-header__left" }, he = { class: "vl-header__title" }, pe = { class: "vl-header__right" }, ye = { class: "vl-user-menu__name" }, ke = {
87
+ key: 0,
88
+ class: "vl-user-menu__dropdown"
89
+ }, $e = { class: "vl-body" }, ge = { class: "vl-main" }, Ct = /* @__PURE__ */ K({
90
+ __name: "AppLayout",
91
+ props: {
92
+ appName: { default: "App" },
93
+ user: { default: null },
94
+ navItems: { default: () => [] }
95
+ },
96
+ emits: ["logout"],
97
+ setup(v, { emit: p }) {
98
+ const C = p, y = Z(), u = b(!1), s = b(!1), h = b(null);
99
+ function w() {
100
+ s.value = !1, C("logout");
101
+ }
102
+ function m(_) {
103
+ h.value && !h.value.contains(_.target) && (s.value = !1);
104
+ }
105
+ return y.afterEach(() => {
106
+ u.value = !1;
107
+ }), H(() => document.addEventListener("click", m)), le(() => document.removeEventListener("click", m)), (_, d) => (t(), a("div", _e, [
108
+ e("header", fe, [
109
+ e("div", be, [
110
+ e("button", {
111
+ class: "vl-hamburger",
112
+ "aria-label": "Toggle navigation",
113
+ onClick: d[0] || (d[0] = ($) => u.value = !u.value)
114
+ }, [...d[4] || (d[4] = [
115
+ e("span", { class: "vl-hamburger__bar" }, null, -1),
116
+ e("span", { class: "vl-hamburger__bar" }, null, -1),
117
+ e("span", { class: "vl-hamburger__bar" }, null, -1)
118
+ ])]),
119
+ O(_.$slots, "logo", {}, () => [
120
+ e("span", he, f(v.appName), 1)
121
+ ])
122
+ ]),
123
+ e("div", pe, [
124
+ v.user ? (t(), a("div", {
125
+ key: 0,
126
+ ref_key: "userMenuRef",
127
+ ref: h,
128
+ class: "vl-user-menu"
129
+ }, [
130
+ e("button", {
131
+ class: "vl-user-menu__btn",
132
+ onClick: d[1] || (d[1] = ($) => s.value = !s.value)
133
+ }, [
134
+ e("span", ye, f(v.user.displayName || v.user.username), 1),
135
+ d[5] || (d[5] = e("span", { class: "vl-user-menu__caret" }, "▾", -1))
136
+ ]),
137
+ s.value ? (t(), a("div", ke, [
138
+ e("button", {
139
+ class: "vl-user-menu__item",
140
+ onClick: w
141
+ }, "Logout")
142
+ ])) : k("", !0)
143
+ ], 512)) : k("", !0)
144
+ ])
145
+ ]),
146
+ e("div", $e, [
147
+ u.value ? (t(), a("div", {
148
+ key: 0,
149
+ class: "vl-sidebar-overlay",
150
+ onClick: d[2] || (d[2] = ($) => u.value = !1)
151
+ })) : k("", !0),
152
+ e("nav", {
153
+ class: F(["vl-sidebar", { "vl-sidebar--open": u.value }])
154
+ }, [
155
+ O(_.$slots, "sidebar", {}, () => [
156
+ A(me, {
157
+ items: v.navItems,
158
+ onNavigate: d[3] || (d[3] = ($) => u.value = !1)
159
+ }, null, 8, ["items"])
160
+ ])
161
+ ], 2),
162
+ e("main", ge, [
163
+ O(_.$slots, "default")
164
+ ])
165
+ ])
166
+ ]));
167
+ }
168
+ }), we = { class: "vl-table-view" }, Ce = { class: "vl-table-view__toolbar" }, Se = { class: "vl-table-view__toolbar-left" }, Ne = {
169
+ key: 0,
170
+ class: "vl-table-view__selected-count"
171
+ }, Ee = { class: "vl-table-view__toolbar-right" }, Te = ["onClick"], Ve = { class: "vl-table-view__scroll" }, Le = { class: "vl-table" }, Ue = { class: "vl-table__th vl-table__th--check" }, je = ["checked", "indeterminate"], Re = ["onClick"], xe = {
172
+ key: 0,
173
+ class: "vl-table__sort-icon"
174
+ }, De = {
175
+ key: 0,
176
+ class: "vl-table__th vl-table__th--actions"
177
+ }, Ie = { class: "vl-table__td vl-table__td--check" }, Me = ["checked", "aria-label", "onChange"], Oe = {
178
+ key: 0,
179
+ class: "vl-table__td vl-table__td--actions"
180
+ }, ze = { key: 0 }, Ae = ["colspan"], Pe = {
181
+ key: 0,
182
+ class: "vl-table-view__pagination"
183
+ }, Be = ["disabled"], Je = { class: "vl-table-view__pagination-info" }, Fe = ["disabled"], Ke = /* @__PURE__ */ K({
184
+ __name: "TableView",
185
+ props: {
186
+ rows: {},
187
+ headers: {},
188
+ idKey: { default: "id" },
189
+ total: { default: 0 },
190
+ page: { default: 1 },
191
+ pageSize: { default: 50 },
192
+ actions: { default: () => [] }
193
+ },
194
+ emits: ["update:page", "action", "sort"],
195
+ setup(v, { expose: p, emit: C }) {
196
+ const y = v, u = C, s = b(/* @__PURE__ */ new Set()), h = b(null), w = b("asc"), m = x(
197
+ () => Math.ceil((y.total || y.rows.length) / y.pageSize)
198
+ ), _ = x(
199
+ () => y.rows.length > 0 && y.rows.every((o) => s.value.has($(o)))
200
+ ), d = x(
201
+ () => y.rows.some((o) => s.value.has($(o)))
202
+ );
203
+ function $(o) {
204
+ return o[y.idKey];
205
+ }
206
+ function V(o, S) {
207
+ return o[S];
208
+ }
209
+ function L() {
210
+ _.value ? y.rows.forEach((o) => s.value.delete($(o))) : y.rows.forEach((o) => s.value.add($(o)));
211
+ }
212
+ function N(o) {
213
+ s.value.has(o) ? s.value.delete(o) : s.value.add(o);
214
+ }
215
+ function U(o) {
216
+ u("action", o.key, Array.from(s.value)), s.value.clear();
217
+ }
218
+ function P(o) {
219
+ h.value === o ? w.value = w.value === "asc" ? "desc" : "asc" : (h.value = o, w.value = "asc"), u("sort", h.value, w.value);
220
+ }
221
+ function B(o) {
222
+ if (o == null)
223
+ return "";
224
+ if (typeof o == "object")
225
+ return JSON.stringify(o);
226
+ const S = String(o);
227
+ return S.length > 120 ? S.slice(0, 120) + "…" : S;
228
+ }
229
+ return p({ selectedIds: s }), (o, S) => (t(), a("div", we, [
230
+ e("div", Ce, [
231
+ e("div", Se, [
232
+ O(o.$slots, "toolbar-left", {}, () => [
233
+ s.value.size > 0 ? (t(), a("span", Ne, f(s.value.size) + " selected ", 1)) : k("", !0)
234
+ ])
235
+ ]),
236
+ e("div", Ee, [
237
+ s.value.size > 0 && v.actions.length > 0 ? (t(!0), a(D, { key: 0 }, M(v.actions, (n) => (t(), a("button", {
238
+ key: n.key,
239
+ class: F(["vl-btn", [`vl-btn--${n.variant || "default"}`]]),
240
+ onClick: (r) => U(n)
241
+ }, f(n.label), 11, Te))), 128)) : k("", !0),
242
+ O(o.$slots, "toolbar-right")
243
+ ])
244
+ ]),
245
+ e("div", Ve, [
246
+ e("table", Le, [
247
+ e("thead", null, [
248
+ e("tr", null, [
249
+ e("th", Ue, [
250
+ e("input", {
251
+ type: "checkbox",
252
+ checked: _.value,
253
+ indeterminate: d.value && !_.value,
254
+ "aria-label": "Select all",
255
+ onChange: L
256
+ }, null, 40, je)
257
+ ]),
258
+ (t(!0), a(D, null, M(v.headers, (n) => (t(), a("th", {
259
+ key: n.key,
260
+ class: F(["vl-table__th", { "vl-table__th--sortable": n.sortable !== !1 }]),
261
+ onClick: (r) => n.sortable !== !1 && P(n.key)
262
+ }, [
263
+ j(f(n.label) + " ", 1),
264
+ h.value === n.key ? (t(), a("span", xe, f(w.value === "asc" ? "↑" : "↓"), 1)) : k("", !0)
265
+ ], 10, Re))), 128)),
266
+ o.$slots["row-actions"] ? (t(), a("th", De, " Actions ")) : k("", !0)
267
+ ])
268
+ ]),
269
+ e("tbody", null, [
270
+ (t(!0), a(D, null, M(v.rows, (n) => (t(), a("tr", {
271
+ key: $(n),
272
+ class: F(["vl-table__tr", {
273
+ "vl-table__tr--selected": s.value.has($(n))
274
+ }])
275
+ }, [
276
+ e("td", Ie, [
277
+ e("input", {
278
+ type: "checkbox",
279
+ checked: s.value.has($(n)),
280
+ "aria-label": `Select row ${$(n)}`,
281
+ onChange: (r) => N($(n))
282
+ }, null, 40, Me)
283
+ ]),
284
+ (t(!0), a(D, null, M(v.headers, (r) => (t(), a("td", {
285
+ key: r.key,
286
+ class: "vl-table__td"
287
+ }, [
288
+ O(o.$slots, `cell-${r.key}`, {
289
+ row: n,
290
+ value: V(n, r.key)
291
+ }, () => [
292
+ j(f(B(V(n, r.key))), 1)
293
+ ])
294
+ ]))), 128)),
295
+ o.$slots["row-actions"] ? (t(), a("td", Oe, [
296
+ O(o.$slots, "row-actions", {
297
+ id: $(n),
298
+ row: n
299
+ })
300
+ ])) : k("", !0)
301
+ ], 2))), 128)),
302
+ v.rows.length === 0 ? (t(), a("tr", ze, [
303
+ e("td", {
304
+ colspan: v.headers.length + 2,
305
+ class: "vl-table__empty"
306
+ }, " No rows found. ", 8, Ae)
307
+ ])) : k("", !0)
308
+ ])
309
+ ])
310
+ ]),
311
+ v.total > v.pageSize ? (t(), a("div", Pe, [
312
+ e("button", {
313
+ class: "vl-btn vl-btn--sm",
314
+ disabled: v.page <= 1,
315
+ onClick: S[0] || (S[0] = (n) => o.$emit("update:page", v.page - 1))
316
+ }, " ← Prev ", 8, Be),
317
+ e("span", Je, " Page " + f(v.page) + " of " + f(m.value) + "  (" + f(v.total) + " total) ", 1),
318
+ e("button", {
319
+ class: "vl-btn vl-btn--sm",
320
+ disabled: v.page >= m.value,
321
+ onClick: S[1] || (S[1] = (n) => o.$emit("update:page", v.page + 1))
322
+ }, " Next → ", 8, Fe)
323
+ ])) : k("", !0)
324
+ ]));
325
+ }
326
+ }), Ge = { class: "vl-admin-table" }, qe = { class: "vl-admin-table__header" }, He = { class: "vl-admin-table__title" }, Xe = {
327
+ key: 0,
328
+ class: "vl-alert vl-alert--error"
329
+ }, Qe = ["onClick"], We = { class: "vl-modal" }, Ye = { class: "vl-modal__actions" }, St = /* @__PURE__ */ K({
330
+ __name: "AdminTable",
331
+ props: {
332
+ tableName: {}
333
+ },
334
+ setup(v) {
335
+ const p = v, C = b([]), y = b([]), u = b(0), s = b(1), h = b(50), w = b(void 0), m = b("asc"), _ = b(null), d = b(null), $ = x(
336
+ () => y.value.map((n) => ({ key: n.name, label: n.name, sortable: !0 }))
337
+ ), V = [
338
+ { key: "delete", label: "Delete Selected", variant: "danger" }
339
+ ];
340
+ async function L(n) {
341
+ s.value = n, await N();
342
+ }
343
+ async function N() {
344
+ _.value = null;
345
+ try {
346
+ const n = new URLSearchParams({
347
+ page: String(s.value),
348
+ pageSize: String(h.value),
349
+ ...w.value ? { orderBy: w.value, dir: m.value } : {}
350
+ }), r = await fetch(`/api/admin/tables/${p.tableName}?${n}`);
351
+ if (!r.ok)
352
+ throw new Error((await r.json()).error);
353
+ const E = await r.json();
354
+ C.value = E.rows, u.value = E.total, y.value.length === 0 && E.rows.length > 0 && (y.value = Object.keys(E.rows[0]).map((R) => ({
355
+ name: R,
356
+ type: "TEXT"
357
+ })));
358
+ } catch (n) {
359
+ _.value = String(n);
360
+ }
361
+ }
362
+ async function U() {
363
+ try {
364
+ const n = await fetch("/api/admin/tables");
365
+ if (!n.ok)
366
+ return;
367
+ const E = (await n.json()).find((R) => R.name === p.tableName);
368
+ E && (y.value = E.columns);
369
+ } catch {
370
+ }
371
+ }
372
+ function P(n, r) {
373
+ w.value = n, m.value = r, s.value = 1, N();
374
+ }
375
+ function B(n, r) {
376
+ n === "delete" && o(r);
377
+ }
378
+ function o(n) {
379
+ d.value = n;
380
+ }
381
+ async function S() {
382
+ if (!d.value)
383
+ return;
384
+ const n = d.value;
385
+ d.value = null;
386
+ try {
387
+ const r = await fetch(`/api/admin/tables/${p.tableName}`, {
388
+ method: "DELETE",
389
+ headers: { "Content-Type": "application/json" },
390
+ body: JSON.stringify({ ids: n })
391
+ });
392
+ if (!r.ok)
393
+ throw new Error((await r.json()).error);
394
+ await N();
395
+ } catch (r) {
396
+ _.value = String(r);
397
+ }
398
+ }
399
+ return ne(
400
+ () => p.tableName,
401
+ () => {
402
+ s.value = 1, y.value = [], U(), N();
403
+ }
404
+ ), H(() => {
405
+ U(), N();
406
+ }), (n, r) => {
407
+ const E = q("RouterLink");
408
+ return t(), a("div", Ge, [
409
+ e("div", qe, [
410
+ e("h1", He, f(v.tableName), 1),
411
+ A(E, {
412
+ to: `/admin/tables/${v.tableName}/new`,
413
+ class: "vl-btn vl-btn--primary"
414
+ }, {
415
+ default: z(() => [...r[2] || (r[2] = [
416
+ j(" + Add Row ", -1)
417
+ ])]),
418
+ _: 1
419
+ }, 8, ["to"])
420
+ ]),
421
+ _.value ? (t(), a("div", Xe, f(_.value), 1)) : k("", !0),
422
+ A(Ke, {
423
+ rows: C.value,
424
+ headers: $.value,
425
+ total: u.value,
426
+ page: s.value,
427
+ "page-size": h.value,
428
+ actions: V,
429
+ "onUpdate:page": L,
430
+ onAction: B,
431
+ onSort: P
432
+ }, {
433
+ "row-actions": z(({ id: R }) => [
434
+ A(E, {
435
+ to: `/admin/tables/${v.tableName}/${R}`,
436
+ class: "vl-btn vl-btn--sm"
437
+ }, {
438
+ default: z(() => [...r[3] || (r[3] = [
439
+ j("Edit", -1)
440
+ ])]),
441
+ _: 1
442
+ }, 8, ["to"]),
443
+ e("button", {
444
+ class: "vl-btn vl-btn--sm vl-btn--danger",
445
+ onClick: (X) => o([R])
446
+ }, " Del ", 8, Qe)
447
+ ]),
448
+ _: 1
449
+ }, 8, ["rows", "headers", "total", "page", "page-size"]),
450
+ d.value ? (t(), a("div", {
451
+ key: 1,
452
+ class: "vl-modal-overlay",
453
+ onClick: r[1] || (r[1] = Y((R) => d.value = null, ["self"]))
454
+ }, [
455
+ e("div", We, [
456
+ r[6] || (r[6] = e("h3", { class: "vl-modal__title" }, "Confirm Delete", -1)),
457
+ e("p", null, [
458
+ j(" Delete " + f(d.value.length) + " row" + f(d.value.length !== 1 ? "s" : "") + "?", 1),
459
+ r[4] || (r[4] = e("br", null, null, -1)),
460
+ r[5] || (r[5] = j("This cannot be undone. ", -1))
461
+ ]),
462
+ e("div", Ye, [
463
+ e("button", {
464
+ class: "vl-btn",
465
+ onClick: r[0] || (r[0] = (R) => d.value = null)
466
+ }, "Cancel"),
467
+ e("button", {
468
+ class: "vl-btn vl-btn--danger",
469
+ onClick: S
470
+ }, " Delete ")
471
+ ])
472
+ ])
473
+ ])) : k("", !0)
474
+ ]);
475
+ };
476
+ }
477
+ }), Ze = { class: "vl-admin-form" }, et = { class: "vl-admin-form__header" }, tt = { class: "vl-admin-form__title" }, at = {
478
+ key: 0,
479
+ class: "vl-alert vl-alert--error"
480
+ }, lt = {
481
+ key: 1,
482
+ class: "vl-alert vl-alert--error"
483
+ }, nt = {
484
+ key: 2,
485
+ class: "vl-alert vl-alert--success"
486
+ }, st = {
487
+ key: 3,
488
+ class: "vl-admin-form__loading"
489
+ }, ot = { class: "vl-form__field-header" }, it = ["id", "onUpdate:modelValue"], ut = ["for"], rt = { class: "vl-form__type" }, vt = {
490
+ key: 0,
491
+ class: "vl-form__required"
492
+ }, dt = { class: "vl-form__input-wrap" }, ct = ["value"], mt = ["id", "onUpdate:modelValue"], _t = ["id", "onUpdate:modelValue", "placeholder"], ft = ["id", "onUpdate:modelValue"], bt = ["id", "onUpdate:modelValue"], ht = ["id", "onUpdate:modelValue"], pt = ["id", "onUpdate:modelValue"], yt = {
493
+ key: 2,
494
+ class: "vl-form__multi-warn"
495
+ }, kt = { class: "vl-form__actions" }, $t = ["disabled"], G = "— Multiple —", Nt = /* @__PURE__ */ K({
496
+ __name: "AdminForm",
497
+ props: {
498
+ tableName: {},
499
+ rowIds: {}
500
+ },
501
+ setup(v) {
502
+ const p = v, C = Z(), y = b([]), u = b({}), s = b({}), h = b({}), w = b(!0), m = b(!1), _ = b(null), d = b(null), $ = b(!1), V = x(() => !p.rowIds || p.rowIds.length === 0), L = x(() => {
503
+ var l;
504
+ return (((l = p.rowIds) == null ? void 0 : l.length) ?? 0) > 1;
505
+ }), N = x(() => p.rowIds ?? []), U = x(
506
+ () => y.value.filter((l) => V.value ? l.pk === 0 || l.dflt_value === null : l.pk === 0)
507
+ ), P = x(
508
+ () => L.value && U.value.some(
509
+ (l) => h.value[l.name] && u.value[l.name] === G
510
+ )
511
+ );
512
+ function B(l) {
513
+ return /bool/i.test(l.type);
514
+ }
515
+ function o(l) {
516
+ return /json/i.test(l.type) || l.name.toLowerCase() === "preferences";
517
+ }
518
+ function S(l) {
519
+ return /text|varchar|nvarchar|clob/i.test(l.type) && l.name.length > 40;
520
+ }
521
+ function n(l) {
522
+ return /int|real|float|double|numeric|decimal/i.test(l.type);
523
+ }
524
+ function r(l) {
525
+ return /date|time/i.test(l.type);
526
+ }
527
+ function E(l) {
528
+ const c = new Set(
529
+ Object.values(s.value).map((g) => g[l])
530
+ );
531
+ return c.size > 1 ? G : String([...c][0] ?? "");
532
+ }
533
+ async function R() {
534
+ const l = await fetch("/api/admin/tables");
535
+ if (!l.ok)
536
+ throw new Error("Failed to load table info");
537
+ const g = (await l.json()).find((i) => i.name === p.tableName);
538
+ if (!g)
539
+ throw new Error(`Table "${p.tableName}" not found`);
540
+ y.value = g.columns;
541
+ }
542
+ async function X() {
543
+ if (!V.value)
544
+ for (const l of N.value) {
545
+ const c = await fetch(`/api/admin/tables/${p.tableName}/${l}`);
546
+ if (!c.ok)
547
+ throw new Error(`Row ${l} not found`);
548
+ s.value[String(l)] = await c.json();
549
+ }
550
+ }
551
+ function ee() {
552
+ var l;
553
+ if (V.value) {
554
+ for (const c of U.value)
555
+ u.value[c.name] = c.dflt_value ?? null;
556
+ return;
557
+ }
558
+ for (const c of U.value)
559
+ if (L.value) {
560
+ const g = E(c.name);
561
+ u.value[c.name] = g, h.value[c.name] = !1;
562
+ } else {
563
+ const g = String(N.value[0]);
564
+ u.value[c.name] = ((l = s.value[g]) == null ? void 0 : l[c.name]) ?? null;
565
+ }
566
+ }
567
+ async function te() {
568
+ m.value = !1, d.value = null, $.value = !1, m.value = !0;
569
+ try {
570
+ if (V.value) {
571
+ const l = await fetch(`/api/admin/tables/${p.tableName}`, {
572
+ method: "POST",
573
+ headers: { "Content-Type": "application/json" },
574
+ body: JSON.stringify(u.value)
575
+ });
576
+ if (!l.ok)
577
+ throw new Error((await l.json()).error);
578
+ C.push(`/admin/tables/${p.tableName}`);
579
+ } else if (L.value) {
580
+ const l = {};
581
+ for (const c of U.value)
582
+ if (h.value[c.name] && u.value[c.name] !== G) {
583
+ if (c.name.toLowerCase().includes("password") && !u.value[c.name])
584
+ continue;
585
+ l[c.name] = u.value[c.name];
586
+ }
587
+ for (const c of N.value) {
588
+ const g = await fetch(`/api/admin/tables/${p.tableName}/${c}`, {
589
+ method: "PUT",
590
+ headers: { "Content-Type": "application/json" },
591
+ body: JSON.stringify(l)
592
+ });
593
+ if (!g.ok)
594
+ throw new Error((await g.json()).error);
595
+ }
596
+ C.push(`/admin/tables/${p.tableName}`);
597
+ } else {
598
+ const l = { ...u.value };
599
+ for (const g of U.value)
600
+ g.name.toLowerCase().includes("password") && !l[g.name] && delete l[g.name];
601
+ const c = await fetch(
602
+ `/api/admin/tables/${p.tableName}/${N.value[0]}`,
603
+ {
604
+ method: "PUT",
605
+ headers: { "Content-Type": "application/json" },
606
+ body: JSON.stringify(l)
607
+ }
608
+ );
609
+ if (!c.ok)
610
+ throw new Error((await c.json()).error);
611
+ $.value = !0;
612
+ }
613
+ } catch (l) {
614
+ d.value = String(l);
615
+ } finally {
616
+ m.value = !1;
617
+ }
618
+ }
619
+ return H(async () => {
620
+ try {
621
+ await R(), await X(), ee();
622
+ } catch (l) {
623
+ _.value = String(l);
624
+ } finally {
625
+ w.value = !1;
626
+ }
627
+ }), (l, c) => {
628
+ const g = q("RouterLink");
629
+ return t(), a("div", Ze, [
630
+ e("div", et, [
631
+ A(g, {
632
+ to: `/admin/tables/${v.tableName}`,
633
+ class: "vl-back-link"
634
+ }, {
635
+ default: z(() => [
636
+ j("← Back to " + f(v.tableName), 1)
637
+ ]),
638
+ _: 1
639
+ }, 8, ["to"]),
640
+ e("h1", tt, f(L.value ? `Edit ${N.value.length} rows` : V.value ? `New ${v.tableName} row` : "Edit row"), 1)
641
+ ]),
642
+ _.value ? (t(), a("div", at, f(_.value), 1)) : k("", !0),
643
+ d.value ? (t(), a("div", lt, f(d.value), 1)) : k("", !0),
644
+ $.value ? (t(), a("div", nt, " Saved successfully. ")) : k("", !0),
645
+ w.value ? (t(), a("div", st, "Loading…")) : (t(), a("form", {
646
+ key: 4,
647
+ class: "vl-form",
648
+ onSubmit: Y(te, ["prevent"])
649
+ }, [
650
+ (t(!0), a(D, null, M(U.value, (i) => (t(), a("div", {
651
+ key: i.name,
652
+ class: "vl-form__field"
653
+ }, [
654
+ e("div", ot, [
655
+ L.value ? I((t(), a("input", {
656
+ key: 0,
657
+ id: `enable-${i.name}`,
658
+ "onUpdate:modelValue": (T) => h.value[i.name] = T,
659
+ type: "checkbox",
660
+ class: "vl-form__field-enable"
661
+ }, null, 8, it)), [
662
+ [W, h.value[i.name]]
663
+ ]) : k("", !0),
664
+ e("label", {
665
+ for: `field-${i.name}`,
666
+ class: "vl-form__label"
667
+ }, [
668
+ j(f(i.name) + " ", 1),
669
+ e("span", rt, f(i.type), 1),
670
+ i.notnull && !i.dflt_value ? (t(), a("span", vt, "*")) : k("", !0)
671
+ ], 8, ut)
672
+ ]),
673
+ e("div", dt, [
674
+ L.value && !h.value[i.name] ? (t(), a("input", {
675
+ key: 0,
676
+ type: "text",
677
+ value: E(i.name),
678
+ disabled: "",
679
+ class: "vl-form__input vl-form__input--disabled"
680
+ }, null, 8, ct)) : (t(), a(D, { key: 1 }, [
681
+ B(i) ? I((t(), a("input", {
682
+ key: 0,
683
+ id: `field-${i.name}`,
684
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
685
+ type: "checkbox",
686
+ class: "vl-form__checkbox"
687
+ }, null, 8, mt)), [
688
+ [W, u.value[i.name]]
689
+ ]) : i.name.toLowerCase().includes("password") ? I((t(), a("input", {
690
+ key: 1,
691
+ id: `field-${i.name}`,
692
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
693
+ type: "password",
694
+ class: "vl-form__input",
695
+ placeholder: V.value ? "" : "(leave blank to keep unchanged)"
696
+ }, null, 8, _t)), [
697
+ [J, u.value[i.name]]
698
+ ]) : o(i) || S(i) ? I((t(), a("textarea", {
699
+ key: 2,
700
+ id: `field-${i.name}`,
701
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
702
+ class: "vl-form__input vl-form__input--textarea",
703
+ rows: "4"
704
+ }, null, 8, ft)), [
705
+ [J, u.value[i.name]]
706
+ ]) : n(i) ? I((t(), a("input", {
707
+ key: 3,
708
+ id: `field-${i.name}`,
709
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
710
+ type: "number",
711
+ class: "vl-form__input"
712
+ }, null, 8, bt)), [
713
+ [J, u.value[i.name]]
714
+ ]) : r(i) ? I((t(), a("input", {
715
+ key: 4,
716
+ id: `field-${i.name}`,
717
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
718
+ type: "datetime-local",
719
+ class: "vl-form__input"
720
+ }, null, 8, ht)), [
721
+ [J, u.value[i.name]]
722
+ ]) : I((t(), a("input", {
723
+ key: 5,
724
+ id: `field-${i.name}`,
725
+ "onUpdate:modelValue": (T) => u.value[i.name] = T,
726
+ type: "text",
727
+ class: "vl-form__input"
728
+ }, null, 8, pt)), [
729
+ [J, u.value[i.name]]
730
+ ])
731
+ ], 64)),
732
+ L.value && h.value[i.name] && u.value[i.name] === G ? (t(), a("span", yt, 'Cannot save "— Multiple —". Please enter a new value.')) : k("", !0)
733
+ ])
734
+ ]))), 128)),
735
+ e("div", kt, [
736
+ A(g, {
737
+ to: `/admin/tables/${v.tableName}`,
738
+ class: "vl-btn"
739
+ }, {
740
+ default: z(() => [...c[0] || (c[0] = [
741
+ j("Cancel", -1)
742
+ ])]),
743
+ _: 1
744
+ }, 8, ["to"]),
745
+ e("button", {
746
+ type: "submit",
747
+ class: "vl-btn vl-btn--primary",
748
+ disabled: m.value || P.value
749
+ }, f(m.value ? "Saving…" : "Save"), 9, $t)
750
+ ])
751
+ ], 32))
752
+ ]);
753
+ };
754
+ }
755
+ });
756
+ export {
757
+ Nt as _,
758
+ St as a,
759
+ Ct as b,
760
+ me as c,
761
+ Ke as d
762
+ };
@@ -1,6 +1,6 @@
1
1
  const SESSION_COOKIE = 'vlib-session';
2
2
  export function createAuthMiddleware(db) {
3
- return (req, res, next) => {
3
+ return (req, _res, next) => {
4
4
  req.db = db;
5
5
  const sessionId = req.cookies?.[SESSION_COOKIE];
6
6
  if (!sessionId) {
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAeA,MAAM,cAAc,GAAG,cAAc,CAAC;AAEtC,MAAM,UAAU,oBAAoB,CAAC,EAAM;IACzC,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QACZ,MAAM,SAAS,GAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC;QACpE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,EAAE;iBACf,OAAO,CACN;;;4CAGkC,CACnC;iBACA,GAAG,CAAC,SAAS,EAAE,GAAG,CASR,CAAC;YAEd,IAAI,OAAO,EAAE,CAAC;gBACZ,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,IAAI,GAAG;oBACT,EAAE,EAAE,OAAO,CAAC,GAAG;oBACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;iBACrD,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,IAAI,EAAE,CAAC;AACT,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,IAAa,EACb,IAAc,EACd,IAAkB;IAElB,IAAI,EAAE,CAAC;AACT,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAeA,MAAM,cAAc,GAAG,cAAc,CAAC;AAEtC,MAAM,UAAU,oBAAoB,CAAC,EAAM;IACzC,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QACZ,MAAM,SAAS,GAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC;QACpE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,EAAE;iBACf,OAAO,CACN;;;4CAGkC,CACnC;iBACA,GAAG,CAAC,SAAS,EAAE,GAAG,CASR,CAAC;YAEd,IAAI,OAAO,EAAE,CAAC;gBACZ,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,IAAI,GAAG;oBACT,EAAE,EAAE,OAAO,CAAC,GAAG;oBACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;iBACrD,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,IAAI,EAAE,CAAC;AACT,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,IAAa,EACb,IAAc,EACd,IAAkB;IAElB,IAAI,EAAE,CAAC;AACT,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -1,4 +1,4 @@
1
- import { _ as s, a as b, b as d, c as i, d as m } from "../AdminForm.vue_vue_type_style_index_0_lang-xCk1ywLq.js";
1
+ import { _ as s, a as b, b as d, c as i, d as m } from "../AdminForm.vue_vue_type_style_index_0_lang-BjqWn8E1.js";
2
2
  export {
3
3
  s as AdminForm,
4
4
  b as AdminTable,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as r, a as o, b as d, c as s, d as t } from "./AdminForm.vue_vue_type_style_index_0_lang-xCk1ywLq.js";
1
+ import { _ as r, a as o, b as d, c as s, d as t } from "./AdminForm.vue_vue_type_style_index_0_lang-BjqWn8E1.js";
2
2
  import { buildRoutes as i, createAuthGuard as m } from "./router/index.js";
3
3
  export {
4
4
  r as AdminForm,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincent99/vlib",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared library and scaffold generator for Vue 3 + SQLite webapps",
5
5
  "homepage": "https://github.com/vincent99/vlib#readme",
6
6
  "bugs": {
@@ -23,7 +23,7 @@
23
23
  @action="handleBulkAction"
24
24
  @sort="handleSort"
25
25
  >
26
- <template #row-actions="{ row, id }">
26
+ <template #row-actions="{ id }">
27
27
  <RouterLink
28
28
  :to="`/admin/tables/${tableName}/${id}`"
29
29
  class="vl-btn vl-btn--sm"
@@ -68,7 +68,7 @@ import type { AppLayoutProps, NavItem } from './types.js';
68
68
 
69
69
  export type { AppLayoutProps, NavItem };
70
70
 
71
- const props = withDefaults(defineProps<AppLayoutProps>(), {
71
+ const _props = withDefaults(defineProps<AppLayoutProps>(), {
72
72
  appName: 'App',
73
73
  user: null,
74
74
  navItems: () => [],
@@ -4,7 +4,10 @@
4
4
  <div class="vl-table-view__toolbar">
5
5
  <div class="vl-table-view__toolbar-left">
6
6
  <slot name="toolbar-left">
7
- <span v-if="selectedIds.size > 0" class="vl-table-view__selected-count">
7
+ <span
8
+ v-if="selectedIds.size > 0"
9
+ class="vl-table-view__selected-count"
10
+ >
8
11
  {{ selectedIds.size }} selected
9
12
  </span>
10
13
  </slot>
@@ -53,7 +56,12 @@
53
56
  {{ sortDir === 'asc' ? '↑' : '↓' }}
54
57
  </span>
55
58
  </th>
56
- <th v-if="$slots['row-actions']" class="vl-table__th vl-table__th--actions">Actions</th>
59
+ <th
60
+ v-if="$slots['row-actions']"
61
+ class="vl-table__th vl-table__th--actions"
62
+ >
63
+ Actions
64
+ </th>
57
65
  </tr>
58
66
  </thead>
59
67
  <tbody>
@@ -61,7 +69,9 @@
61
69
  v-for="row in rows"
62
70
  :key="getRowId(row)"
63
71
  class="vl-table__tr"
64
- :class="{ 'vl-table__tr--selected': selectedIds.has(getRowId(row)) }"
72
+ :class="{
73
+ 'vl-table__tr--selected': selectedIds.has(getRowId(row)),
74
+ }"
65
75
  >
66
76
  <td class="vl-table__td vl-table__td--check">
67
77
  <input
@@ -71,16 +81,19 @@
71
81
  @change="toggleRow(getRowId(row))"
72
82
  />
73
83
  </td>
74
- <td
75
- v-for="col in headers"
76
- :key="col.key"
77
- class="vl-table__td"
78
- >
79
- <slot :name="`cell-${col.key}`" :row="row" :value="(row as Record<string, unknown>)[col.key]">
80
- {{ formatCell((row as Record<string, unknown>)[col.key]) }}
84
+ <td v-for="col in headers" :key="col.key" class="vl-table__td">
85
+ <slot
86
+ :name="`cell-${col.key}`"
87
+ :row="row"
88
+ :value="getCellValue(row, col.key)"
89
+ >
90
+ {{ formatCell(getCellValue(row, col.key)) }}
81
91
  </slot>
82
92
  </td>
83
- <td v-if="$slots['row-actions']" class="vl-table__td vl-table__td--actions">
93
+ <td
94
+ v-if="$slots['row-actions']"
95
+ class="vl-table__td vl-table__td--actions"
96
+ >
84
97
  <slot :id="getRowId(row)" name="row-actions" :row="row" />
85
98
  </td>
86
99
  </tr>
@@ -99,7 +112,9 @@
99
112
  class="vl-btn vl-btn--sm"
100
113
  :disabled="page <= 1"
101
114
  @click="$emit('update:page', page - 1)"
102
- >← Prev</button>
115
+ >
116
+ ← Prev
117
+ </button>
103
118
  <span class="vl-table-view__pagination-info">
104
119
  Page {{ page }} of {{ totalPages }} &nbsp;({{ total }} total)
105
120
  </span>
@@ -107,26 +122,28 @@
107
122
  class="vl-btn vl-btn--sm"
108
123
  :disabled="page >= totalPages"
109
124
  @click="$emit('update:page', page + 1)"
110
- >Next →</button>
125
+ >
126
+ Next →
127
+ </button>
111
128
  </div>
112
129
  </div>
113
130
  </template>
114
131
 
115
132
  <script setup lang="ts">
116
- import { ref, computed } from 'vue'
117
- import type { TableHeader, TableAction } from './types.js'
133
+ import { ref, computed } from 'vue';
134
+ import type { TableHeader, TableAction } from './types.js';
118
135
 
119
- export type { TableHeader, TableAction }
136
+ export type { TableHeader, TableAction };
120
137
 
121
138
  const props = withDefaults(
122
139
  defineProps<{
123
- rows: Record<string, unknown>[]
124
- headers: TableHeader[]
125
- idKey?: string
126
- total?: number
127
- page?: number
128
- pageSize?: number
129
- actions?: TableAction[]
140
+ rows: Record<string, unknown>[];
141
+ headers: TableHeader[];
142
+ idKey?: string;
143
+ total?: number;
144
+ page?: number;
145
+ pageSize?: number;
146
+ actions?: TableAction[];
130
147
  }>(),
131
148
  {
132
149
  idKey: 'id',
@@ -135,66 +152,83 @@ const props = withDefaults(
135
152
  pageSize: 50,
136
153
  actions: () => [],
137
154
  }
138
- )
155
+ );
139
156
 
140
157
  const emit = defineEmits<{
141
- 'update:page': [page: number]
142
- 'action': [key: string, ids: (string | number)[]]
143
- 'sort': [key: string, dir: 'asc' | 'desc']
144
- }>()
158
+ 'update:page': [page: number];
159
+ action: [key: string, ids: (string | number)[]];
160
+ sort: [key: string, dir: 'asc' | 'desc'];
161
+ }>();
145
162
 
146
- const selectedIds = ref(new Set<string | number>())
147
- const sortKey = ref<string | null>(null)
148
- const sortDir = ref<'asc' | 'desc'>('asc')
163
+ const selectedIds = ref(new Set<string | number>());
164
+ const sortKey = ref<string | null>(null);
165
+ const sortDir = ref<'asc' | 'desc'>('asc');
149
166
 
150
- const totalPages = computed(() => Math.ceil((props.total || props.rows.length) / props.pageSize))
167
+ const totalPages = computed(() =>
168
+ Math.ceil((props.total || props.rows.length) / props.pageSize)
169
+ );
151
170
 
152
171
  const allSelected = computed(
153
- () => props.rows.length > 0 && props.rows.every((r) => selectedIds.value.has(getRowId(r)))
154
- )
155
- const someSelected = computed(() => props.rows.some((r) => selectedIds.value.has(getRowId(r))))
172
+ () =>
173
+ props.rows.length > 0 &&
174
+ props.rows.every((r) => selectedIds.value.has(getRowId(r)))
175
+ );
176
+ const someSelected = computed(() =>
177
+ props.rows.some((r) => selectedIds.value.has(getRowId(r)))
178
+ );
156
179
 
157
180
  function getRowId(row: Record<string, unknown>): string | number {
158
- return row[props.idKey] as string | number
181
+ return row[props.idKey] as string | number;
182
+ }
183
+
184
+ function getCellValue(row: Record<string, unknown>, key: string): unknown {
185
+ return row[key];
159
186
  }
160
187
 
161
188
  function toggleAll() {
162
189
  if (allSelected.value) {
163
- props.rows.forEach((r) => selectedIds.value.delete(getRowId(r)))
190
+ props.rows.forEach((r) => selectedIds.value.delete(getRowId(r)));
164
191
  } else {
165
- props.rows.forEach((r) => selectedIds.value.add(getRowId(r)))
192
+ props.rows.forEach((r) => selectedIds.value.add(getRowId(r)));
166
193
  }
167
194
  }
168
195
 
169
196
  function toggleRow(id: string | number) {
170
- if (selectedIds.value.has(id)) {selectedIds.value.delete(id)}
171
- else {selectedIds.value.add(id)}
197
+ if (selectedIds.value.has(id)) {
198
+ selectedIds.value.delete(id);
199
+ } else {
200
+ selectedIds.value.add(id);
201
+ }
172
202
  }
173
203
 
174
204
  function handleAction(action: TableAction) {
175
- emit('action', action.key, Array.from(selectedIds.value))
176
- selectedIds.value.clear()
205
+ emit('action', action.key, Array.from(selectedIds.value));
206
+ selectedIds.value.clear();
177
207
  }
178
208
 
179
209
  function toggleSort(key: string) {
180
210
  if (sortKey.value === key) {
181
- sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc'
211
+ sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc';
182
212
  } else {
183
- sortKey.value = key
184
- sortDir.value = 'asc'
213
+ sortKey.value = key;
214
+ sortDir.value = 'asc';
185
215
  }
186
- emit('sort', sortKey.value!, sortDir.value)
216
+ emit('sort', sortKey.value!, sortDir.value);
187
217
  }
188
218
 
189
219
  function formatCell(value: unknown): string {
190
- if (value === null || value === undefined) {return ''}
191
- if (typeof value === 'object') {return JSON.stringify(value)}
192
- const str = String(value)
193
- return str.length > 120 ? str.slice(0, 120) + '' : str
220
+ if (value === null || value === undefined) {
221
+ return '';
222
+ }
223
+ if (typeof value === 'object') {
224
+ return JSON.stringify(value);
225
+ }
226
+ const str = String(value);
227
+ return str.length > 120 ? str.slice(0, 120) + '…' : str;
194
228
  }
195
229
 
196
230
  // Expose selected IDs for parent use
197
- defineExpose({ selectedIds })
231
+ defineExpose({ selectedIds });
198
232
  </script>
199
233
 
200
234
  <style lang="scss">