@rastr/vue 0.0.0-stage → 0.1.0-beta.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/dist/index.js ADDED
@@ -0,0 +1,926 @@
1
+ // src/RsGrid.ts
2
+ import { createGridStore, createSavedViews, deserializeState, isSelected, serializeState, RastrError as RastrError2 } from "@rastr/core";
3
+ import { createGrid } from "@rastr/grid";
4
+ import { camelize, defineComponent as defineComponent5, Fragment, getCurrentInstance, h as h3, nextTick, onBeforeUnmount as onBeforeUnmount3, onMounted as onMounted3, ref as ref3, shallowRef as shallowRef3, Teleport, toRaw as toRaw3, watch as watch3 } from "vue";
5
+
6
+ // src/cell-renderer.ts
7
+ import { createVNode, defineComponent, render, shallowRef } from "vue";
8
+ function toSlotProps(ctx) {
9
+ return {
10
+ row: ctx.row,
11
+ value: ctx.value,
12
+ text: ctx.text,
13
+ key: ctx.key,
14
+ column: ctx.column,
15
+ get rowIndex() {
16
+ return ctx.rowIndex;
17
+ }
18
+ };
19
+ }
20
+ var CellHost = defineComponent({
21
+ name: "RsCell",
22
+ props: {
23
+ state: { type: Object, required: true },
24
+ getSlot: { type: Function, required: true }
25
+ },
26
+ setup(props) {
27
+ return () => props.getSlot()?.(props.state.value) ?? null;
28
+ }
29
+ });
30
+ function slotAppContext(parent) {
31
+ const provides = parent.provides;
32
+ return { ...parent.appContext, provides };
33
+ }
34
+ function mountSlot(container, getSlot, initial, appContext) {
35
+ const state = shallowRef(initial);
36
+ const vnode = createVNode(CellHost, { state, getSlot });
37
+ vnode.appContext = appContext;
38
+ render(vnode, container);
39
+ return {
40
+ update(next) {
41
+ state.value = next;
42
+ },
43
+ dispose() {
44
+ render(null, container);
45
+ }
46
+ };
47
+ }
48
+ function createVueCellRenderer(getSlot, parent) {
49
+ const appContext = slotAppContext(parent);
50
+ return (container, ctx) => {
51
+ const mounted = mountSlot(container, getSlot, toSlotProps(ctx), appContext);
52
+ return {
53
+ update: (next) => mounted.update(toSlotProps(next)),
54
+ dispose: () => mounted.dispose()
55
+ };
56
+ };
57
+ }
58
+ function createVueHeaderRenderer(getSlot, parent) {
59
+ const appContext = slotAppContext(parent);
60
+ return (container, ctx) => mountSlot(container, getSlot, { column: ctx.column, caption: ctx.caption }, appContext).dispose;
61
+ }
62
+
63
+ // src/edit-renderer.ts
64
+ var FOCUSABLE = 'input, select, textarea, button, [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
65
+ function createVueEditRenderer(getSlot, parent) {
66
+ const appContext = slotAppContext(parent);
67
+ return (container, ctx) => {
68
+ const props = (value) => ({
69
+ row: ctx.row,
70
+ value,
71
+ column: ctx.column,
72
+ setValue,
73
+ commit: ctx.commit,
74
+ cancel: ctx.cancel
75
+ });
76
+ const setValue = (value) => {
77
+ ctx.setValue(value);
78
+ mounted.update(props(value));
79
+ };
80
+ const mounted = mountSlot(container, getSlot, props(ctx.value), appContext);
81
+ return {
82
+ focus: () => container.querySelector(FOCUSABLE)?.focus(),
83
+ dispose: () => mounted.dispose()
84
+ };
85
+ };
86
+ }
87
+
88
+ // src/RsColumn.ts
89
+ import { defineComponent as defineComponent2 } from "vue";
90
+ var optionalBoolean = { type: Boolean, default: void 0 };
91
+ var RsColumn = defineComponent2({
92
+ name: "RsColumn",
93
+ props: {
94
+ field: { type: String, required: true },
95
+ caption: String,
96
+ dataType: String,
97
+ visible: optionalBoolean,
98
+ width: [Number, String],
99
+ minWidth: [Number, String],
100
+ pinned: String,
101
+ align: String,
102
+ sortable: optionalBoolean,
103
+ filterable: optionalBoolean,
104
+ searchable: optionalBoolean,
105
+ headerFilter: optionalBoolean,
106
+ resizable: optionalBoolean,
107
+ reorderable: optionalBoolean,
108
+ lookup: Array,
109
+ format: [Object, Function],
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ value: Function,
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ sortValue: Function,
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ cellClass: Function,
116
+ toFilter: Function,
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ editable: { type: [Boolean, Function], default: void 0 },
119
+ validate: Array,
120
+ /** Total summaries of the column: 'sum', 'avg', 'min', 'max', 'count' or a custom one. */
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ summary: Array,
123
+ summaryFormat: Function,
124
+ /** On a narrow grid the lowest priority hides first. */
125
+ hidingPriority: [Number, String]
126
+ },
127
+ setup() {
128
+ return () => null;
129
+ }
130
+ });
131
+ var RS_COLUMN_BOOLEAN_PROPS = /* @__PURE__ */ new Set([
132
+ "visible",
133
+ "sortable",
134
+ "filterable",
135
+ "searchable",
136
+ "headerFilter",
137
+ "resizable",
138
+ "reorderable"
139
+ ]);
140
+ var RS_COLUMN_NUMBER_PROPS = /* @__PURE__ */ new Set(["width", "minWidth", "hidingPriority"]);
141
+
142
+ // src/options.ts
143
+ import { createWebStorage, RastrError } from "@rastr/core";
144
+ function toPersistence(prop) {
145
+ if (prop === void 0) return void 0;
146
+ if (typeof prop !== "string") return { key: prop.key, storage: prop.storage };
147
+ const separator = prop.indexOf(":");
148
+ const kind = prop.slice(0, separator);
149
+ const key = prop.slice(separator + 1);
150
+ if (kind !== "local" && kind !== "session" || key === "") {
151
+ throw new RastrError("vue/state-storage", `Invalid stateStorage "${prop}"; use "local:<key>", "session:<key>" or { key, storage }`);
152
+ }
153
+ return { key, storage: createWebStorage(kind) };
154
+ }
155
+ function toStoreOptions(props, columns, carriedState) {
156
+ const configured = toPersistence(props.stateStorage);
157
+ const persistence = configured && carriedState ? { ...configured, restore: false } : configured;
158
+ const initialState = carriedState || props.initialState ? { ...props.initialState, ...carriedState } : void 0;
159
+ return {
160
+ columns,
161
+ dataSource: props.dataSource,
162
+ keyField: props.keyField,
163
+ ...props.selectionMode ? { selectionMode: props.selectionMode } : {},
164
+ ...props.pageSize ? { pageSize: props.pageSize } : {},
165
+ ...props.paging === false ? { pagingEnabled: false } : {},
166
+ ...props.scrolling ? { scrolling: props.scrolling } : {},
167
+ ...props.blockSize ? { blockSize: props.blockSize } : {},
168
+ ...initialState ? { initialState } : {},
169
+ ...persistence ? { persistence } : {}
170
+ };
171
+ }
172
+
173
+ // src/RsSavedViews.ts
174
+ import { isSavedView } from "@rastr/core";
175
+ import { createSelect } from "@rastr/controls";
176
+ import { defineComponent as defineComponent4, h as h2, onBeforeUnmount as onBeforeUnmount2, onMounted as onMounted2, ref as ref2, shallowRef as shallowRef2, toRaw as toRaw2, watch as watch2 } from "vue";
177
+
178
+ // src/RsFilterBuilder.ts
179
+ import { createFilterBuilder } from "@rastr/filter-builder";
180
+ import { defineComponent as defineComponent3, h, onBeforeUnmount, onMounted, ref, toRaw, watch } from "vue";
181
+ var RsFilterBuilder = defineComponent3({
182
+ name: "RsFilterBuilder",
183
+ props: {
184
+ modelValue: { type: Object, default: null },
185
+ columns: { type: Array, required: true },
186
+ loadValues: Function,
187
+ locale: String,
188
+ messages: Object
189
+ },
190
+ emits: {
191
+ "update:modelValue": (value) => value !== void 0
192
+ },
193
+ setup(props, { emit, expose }) {
194
+ const host = ref(null);
195
+ let builder = null;
196
+ const create = () => {
197
+ if (!host.value) return;
198
+ builder?.destroy();
199
+ builder = createFilterBuilder(host.value, {
200
+ columns: toRaw(props.columns),
201
+ value: props.modelValue ? toRaw(props.modelValue) : null,
202
+ onChange: (value) => emit("update:modelValue", value),
203
+ ...props.locale ? { locale: props.locale } : {},
204
+ ...props.messages ? { messages: props.messages } : {},
205
+ ...props.loadValues ? { loadValues: props.loadValues } : {}
206
+ });
207
+ };
208
+ expose({ flush: () => builder?.flush() });
209
+ onMounted(create);
210
+ onBeforeUnmount(() => {
211
+ builder?.destroy();
212
+ builder = null;
213
+ });
214
+ watch(
215
+ () => props.modelValue,
216
+ (value) => builder?.setValue(value ? toRaw(value) : null)
217
+ );
218
+ watch(() => [props.columns, props.locale, props.messages], create);
219
+ return () => h("div", { ref: host, class: "rs-vue-filter-builder" });
220
+ }
221
+ });
222
+
223
+ // src/RsSavedViews.ts
224
+ var MESSAGES = {
225
+ cs: {
226
+ view: "Pohled",
227
+ noView: "(bez pohledu)",
228
+ newView: "Nov\xFD pohled",
229
+ edit: "Upravit",
230
+ remove: "Smazat",
231
+ name: "N\xE1zev",
232
+ isDefault: "V\xFDchoz\xED",
233
+ save: "Ulo\u017Eit",
234
+ cancel: "Zru\u0161it",
235
+ confirmRemove: "Smazat pohled \u201E{name}\u201C?"
236
+ },
237
+ en: {
238
+ view: "View",
239
+ noView: "(no view)",
240
+ newView: "New view",
241
+ edit: "Edit",
242
+ remove: "Delete",
243
+ name: "Name",
244
+ isDefault: "Default",
245
+ save: "Save",
246
+ cancel: "Cancel",
247
+ confirmRemove: 'Delete view "{name}"?'
248
+ }
249
+ };
250
+ var RsSavedViews = defineComponent4({
251
+ name: "RsSavedViews",
252
+ props: {
253
+ views: { type: Object, required: true },
254
+ store: { type: Object, required: true },
255
+ columns: { type: Array, required: true },
256
+ /** Asked before a view is deleted; defaults to window.confirm. */
257
+ confirm: Function,
258
+ locale: String,
259
+ messages: Object
260
+ },
261
+ setup(props) {
262
+ const text = () => ({ ...MESSAGES[props.locale?.toLowerCase().startsWith("en") ? "en" : "cs"], ...props.messages });
263
+ const snapshot = shallowRef2(props.views.getSnapshot());
264
+ let unsubscribe = props.views.subscribe((next) => snapshot.value = next);
265
+ watch2(
266
+ () => props.views,
267
+ (views) => {
268
+ unsubscribe();
269
+ snapshot.value = views.getSnapshot();
270
+ unsubscribe = views.subscribe((next) => snapshot.value = next);
271
+ }
272
+ );
273
+ onBeforeUnmount2(() => unsubscribe());
274
+ let picker = null;
275
+ let shownOptions = "";
276
+ const pickerHost = ref2(null);
277
+ const syncPicker = () => {
278
+ if (!picker) return;
279
+ const t = text();
280
+ const options = [
281
+ { value: "", label: t.noView },
282
+ ...snapshot.value.views.map((view) => ({
283
+ value: view.id,
284
+ label: view.name,
285
+ ...isSavedView(view) ? {} : { disabled: true, title: view.invalid }
286
+ }))
287
+ ];
288
+ const key = JSON.stringify(options);
289
+ if (key !== shownOptions) {
290
+ shownOptions = key;
291
+ picker.setOptions(options);
292
+ }
293
+ picker.setValue(snapshot.value.currentId ?? "");
294
+ picker.setLabel(t.view);
295
+ };
296
+ onMounted2(() => {
297
+ picker = createSelect({
298
+ options: [],
299
+ value: "",
300
+ label: text().view,
301
+ className: "rs-fb__select",
302
+ onChange: (id) => {
303
+ if (id) props.views.apply(id);
304
+ }
305
+ });
306
+ pickerHost.value?.append(picker.element);
307
+ syncPicker();
308
+ });
309
+ onBeforeUnmount2(() => picker?.destroy());
310
+ watch2(() => [snapshot.value, props.locale, props.messages], syncPicker);
311
+ const dialog = ref2(null);
312
+ const builder = ref2(null);
313
+ const editingId = ref2(null);
314
+ const name = ref2("");
315
+ const isDefault = ref2(false);
316
+ const filter = shallowRef2(null);
317
+ const error = ref2("");
318
+ const open = ref2(false);
319
+ const current = () => {
320
+ const view = snapshot.value.views.find((v) => v.id === snapshot.value.currentId);
321
+ return view && isSavedView(view) ? view : void 0;
322
+ };
323
+ const show = (view) => {
324
+ editingId.value = view?.id ?? null;
325
+ name.value = view?.name ?? "";
326
+ isDefault.value = view?.isDefault ?? false;
327
+ filter.value = props.store.getSnapshot().state.filter;
328
+ error.value = "";
329
+ open.value = true;
330
+ const el = dialog.value;
331
+ if (el && typeof el.showModal === "function") el.showModal();
332
+ else el?.setAttribute("open", "");
333
+ };
334
+ const close = () => {
335
+ open.value = false;
336
+ const el = dialog.value;
337
+ if (el && typeof el.close === "function") el.close();
338
+ else el?.removeAttribute("open");
339
+ };
340
+ const save = async () => {
341
+ if (name.value.trim() === "") return;
342
+ builder.value?.flush();
343
+ props.store.dispatch({ type: "setFilter", filter: filter.value });
344
+ try {
345
+ await props.views.saveCurrent({ ...editingId.value ? { id: editingId.value } : {}, name: name.value.trim(), isDefault: isDefault.value });
346
+ close();
347
+ } catch (reason) {
348
+ error.value = reason instanceof Error ? reason.message : String(reason);
349
+ }
350
+ };
351
+ const remove = async () => {
352
+ const view = current();
353
+ if (!view) return;
354
+ const message = text().confirmRemove.replace("{name}", view.name);
355
+ const confirmed = props.confirm ? await props.confirm(message) : window.confirm(message);
356
+ if (!confirmed) return;
357
+ try {
358
+ await props.views.remove(view.id);
359
+ } catch {
360
+ }
361
+ };
362
+ const describe = (reason) => reason instanceof Error ? reason.message : String(reason);
363
+ const loadValues = (field, search) => props.store.loadDistinct(field, { search });
364
+ return () => {
365
+ const t = text();
366
+ return h2("div", { class: "rs-fb rs-fb__views" }, [
367
+ // Host of the picker (a controls select appended on mount).
368
+ h2("span", { ref: pickerHost, style: "display: contents" }),
369
+ h2("button", { type: "button", class: "rs-fb__btn rs-fb__add", onClick: () => show(void 0) }, t.newView),
370
+ h2("button", { type: "button", class: "rs-fb__btn", disabled: !current(), onClick: () => show(current()) }, t.edit),
371
+ h2("button", { type: "button", class: "rs-fb__btn rs-fb__danger", disabled: !current(), onClick: () => void remove() }, t.remove),
372
+ // Failed list loads and deletes; save errors are shown in the dialog.
373
+ snapshot.value.status === "error" && !open.value ? h2("p", { class: "rs-fb__error", role: "alert" }, describe(snapshot.value.error)) : null,
374
+ h2("dialog", { ref: dialog, class: "rs-fb rs-fb__dialog", onClose: () => open.value = false }, [
375
+ h2("label", { class: "rs-fb__field-label" }, [
376
+ t.name,
377
+ h2("input", {
378
+ class: "rs-fb__input",
379
+ name: "name",
380
+ value: name.value,
381
+ onInput: (event) => name.value = event.target.value
382
+ })
383
+ ]),
384
+ h2("label", { class: "rs-fb__negate" }, [
385
+ h2("input", {
386
+ class: "rs-fb__check",
387
+ type: "checkbox",
388
+ name: "isDefault",
389
+ checked: isDefault.value,
390
+ onChange: (event) => isDefault.value = event.target.checked
391
+ }),
392
+ t.isDefault
393
+ ]),
394
+ // The builder is mounted only while the dialog is open, over the grid's current filter.
395
+ open.value ? h2(RsFilterBuilder, {
396
+ ref: builder,
397
+ modelValue: filter.value,
398
+ "onUpdate:modelValue": (value) => filter.value = value,
399
+ columns: toRaw2(props.columns),
400
+ loadValues,
401
+ ...props.locale ? { locale: props.locale } : {}
402
+ }) : null,
403
+ error.value ? h2("p", { class: "rs-fb__error", role: "alert" }, error.value) : null,
404
+ h2("div", { class: "rs-fb__dialog-actions" }, [
405
+ h2("button", { type: "button", class: "rs-fb__btn", onClick: close }, t.cancel),
406
+ h2("button", { type: "button", class: "rs-fb__btn rs-fb__primary", onClick: () => void save() }, t.save)
407
+ ])
408
+ ])
409
+ ]);
410
+ };
411
+ }
412
+ });
413
+
414
+ // src/RsGrid.ts
415
+ var optionalBoolean2 = { type: Boolean, default: void 0 };
416
+ var EXPORT_FORMATS = ["xlsx", "csv", "pdf"];
417
+ var rsGridProps = {
418
+ dataSource: Object,
419
+ keyField: String,
420
+ store: Object,
421
+ columns: Array,
422
+ selectionMode: String,
423
+ pageSize: Number,
424
+ paging: optionalBoolean2,
425
+ scrolling: String,
426
+ blockSize: Number,
427
+ stateStorage: [String, Object],
428
+ initialState: Object,
429
+ /** v-model:state */
430
+ state: Object,
431
+ showCheckboxes: optionalBoolean2,
432
+ filterRow: optionalBoolean2,
433
+ headerFilter: optionalBoolean2,
434
+ columnChooser: optionalBoolean2,
435
+ pager: { type: [Boolean, Object], default: void 0 },
436
+ toolbar: optionalBoolean2,
437
+ rowClass: Function,
438
+ height: String,
439
+ locale: String,
440
+ messages: Object,
441
+ theme: String,
442
+ density: String,
443
+ /** Editing; only the mode is structural, callbacks are read when called (inline objects are fine). */
444
+ editing: Object,
445
+ /** Drag handle column; onReorder, canDrag and sortField are read from the latest props on every move. */
446
+ rowReorder: Object,
447
+ /** Caption rows above the column headers (bands follow their columns when they move). */
448
+ headerBands: Array,
449
+ /** Renders only the body cells in view; 'auto' (default) from 50 data columns. */
450
+ columnVirtualization: { type: [Boolean, String], default: void 0 },
451
+ /** Rows that get a detail toggle (with the #detail slot); defaults to all rows. Read on every render of a row. */
452
+ detailCanExpand: Function,
453
+ /** Storage of the user's saved views; shows the view picker in the toolbar (unless #toolbar-start is used). */
454
+ savedViews: Object,
455
+ /** { fileName, sheetName, title, maxRows, formats: { xlsx: () => import('@rastr/export-xlsx'), … } } */
456
+ export: Object
457
+ };
458
+ function readChildColumns(nodes) {
459
+ const columns = [];
460
+ const slotMap = /* @__PURE__ */ new Map();
461
+ const visit = (node) => {
462
+ if (Array.isArray(node)) {
463
+ node.forEach(visit);
464
+ return;
465
+ }
466
+ const vnode = node && typeof node === "object" ? node : null;
467
+ if (!vnode) return;
468
+ if (vnode.type === Fragment) {
469
+ visit(vnode.children);
470
+ return;
471
+ }
472
+ if (vnode.type !== RsColumn) return;
473
+ const column = {};
474
+ for (const [rawKey, rawValue] of Object.entries(vnode.props ?? {})) {
475
+ const key = camelize(rawKey);
476
+ if (RS_COLUMN_BOOLEAN_PROPS.has(key)) column[key] = rawValue === "" || rawValue === true || rawValue === "true";
477
+ else if (key === "editable") column[key] = typeof rawValue === "function" ? rawValue : rawValue === "" || rawValue === true || rawValue === "true";
478
+ else if (RS_COLUMN_NUMBER_PROPS.has(key)) column[key] = Number(rawValue);
479
+ else column[key] = rawValue;
480
+ }
481
+ const children = vnode.children ?? {};
482
+ columns.push(column);
483
+ const slots = {};
484
+ for (const name of ["cell", "header", "edit"]) {
485
+ const slot = children[name];
486
+ if (slot) slots[name] = slot;
487
+ }
488
+ slotMap.set(String(column["field"]), slots);
489
+ };
490
+ visit(nodes);
491
+ if (columns.length === 0) return null;
492
+ const signature = JSON.stringify(columns.map((c, i) => [c, slotMap.has(c.field) ? Object.keys(slotMap.get(c.field)) : [], i]));
493
+ return { columns, slots: slotMap, signature };
494
+ }
495
+ function placeNewColumns(states, definitions, previous) {
496
+ const known = new Set(previous.map((c) => c.field));
497
+ const result = states.filter((c) => known.has(c.field));
498
+ definitions.forEach((definition, index) => {
499
+ if (known.has(definition.field)) return;
500
+ const state = states.find((c) => c.field === definition.field);
501
+ if (!state) return;
502
+ const before = definitions.slice(0, index).reverse().find((d) => result.some((r) => r.field === d.field))?.field;
503
+ const at = before === void 0 ? 0 : result.findIndex((r) => r.field === before) + 1;
504
+ result.splice(at, 0, state);
505
+ });
506
+ return result;
507
+ }
508
+ var identities = /* @__PURE__ */ new WeakMap();
509
+ var nextIdentity = 0;
510
+ function identity(value) {
511
+ let id = identities.get(value);
512
+ if (id === void 0) {
513
+ id = ++nextIdentity;
514
+ identities.set(value, id);
515
+ }
516
+ return id;
517
+ }
518
+ function structuralSignature(values) {
519
+ return JSON.stringify(
520
+ values,
521
+ (_key, value) => typeof value === "function" ? `fn#${identity(value)}` : value
522
+ );
523
+ }
524
+ var RsGrid = defineComponent5({
525
+ name: "RsGrid",
526
+ props: rsGridProps,
527
+ emits: {
528
+ rowClick: (payload) => payload !== void 0,
529
+ rowDblClick: (payload) => payload !== void 0,
530
+ rowActivate: (payload) => payload !== void 0,
531
+ focusedRowChange: (payload) => payload !== void 0,
532
+ selectionChange: (keys, selection) => keys !== void 0 && selection !== void 0,
533
+ stateChange: (state) => state !== void 0,
534
+ loadError: (error) => error !== void 0 || error === void 0,
535
+ "update:state": (state) => state !== void 0,
536
+ copy: (result) => result !== void 0,
537
+ saveError: (payload) => payload !== void 0,
538
+ exportError: (error) => error !== void 0 || error === void 0,
539
+ reorderError: (payload) => payload !== void 0,
540
+ detailToggle: (payload) => payload !== void 0
541
+ },
542
+ slots: Object,
543
+ setup(props, { slots, emit, expose }) {
544
+ const instance = getCurrentInstance();
545
+ const host = ref3(null);
546
+ let store = null;
547
+ let ownsStore = false;
548
+ let grid = null;
549
+ let currentColumns = [];
550
+ let unsubscribe = null;
551
+ let children = null;
552
+ let rebuildQueued = false;
553
+ let mounted = false;
554
+ let warnedMissingSource = false;
555
+ const toolbarTarget = () => {
556
+ const el = document.createElement("div");
557
+ el.style.display = "contents";
558
+ return el;
559
+ };
560
+ const toolbarStart = toolbarTarget();
561
+ const toolbarEnd = toolbarTarget();
562
+ const views = shallowRef3(null);
563
+ const showViews = () => !!props.savedViews && !slots["toolbar-start"];
564
+ const requireStore = () => {
565
+ if (!store) throw new RastrError2("vue/not-mounted", "RsGrid is not mounted yet");
566
+ return store;
567
+ };
568
+ const baseColumns = () => props.columns ?? children?.columns ?? [];
569
+ const resolveColumns = () => baseColumns().map((column) => {
570
+ const field = column.field;
571
+ const cellSlot = () => slots[`cell-${field}`] ?? children?.slots.get(field)?.cell;
572
+ const headerSlot = () => children?.slots.get(field)?.header;
573
+ const editSlot = () => slots[`edit-${field}`] ?? children?.slots.get(field)?.edit;
574
+ return {
575
+ ...column,
576
+ // An editor slot makes the column editable unless editable says otherwise.
577
+ ...editSlot() ? { editor: createVueEditRenderer(editSlot, instance), editable: column.editable ?? true } : {},
578
+ ...cellSlot() ? { cellRenderer: createVueCellRenderer(cellSlot, instance) } : {},
579
+ ...headerSlot() ? { headerRenderer: createVueHeaderRenderer(headerSlot, instance) } : {}
580
+ };
581
+ });
582
+ const viewOptions = (columns, gridStore) => {
583
+ const options = { store: gridStore, columns };
584
+ const keys = [
585
+ "showCheckboxes",
586
+ "filterRow",
587
+ "headerFilter",
588
+ "columnChooser",
589
+ "pager",
590
+ "rowClass",
591
+ "height",
592
+ "locale",
593
+ "messages",
594
+ "theme",
595
+ "density",
596
+ "headerBands",
597
+ "columnVirtualization"
598
+ ];
599
+ for (const key of keys) {
600
+ if (props[key] !== void 0) options[key] = props[key];
601
+ }
602
+ options["onCopy"] = (result) => emit("copy", result);
603
+ if (props.editing) options["editing"] = latestEditing(props.editing.mode);
604
+ if (props.rowReorder) options["rowReorder"] = latestReorder();
605
+ if (detailSlot()) options["detail"] = detailOptions();
606
+ if (availableFormats().length > 0) options["export"] = latestExport(availableFormats());
607
+ if (instance.vnode.props?.["onExportError"]) options["onExportError"] = (error) => emit("exportError", error);
608
+ if (props.toolbar === false) options["toolbar"] = false;
609
+ else {
610
+ options["toolbar"] = {
611
+ ...slots["toolbar-start"] || showViews() ? { start: toolbarStart } : {},
612
+ ...slots["toolbar-end"] ? { end: toolbarEnd } : {}
613
+ };
614
+ }
615
+ return options;
616
+ };
617
+ const availableFormats = () => {
618
+ const settings = props.export;
619
+ const server = settings?.server ? settings.serverFormats ?? EXPORT_FORMATS : [];
620
+ return EXPORT_FORMATS.filter((format) => !!settings?.formats?.[format] || server.includes(format));
621
+ };
622
+ const latestExport = (formats) => ({
623
+ get fileName() {
624
+ return props.export?.fileName;
625
+ },
626
+ get sheetName() {
627
+ return props.export?.sheetName;
628
+ },
629
+ get title() {
630
+ return props.export?.title;
631
+ },
632
+ get maxRows() {
633
+ return props.export?.maxRows;
634
+ },
635
+ get pdf() {
636
+ return props.export?.pdf;
637
+ },
638
+ formats: Object.fromEntries(
639
+ formats.filter((format) => !!props.export?.formats?.[format]).map((format) => [format, () => props.export.formats[format]()])
640
+ ),
641
+ ...props.export?.server ? {
642
+ server: (request) => props.export.server(request),
643
+ serverFormats: formats.filter((format) => (props.export?.serverFormats ?? EXPORT_FORMATS).includes(format))
644
+ } : {}
645
+ });
646
+ const latestEditing = (mode) => ({
647
+ mode,
648
+ onSave: async (changes) => props.editing?.onSave(changes),
649
+ allowDelete: (row) => {
650
+ const allow = props.editing?.allowDelete;
651
+ return typeof allow === "function" ? allow(row) : allow === true;
652
+ },
653
+ confirmDelete: async (row) => props.editing?.confirmDelete ? props.editing.confirmDelete(row) : true
654
+ });
655
+ const detailSlot = () => slots.detail;
656
+ const detailOptions = () => {
657
+ const appContext = slotAppContext(instance);
658
+ return {
659
+ render(container, ctx) {
660
+ const mounted2 = mountSlot(container, detailSlot, { row: ctx.row, key: ctx.key }, appContext);
661
+ return { update: (next) => mounted2.update({ row: next.row, key: next.key }), dispose: () => mounted2.dispose() };
662
+ },
663
+ canExpand: (row) => props.detailCanExpand?.(row) ?? true
664
+ };
665
+ };
666
+ const latestReorder = () => ({
667
+ onReorder: (event) => props.rowReorder?.onReorder(event),
668
+ canDrag: (row) => props.rowReorder?.canDrag?.(row) ?? true,
669
+ get sortField() {
670
+ return props.rowReorder?.sortField;
671
+ }
672
+ });
673
+ const emitState = (state) => {
674
+ emit("stateChange", state);
675
+ emit("update:state", state);
676
+ };
677
+ const watchStore = (gridStore) => {
678
+ let last = gridStore.getSnapshot();
679
+ return gridStore.subscribe((snapshot) => {
680
+ const previous = last;
681
+ last = snapshot;
682
+ if (snapshot.state !== previous.state) emitState(snapshot.state);
683
+ if (snapshot.state.selection !== previous.state.selection) {
684
+ const selection = snapshot.state.selection;
685
+ emit("selectionChange", selection.mode === "include" ? selection.keys : [], selection);
686
+ }
687
+ if (snapshot.status === "error" && snapshot.error !== previous.error) emit("loadError", snapshot.error);
688
+ });
689
+ };
690
+ const build = (carriedState) => {
691
+ if (!host.value) return;
692
+ const columns = resolveColumns();
693
+ if (props.store) {
694
+ store = props.store;
695
+ ownsStore = false;
696
+ } else {
697
+ if (!props.dataSource || !props.keyField) {
698
+ if (!warnedMissingSource) {
699
+ warnedMissingSource = true;
700
+ console.warn("[rastr] RsGrid is waiting for a store or a dataSource with keyField");
701
+ }
702
+ return;
703
+ }
704
+ store = createGridStore(
705
+ toStoreOptions(
706
+ {
707
+ dataSource: props.dataSource,
708
+ keyField: props.keyField,
709
+ selectionMode: props.selectionMode,
710
+ pageSize: props.pageSize,
711
+ paging: props.paging,
712
+ scrolling: props.scrolling,
713
+ blockSize: props.blockSize,
714
+ stateStorage: props.stateStorage,
715
+ initialState: props.state ?? props.initialState
716
+ },
717
+ columns,
718
+ carriedState
719
+ )
720
+ );
721
+ ownsStore = true;
722
+ }
723
+ unsubscribe = watchStore(store);
724
+ currentColumns = columns;
725
+ grid = createGrid(host.value, viewOptions(columns, store));
726
+ grid.on("rowClick", (payload) => emit("rowClick", payload));
727
+ grid.on("rowDblClick", (payload) => emit("rowDblClick", payload));
728
+ grid.on("rowActivate", (payload) => emit("rowActivate", payload));
729
+ grid.on("focusedRowChange", (payload) => emit("focusedRowChange", payload));
730
+ grid.on("saveError", (payload) => emit("saveError", payload));
731
+ grid.on("detailToggle", (payload) => emit("detailToggle", payload));
732
+ if (instance.vnode.props?.["onReorderError"]) grid.on("reorderError", (payload) => emit("reorderError", payload));
733
+ if (props.savedViews) {
734
+ const controller = createSavedViews(store, baseColumns(), props.savedViews);
735
+ views.value = controller;
736
+ if (!carriedState && !store.restored) void controller.applyDefault();
737
+ else void controller.reload();
738
+ }
739
+ };
740
+ const teardown = () => {
741
+ views.value?.destroy();
742
+ views.value = null;
743
+ grid?.destroy();
744
+ grid = null;
745
+ unsubscribe?.();
746
+ unsubscribe = null;
747
+ if (ownsStore) store?.destroy();
748
+ store = null;
749
+ };
750
+ const rebuild = () => {
751
+ if (!mounted) return;
752
+ if (!store) {
753
+ build();
754
+ if (store) emit("update:state", store.getSnapshot().state);
755
+ return;
756
+ }
757
+ const previous = store.getSnapshot().state;
758
+ const columns = baseColumns();
759
+ const restored = deserializeState(serializeState(previous), columns);
760
+ const carried = restored ? {
761
+ ...restored,
762
+ ...restored.columns ? { columns: placeNewColumns(restored.columns, columns, previous.columns) } : {},
763
+ paging: { pageSize: previous.paging.pageSize, pageIndex: previous.paging.pageIndex }
764
+ } : void 0;
765
+ teardown();
766
+ build(carried);
767
+ if (store) emitState(store.getSnapshot().state);
768
+ };
769
+ const queueRebuild = () => {
770
+ if (rebuildQueued || !mounted) return;
771
+ rebuildQueued = true;
772
+ void nextTick(() => {
773
+ rebuildQueued = false;
774
+ rebuild();
775
+ });
776
+ };
777
+ onMounted3(() => {
778
+ mounted = true;
779
+ build();
780
+ if (store) emit("update:state", store.getSnapshot().state);
781
+ });
782
+ onBeforeUnmount3(() => {
783
+ mounted = false;
784
+ teardown();
785
+ });
786
+ watch3(
787
+ () => structuralSignature([
788
+ props.columns,
789
+ props.dataSource,
790
+ props.keyField,
791
+ props.store,
792
+ props.scrolling ?? null,
793
+ props.blockSize ?? null,
794
+ props.selectionMode,
795
+ props.locale,
796
+ props.messages,
797
+ props.filterRow,
798
+ props.headerFilter,
799
+ props.columnChooser,
800
+ props.pager,
801
+ props.showCheckboxes,
802
+ props.toolbar,
803
+ props.editing?.mode ?? null,
804
+ props.rowReorder ? "reorder" : null,
805
+ props.headerBands ?? null,
806
+ props.columnVirtualization ?? null,
807
+ // The detail column comes and goes with the slot.
808
+ detailSlot() ? "detail" : null,
809
+ props.savedViews ? `views#${identity(props.savedViews)}` : null,
810
+ // Only the set of formats changes the toolbar; names and loaders are read when exporting.
811
+ availableFormats().join(",")
812
+ ]),
813
+ (next, previous) => {
814
+ if (next !== previous) queueRebuild();
815
+ }
816
+ );
817
+ watch3(
818
+ () => props.state,
819
+ (next) => {
820
+ const raw = next ? toRaw3(next) : void 0;
821
+ if (raw && store && raw !== store.getSnapshot().state) store.dispatch({ type: "replaceState", state: raw });
822
+ }
823
+ );
824
+ watch3(
825
+ () => [props.theme, props.density, props.rowClass, props.height],
826
+ ([theme, density, rowClass, height]) => grid?.setOptions({ theme, density, rowClass, height })
827
+ );
828
+ const api = {
829
+ get store() {
830
+ return requireStore();
831
+ },
832
+ refresh: () => requireStore().reload(),
833
+ setFilter: (filter) => requireStore().dispatch({ type: "setFilter", filter }),
834
+ setColumnFilter: (field, filter) => requireStore().dispatch({ type: "setColumnFilter", field, filter }),
835
+ clearFilters() {
836
+ requireStore().dispatch({ type: "setFilter", filter: null });
837
+ requireStore().dispatch({ type: "setSearch", search: "" });
838
+ },
839
+ getState: () => requireStore().getSnapshot().state,
840
+ getSelectedKeys() {
841
+ const selection = requireStore().getSnapshot().state.selection;
842
+ if (selection.mode === "exclude") {
843
+ throw new RastrError2("vue/selection-all", "All rows are selected; use getSelection() to read the exclusion list");
844
+ }
845
+ return selection.keys;
846
+ },
847
+ getSelection: () => requireStore().getSnapshot().state.selection,
848
+ getSelectedRows() {
849
+ const gridStore = requireStore();
850
+ const { rows, state } = gridStore.getSnapshot();
851
+ return rows.filter((row) => isSelected(state.selection, gridStore.getRowKey(row)));
852
+ },
853
+ selectAll: () => requireStore().dispatch({ type: "selectAll" }),
854
+ clearSelection: () => requireStore().dispatch({ type: "clearSelection" }),
855
+ focusRow: (key) => grid?.focusRow(key),
856
+ expandDetail: (key) => grid?.expandDetail(key),
857
+ collapseDetail: (key) => grid?.collapseDetail(key),
858
+ isDetailExpanded: (key) => grid?.isDetailExpanded(key) ?? false,
859
+ scrollToIndex: (index) => grid?.scrollToIndex(index),
860
+ export: (format) => grid?.export(format) ?? Promise.resolve(),
861
+ get savedViews() {
862
+ return views.value;
863
+ },
864
+ get columns() {
865
+ return currentColumns;
866
+ }
867
+ };
868
+ expose(api);
869
+ return () => {
870
+ const declared = readChildColumns(slots.default?.({}));
871
+ if (declared && props.columns) {
872
+ throw new RastrError2("vue/columns-twice", "RsGrid got both the columns prop and RsColumn children; use one of them");
873
+ }
874
+ const changed = (declared?.signature ?? "") !== (children?.signature ?? "");
875
+ children = declared;
876
+ if (changed) queueRebuild();
877
+ const nodes = [h3("div", { ref: host })];
878
+ const start = slots["toolbar-start"];
879
+ const end = slots["toolbar-end"];
880
+ if (start) nodes.push(h3(Teleport, { to: toolbarStart }, start()));
881
+ else if (showViews() && views.value && store) {
882
+ nodes.push(
883
+ h3(Teleport, { to: toolbarStart }, [
884
+ h3(RsSavedViews, { views: views.value, store, columns: baseColumns(), ...props.locale ? { locale: props.locale } : {} })
885
+ ])
886
+ );
887
+ }
888
+ if (end) nodes.push(h3(Teleport, { to: toolbarEnd }, end()));
889
+ return h3("div", { class: "rs-vue" }, nodes);
890
+ };
891
+ }
892
+ });
893
+
894
+ // src/define-columns.ts
895
+ function defineColumns(columns) {
896
+ return columns;
897
+ }
898
+
899
+ // src/index.ts
900
+ import { and, cond, createArrayDataSource, not, or } from "@rastr/core";
901
+ import { createRemoteExport, createSavedViews as createSavedViews2 } from "@rastr/core";
902
+ import { actionsColumn, custom, editActionsColumn, pattern, range, required, rowNumberColumn } from "@rastr/grid";
903
+ export {
904
+ RsColumn,
905
+ RsFilterBuilder,
906
+ RsGrid,
907
+ RsSavedViews,
908
+ actionsColumn,
909
+ and,
910
+ cond,
911
+ createArrayDataSource,
912
+ createRemoteExport,
913
+ createSavedViews2 as createSavedViews,
914
+ createVueCellRenderer,
915
+ createVueHeaderRenderer,
916
+ custom,
917
+ defineColumns,
918
+ editActionsColumn,
919
+ not,
920
+ or,
921
+ pattern,
922
+ range,
923
+ required,
924
+ rowNumberColumn
925
+ };
926
+ //# sourceMappingURL=index.js.map