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