@erplora/outfitkit 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/LICENSE +21 -0
- package/README.md +457 -0
- package/dist/base/anchor.d.ts +13 -0
- package/dist/base/define.d.ts +1 -0
- package/dist/base/relay.d.ts +1 -0
- package/dist/cdn.d.ts +96 -0
- package/dist/components/ok-app-launcher/ok-app-launcher.d.ts +57 -0
- package/dist/components/ok-audio/ok-audio.d.ts +45 -0
- package/dist/components/ok-avatar/ok-avatar.d.ts +36 -0
- package/dist/components/ok-avatar-group/ok-avatar-group.d.ts +38 -0
- package/dist/components/ok-bar-list/ok-bar-list.d.ts +36 -0
- package/dist/components/ok-bento/ok-bento.d.ts +17 -0
- package/dist/components/ok-bento-item/ok-bento-item.d.ts +34 -0
- package/dist/components/ok-calculator/ok-calculator.d.ts +46 -0
- package/dist/components/ok-calendar/ok-calendar.d.ts +63 -0
- package/dist/components/ok-carousel/ok-carousel.d.ts +48 -0
- package/dist/components/ok-chart/ok-chart.d.ts +55 -0
- package/dist/components/ok-chat/ok-chat.d.ts +54 -0
- package/dist/components/ok-coachmark/ok-coachmark.d.ts +69 -0
- package/dist/components/ok-code/ok-code.d.ts +28 -0
- package/dist/components/ok-color-picker/ok-color-picker.d.ts +63 -0
- package/dist/components/ok-combo/ok-combo.d.ts +46 -0
- package/dist/components/ok-command-palette/ok-command-palette.d.ts +72 -0
- package/dist/components/ok-contact-form/ok-contact-form.d.ts +54 -0
- package/dist/components/ok-cropper/ok-cropper.d.ts +60 -0
- package/dist/components/ok-cta-band/ok-cta-band.d.ts +18 -0
- package/dist/components/ok-currency/ok-currency.d.ts +31 -0
- package/dist/components/ok-data-table/ok-data-table.d.ts +312 -0
- package/dist/components/ok-date-picker/ok-date-picker.d.ts +81 -0
- package/dist/components/ok-detail-list/ok-detail-list.d.ts +30 -0
- package/dist/components/ok-diff/ok-diff.d.ts +38 -0
- package/dist/components/ok-donut/ok-donut.d.ts +38 -0
- package/dist/components/ok-drawer/ok-drawer.d.ts +56 -0
- package/dist/components/ok-dropzone/ok-dropzone.d.ts +48 -0
- package/dist/components/ok-empty-state/ok-empty-state.d.ts +16 -0
- package/dist/components/ok-error-page/ok-error-page.d.ts +77 -0
- package/dist/components/ok-event-card/ok-event-card.d.ts +56 -0
- package/dist/components/ok-feature-card/ok-feature-card.d.ts +23 -0
- package/dist/components/ok-file-item/ok-file-item.d.ts +31 -0
- package/dist/components/ok-file-manager/ok-file-manager.d.ts +145 -0
- package/dist/components/ok-footer/ok-footer.d.ts +10 -0
- package/dist/components/ok-funnel/ok-funnel.d.ts +31 -0
- package/dist/components/ok-gallery/ok-gallery.d.ts +34 -0
- package/dist/components/ok-gauge/ok-gauge.d.ts +49 -0
- package/dist/components/ok-heatmap/ok-heatmap.d.ts +45 -0
- package/dist/components/ok-hero/ok-hero.d.ts +10 -0
- package/dist/components/ok-hover-card/ok-hover-card.d.ts +76 -0
- package/dist/components/ok-icon-tile/ok-icon-tile.d.ts +24 -0
- package/dist/components/ok-image/ok-image.d.ts +56 -0
- package/dist/components/ok-inline-feedback/ok-inline-feedback.d.ts +33 -0
- package/dist/components/ok-invoice/ok-invoice.d.ts +137 -0
- package/dist/components/ok-json-viewer/ok-json-viewer.d.ts +31 -0
- package/dist/components/ok-kanban/ok-kanban.d.ts +56 -0
- package/dist/components/ok-kbd/ok-kbd.d.ts +21 -0
- package/dist/components/ok-keyboard/ok-keyboard.d.ts +35 -0
- package/dist/components/ok-kpi/ok-kpi.d.ts +24 -0
- package/dist/components/ok-language-select/ok-language-select.d.ts +31 -0
- package/dist/components/ok-lightbox/ok-lightbox.d.ts +59 -0
- package/dist/components/ok-logo-cloud/ok-logo-cloud.d.ts +14 -0
- package/dist/components/ok-loyalty-card/ok-loyalty-card.d.ts +35 -0
- package/dist/components/ok-mail/ok-mail.d.ts +117 -0
- package/dist/components/ok-menu/ok-menu.d.ts +75 -0
- package/dist/components/ok-menubar/ok-menubar.d.ts +75 -0
- package/dist/components/ok-navbar/ok-navbar.d.ts +42 -0
- package/dist/components/ok-notification-center/ok-notification-center.d.ts +79 -0
- package/dist/components/ok-org-chart/ok-org-chart.d.ts +67 -0
- package/dist/components/ok-otp/ok-otp.d.ts +31 -0
- package/dist/components/ok-page-header/ok-page-header.d.ts +23 -0
- package/dist/components/ok-pagination/ok-pagination.d.ts +44 -0
- package/dist/components/ok-pdf/ok-pdf.d.ts +32 -0
- package/dist/components/ok-phone/ok-phone.d.ts +48 -0
- package/dist/components/ok-pinpad/ok-pinpad.d.ts +29 -0
- package/dist/components/ok-pricing-card/ok-pricing-card.d.ts +31 -0
- package/dist/components/ok-product-card/ok-product-card.d.ts +25 -0
- package/dist/components/ok-qr/ok-qr.d.ts +24 -0
- package/dist/components/ok-qty-stepper/ok-qty-stepper.d.ts +35 -0
- package/dist/components/ok-range-dual/ok-range-dual.d.ts +38 -0
- package/dist/components/ok-rating/ok-rating.d.ts +33 -0
- package/dist/components/ok-receipt/ok-receipt.d.ts +103 -0
- package/dist/components/ok-reveal/ok-reveal.d.ts +21 -0
- package/dist/components/ok-rich-text/ok-rich-text.d.ts +46 -0
- package/dist/components/ok-scheduler/ok-scheduler.d.ts +74 -0
- package/dist/components/ok-select-card/ok-select-card.d.ts +37 -0
- package/dist/components/ok-signature/ok-signature.d.ts +55 -0
- package/dist/components/ok-skeleton/ok-skeleton.d.ts +40 -0
- package/dist/components/ok-sparkline/ok-sparkline.d.ts +27 -0
- package/dist/components/ok-split-button/ok-split-button.d.ts +49 -0
- package/dist/components/ok-splitter/ok-splitter.d.ts +36 -0
- package/dist/components/ok-stat/ok-stat.d.ts +16 -0
- package/dist/components/ok-status-dot/ok-status-dot.d.ts +24 -0
- package/dist/components/ok-status-pill/ok-status-pill.d.ts +22 -0
- package/dist/components/ok-stepper/ok-stepper.d.ts +33 -0
- package/dist/components/ok-store/ok-store.d.ts +33 -0
- package/dist/components/ok-tag-input/ok-tag-input.d.ts +39 -0
- package/dist/components/ok-testimonial/ok-testimonial.d.ts +21 -0
- package/dist/components/ok-time-picker/ok-time-picker.d.ts +50 -0
- package/dist/components/ok-timeline/ok-timeline.d.ts +33 -0
- package/dist/components/ok-tree/ok-tree.d.ts +46 -0
- package/dist/components/ok-video/ok-video.d.ts +49 -0
- package/dist/components/ok-widget-board/ok-widget-board.d.ts +71 -0
- package/dist/components/ok-wizard/ok-wizard.d.ts +30 -0
- package/dist/define.js +8 -0
- package/dist/erplora.css +112 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +197 -0
- package/dist/layout.css +338 -0
- package/dist/ok-app-launcher.js +396 -0
- package/dist/ok-audio.js +308 -0
- package/dist/ok-avatar-group.js +158 -0
- package/dist/ok-avatar.js +179 -0
- package/dist/ok-bar-list.js +189 -0
- package/dist/ok-bento-item.js +168 -0
- package/dist/ok-bento.js +63 -0
- package/dist/ok-calculator.js +406 -0
- package/dist/ok-calendar.js +541 -0
- package/dist/ok-carousel.js +352 -0
- package/dist/ok-chart.js +325 -0
- package/dist/ok-chat.js +320 -0
- package/dist/ok-coachmark.js +500 -0
- package/dist/ok-code.js +190 -0
- package/dist/ok-color-picker.js +569 -0
- package/dist/ok-combo.js +294 -0
- package/dist/ok-command-palette.js +448 -0
- package/dist/ok-contact-form.js +288 -0
- package/dist/ok-cropper.js +404 -0
- package/dist/ok-cta-band.js +134 -0
- package/dist/ok-currency.js +172 -0
- package/dist/ok-data-table.js +1281 -0
- package/dist/ok-date-picker.js +736 -0
- package/dist/ok-detail-list.js +156 -0
- package/dist/ok-diff.js +200 -0
- package/dist/ok-donut.js +280 -0
- package/dist/ok-drawer.js +357 -0
- package/dist/ok-dropzone.js +376 -0
- package/dist/ok-empty-state.js +104 -0
- package/dist/ok-error-page.js +547 -0
- package/dist/ok-event-card.js +384 -0
- package/dist/ok-feature-card.js +152 -0
- package/dist/ok-file-item.js +259 -0
- package/dist/ok-file-manager.js +1116 -0
- package/dist/ok-footer.js +67 -0
- package/dist/ok-funnel.js +181 -0
- package/dist/ok-gallery.js +293 -0
- package/dist/ok-gauge.js +385 -0
- package/dist/ok-heatmap.js +268 -0
- package/dist/ok-hero.js +43 -0
- package/dist/ok-hover-card.js +480 -0
- package/dist/ok-icon-tile.js +123 -0
- package/dist/ok-image.js +471 -0
- package/dist/ok-inline-feedback.js +221 -0
- package/dist/ok-invoice.js +229 -0
- package/dist/ok-json-viewer.js +330 -0
- package/dist/ok-kanban.js +427 -0
- package/dist/ok-kbd.js +159 -0
- package/dist/ok-keyboard.js +402 -0
- package/dist/ok-kpi.js +147 -0
- package/dist/ok-language-select.js +188 -0
- package/dist/ok-lightbox.js +490 -0
- package/dist/ok-logo-cloud.js +92 -0
- package/dist/ok-loyalty-card.js +353 -0
- package/dist/ok-mail.js +562 -0
- package/dist/ok-menu.js +529 -0
- package/dist/ok-menubar.js +628 -0
- package/dist/ok-navbar.js +306 -0
- package/dist/ok-notification-center.js +545 -0
- package/dist/ok-org-chart.js +619 -0
- package/dist/ok-otp.js +199 -0
- package/dist/ok-page-header.js +202 -0
- package/dist/ok-pagination.js +366 -0
- package/dist/ok-pdf.js +160 -0
- package/dist/ok-phone.js +225 -0
- package/dist/ok-pinpad.js +171 -0
- package/dist/ok-pricing-card.js +184 -0
- package/dist/ok-product-card.js +178 -0
- package/dist/ok-qr.js +652 -0
- package/dist/ok-qty-stepper.js +212 -0
- package/dist/ok-range-dual.js +280 -0
- package/dist/ok-rating.js +199 -0
- package/dist/ok-receipt.js +183 -0
- package/dist/ok-reveal.js +94 -0
- package/dist/ok-rich-text.js +538 -0
- package/dist/ok-scheduler.js +518 -0
- package/dist/ok-select-card.js +231 -0
- package/dist/ok-signature.js +267 -0
- package/dist/ok-skeleton.js +345 -0
- package/dist/ok-sparkline.js +150 -0
- package/dist/ok-split-button.js +251 -0
- package/dist/ok-splitter.js +289 -0
- package/dist/ok-stat.js +77 -0
- package/dist/ok-status-dot.js +163 -0
- package/dist/ok-status-pill.js +123 -0
- package/dist/ok-stepper.js +299 -0
- package/dist/ok-store.js +83 -0
- package/dist/ok-tag-input.js +358 -0
- package/dist/ok-testimonial.js +136 -0
- package/dist/ok-time-picker.js +472 -0
- package/dist/ok-timeline.js +251 -0
- package/dist/ok-tree.js +266 -0
- package/dist/ok-video.js +362 -0
- package/dist/ok-widget-board.js +265 -0
- package/dist/ok-wizard.js +153 -0
- package/dist/outfitkit.js +96 -0
- package/dist/shared/anchor.js +14 -0
- package/dist/store/controller.d.ts +17 -0
- package/dist/store/idb.d.ts +16 -0
- package/dist/store/store.d.ts +39 -0
- package/dist/store-controller.js +31 -0
- package/dist/store.js +182 -0
- package/dist/theme.example.css +70 -0
- package/package.json +147 -0
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
import { LitElement, css, nothing, html } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
4
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
5
|
+
import { define } from "./define.js";
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
var result = void 0;
|
|
9
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
10
|
+
if (decorator = decorators[i])
|
|
11
|
+
result = decorator(target, key, result) || result;
|
|
12
|
+
if (result) __defProp(target, key, result);
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_LABELS = {
|
|
16
|
+
search: "Search…",
|
|
17
|
+
empty: "No results",
|
|
18
|
+
filters: "Filters",
|
|
19
|
+
clear: "Clear",
|
|
20
|
+
apply: "Apply",
|
|
21
|
+
selected: "{n} selected",
|
|
22
|
+
importCsv: "Import CSV",
|
|
23
|
+
exportCsv: "Export CSV",
|
|
24
|
+
add: "Add",
|
|
25
|
+
moreActions: "More actions",
|
|
26
|
+
rowsPerPage: "Rows per page",
|
|
27
|
+
perPageShort: "{n} / page",
|
|
28
|
+
viewList: "View as list",
|
|
29
|
+
viewCards: "View as cards",
|
|
30
|
+
columnsVisible: "Visible columns",
|
|
31
|
+
columns: "Columns",
|
|
32
|
+
actions: "Actions",
|
|
33
|
+
close: "Close",
|
|
34
|
+
newRecord: "New",
|
|
35
|
+
form: "Form",
|
|
36
|
+
filterPlaceholder: "Filter…",
|
|
37
|
+
from: "From",
|
|
38
|
+
to: "To",
|
|
39
|
+
fromOf: "{label} from",
|
|
40
|
+
toOf: "{label} to",
|
|
41
|
+
gte: "≥",
|
|
42
|
+
lte: "≤",
|
|
43
|
+
noValues: "No values",
|
|
44
|
+
selectAll: "Select all",
|
|
45
|
+
selectRow: "Select row",
|
|
46
|
+
select: "Select",
|
|
47
|
+
showing: "Showing {from}–{to} of",
|
|
48
|
+
recordSingular: "record",
|
|
49
|
+
recordPlural: "records"
|
|
50
|
+
};
|
|
51
|
+
class OkDataTable extends LitElement {
|
|
52
|
+
constructor() {
|
|
53
|
+
super(...arguments);
|
|
54
|
+
this.columns = [];
|
|
55
|
+
this.rows = [];
|
|
56
|
+
this.searchKeys = [];
|
|
57
|
+
this.rowKeyField = "id";
|
|
58
|
+
this.pageSize = 10;
|
|
59
|
+
this.labels = {};
|
|
60
|
+
this.actions = [];
|
|
61
|
+
this.addable = false;
|
|
62
|
+
this.pageSizeOptions = [10, 25, 50, 100];
|
|
63
|
+
this.fill = false;
|
|
64
|
+
this.columnPicker = true;
|
|
65
|
+
this.csv = false;
|
|
66
|
+
this.csvName = "export.csv";
|
|
67
|
+
this.serverSide = false;
|
|
68
|
+
this.total = 0;
|
|
69
|
+
this.page = 0;
|
|
70
|
+
this.searchable = false;
|
|
71
|
+
this.sortDir = "asc";
|
|
72
|
+
this.title = "";
|
|
73
|
+
this.views = false;
|
|
74
|
+
this.exportable = false;
|
|
75
|
+
this.importable = false;
|
|
76
|
+
this.columnSelector = false;
|
|
77
|
+
this.selectable = false;
|
|
78
|
+
this.inlineFilters = false;
|
|
79
|
+
this.menuActions = [];
|
|
80
|
+
this.q = "";
|
|
81
|
+
this.clientPage = 0;
|
|
82
|
+
this.clientPageSize = 0;
|
|
83
|
+
this.clientSort = "";
|
|
84
|
+
this.clientSortDir = "asc";
|
|
85
|
+
this.clientFilters = {};
|
|
86
|
+
this.filterDraft = {};
|
|
87
|
+
this.panel = "none";
|
|
88
|
+
this.viewMode = "table";
|
|
89
|
+
this.hiddenKeys = /* @__PURE__ */ new Set();
|
|
90
|
+
this.internalSelection = /* @__PURE__ */ new Set();
|
|
91
|
+
this.menuOpen = false;
|
|
92
|
+
this.onSearch = (ev) => {
|
|
93
|
+
const value = ev.target.value ?? "";
|
|
94
|
+
if (this.serverSide) {
|
|
95
|
+
this.emit("searchChange", value);
|
|
96
|
+
} else {
|
|
97
|
+
this.q = value;
|
|
98
|
+
this.clientPage = 0;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
static {
|
|
103
|
+
this.styles = css`
|
|
104
|
+
:host {
|
|
105
|
+
/* Vars overridable (estilo Ionic), default = cadena --ok-* → --ion-* → hex */
|
|
106
|
+
--background: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #ffffff)));
|
|
107
|
+
--color: var(--ok-text, var(--ion-text-color, #1c1b17));
|
|
108
|
+
--color-muted: var(--ok-muted, var(--ion-color-medium, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.55)));
|
|
109
|
+
--border-color: var(--ok-border, var(--ion-color-step-150, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.12)));
|
|
110
|
+
--border-color-soft: var(--ok-border-soft, var(--ion-color-step-100, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.07)));
|
|
111
|
+
/* Borde más marcado para los controles de la toolbar (selects/pastilla de fechas), para que se
|
|
112
|
+
* distingan como controles en claro y oscuro aunque el lienzo y la superficie casi no contrasten. */
|
|
113
|
+
--control-border: color-mix(in srgb, var(--color) 22%, transparent);
|
|
114
|
+
/* Relieve de cabecera/pie: step-100 (definido en claro y oscuro) → contraste con el lienzo. */
|
|
115
|
+
--header-background: var(--ok-surface-2, var(--ion-color-step-100, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.04)));
|
|
116
|
+
--row-hover: var(--ok-row-hover, var(--ion-color-step-50, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.03)));
|
|
117
|
+
--primary: var(--ok-primary, var(--ion-color-primary, #3880ff));
|
|
118
|
+
--primary-contrast: var(--ok-primary-contrast, var(--ion-color-primary-contrast, #ffffff));
|
|
119
|
+
--border-radius: var(--ok-radius, 16px);
|
|
120
|
+
--font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
|
|
121
|
+
|
|
122
|
+
display: block;
|
|
123
|
+
color: var(--color);
|
|
124
|
+
font-family: var(--font);
|
|
125
|
+
}
|
|
126
|
+
* { box-sizing: border-box; }
|
|
127
|
+
.card {
|
|
128
|
+
position: relative;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
/* Flat: sin borde ni elevación (directiva 2026-06-09). */
|
|
132
|
+
border: 0;
|
|
133
|
+
border-radius: var(--border-radius);
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
background: var(--background);
|
|
136
|
+
box-shadow: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Panel lateral derecho (drawer) DENTRO de la tabla: filtros / alta-edición. No empuja contenido. */
|
|
140
|
+
.tk-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.18); z-index: 19; }
|
|
141
|
+
.drawer { position: absolute; top: 0; right: 0; height: 100%; width: 340px; max-width: 88%;
|
|
142
|
+
background: var(--background); border-left: 1px solid var(--border-color);
|
|
143
|
+
box-shadow: -10px 0 28px rgba(0, 0, 0, 0.10); display: flex; flex-direction: column; z-index: 20;
|
|
144
|
+
animation: tk-slide-in 0.18s ease; }
|
|
145
|
+
@keyframes tk-slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
|
146
|
+
.drawer .dh { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
|
|
147
|
+
padding: 0.6rem 0.5rem 0.6rem 1rem; border-bottom: 1px solid var(--border-color); font-size: 1rem; }
|
|
148
|
+
.drawer .db { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.85rem; }
|
|
149
|
+
.fblock { display: flex; flex-direction: column; gap: 0.45rem; }
|
|
150
|
+
.flabel { font-size: 13px; font-weight: 500; color: var(--color); }
|
|
151
|
+
.frange { display: flex; gap: 0.5rem; }
|
|
152
|
+
/* Filtros cliente: chips multi-select (estilo Hub) + rango de fechas. */
|
|
153
|
+
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
154
|
+
.chip { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.6rem; border: 1px solid var(--border-color); border-radius: 999px; background: var(--background); color: var(--color-muted); font-size: 12px; cursor: pointer; transition: color 0.12s, background 0.12s, border-color 0.12s; }
|
|
155
|
+
.chip:hover { color: var(--color); }
|
|
156
|
+
.chip.on { border-color: var(--primary); color: var(--primary); background: color-mix(in srgb, var(--primary) 15%, transparent); }
|
|
157
|
+
.chip ion-icon { font-size: 12px; }
|
|
158
|
+
.chip-empty { font-size: 12px; color: var(--color-muted); }
|
|
159
|
+
.daterange { display: flex; gap: 0.6rem; }
|
|
160
|
+
.daterange ion-input { flex: 1; }
|
|
161
|
+
/* Pie del drawer de filtros: Limpiar / Aplicar. */
|
|
162
|
+
.df { flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; padding: 0.6rem 0.85rem; border-top: 1px solid var(--border-color); }
|
|
163
|
+
.df .df-clear { margin-right: auto; }
|
|
164
|
+
|
|
165
|
+
/* Modo fill: la tabla ocupa el alto del contenedor; filas con scroll interno; pager fijo. */
|
|
166
|
+
:host([fill]) { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
167
|
+
:host([fill]) .card { flex: 1 1 auto; min-height: 0; }
|
|
168
|
+
:host([fill]) .bar, :host([fill]) .panel, :host([fill]) .pager { flex: 0 0 auto; }
|
|
169
|
+
:host([fill]) .scroll, :host([fill]) .cards-grid { flex: 1 1 auto; min-height: 0; overflow: auto; }
|
|
170
|
+
|
|
171
|
+
/* ── Topbar / cabecera (relieve) ─────────────────────────────────────────────────────── */
|
|
172
|
+
.bar { display: flex; flex-direction: column; gap: 0.6rem; padding: 0.65rem 1rem; border-bottom: 1px solid var(--border-color); background: var(--header-background); }
|
|
173
|
+
/* Toolbar CONSOLIDADA: TODOS los controles (buscador, filtros, page-size, vistas, columnas,
|
|
174
|
+
* CSV, ⋮, alta) son hijos directos de UNA sola fila flex que envuelve ELEMENTO A ELEMENTO
|
|
175
|
+
* (no por bloques): caben en una línea → una línea; los que no caben bajan a la(s) línea(s)
|
|
176
|
+
* que hagan falta. El cluster derecho se empuja al borde con .tk-spacer (hueco flexible)
|
|
177
|
+
* solo cuando todo cabe en una línea; al envolver, el spacer se oculta y todo se apila a la
|
|
178
|
+
* izquierda. */
|
|
179
|
+
.bar-main { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
|
|
180
|
+
.bar-main > ion-button { --padding-start: 0.5rem; --padding-end: 0.5rem; margin: 0; }
|
|
181
|
+
/* Spacer que absorbe el hueco libre en pantallas anchas (empuja el cluster derecho al borde).
|
|
182
|
+
* Se oculta por debajo de 1024px para que, al envolver, los controles se apilen a la izquierda. */
|
|
183
|
+
.tk-spacer { flex: 1 1 0; min-width: 0; align-self: stretch; }
|
|
184
|
+
@media (max-width: 1024px) { .tk-spacer { display: none; } }
|
|
185
|
+
/* Buscador a ancho completo (línea propia) en móvil; el resto envuelve debajo. */
|
|
186
|
+
@media (max-width: 640px) { .search { flex-basis: 100%; max-width: none; } }
|
|
187
|
+
.title-wrap { display: flex; align-items: baseline; gap: 0.5rem; }
|
|
188
|
+
.title { font-size: 15px; font-weight: 600; line-height: 1; margin: 0; }
|
|
189
|
+
.title-count { font-size: 12px; font-weight: 500; color: var(--color-muted); }
|
|
190
|
+
|
|
191
|
+
/* Botón de herramienta cuadrado (filtros/import/export), look del Hub: 36×36, badge contador. */
|
|
192
|
+
.toolbtn { position: relative; --padding-start: 0; --padding-end: 0; --border-radius: 10px; width: 36px; height: 36px; margin: 0; }
|
|
193
|
+
.toolbtn .badge { position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 3px; border-radius: 999px; background: var(--primary); color: var(--primary-contrast); font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; pointer-events: none; }
|
|
194
|
+
|
|
195
|
+
/* Buscador (caja con icono + limpiar), look del Hub. No crece (el spacer se queda el hueco);
|
|
196
|
+
* puede encoger hasta min-width y, por debajo, envuelve. */
|
|
197
|
+
.search { flex: 0 1 22rem; min-width: 12rem; max-width: 24rem; }
|
|
198
|
+
ion-searchbar { --background: var(--background); --border-radius: 10px; padding: 0; min-height: 36px; }
|
|
199
|
+
/* Flat: el buscador quita borde y elevación vía la clase específica de Ionic 'ion-no-border'.
|
|
200
|
+
* (La regla global de Ionic para .ion-no-border no cruza el Shadow DOM, así que la
|
|
201
|
+
* reimplementamos aquí dentro: --box-shadow controla la elevación; ::part(native) el borde.) */
|
|
202
|
+
ion-searchbar.ion-no-border { --box-shadow: none; }
|
|
203
|
+
ion-searchbar.ion-no-border::part(native) { border: none; box-shadow: none; }
|
|
204
|
+
|
|
205
|
+
/* Toggle de vista lista/tarjetas (segmento) */
|
|
206
|
+
.viewseg { display: inline-flex; align-items: center; gap: 2px; padding: 2px; border: 1px solid var(--border-color); border-radius: 10px; background: var(--background); }
|
|
207
|
+
.viewseg ion-button { --border-radius: 7px; }
|
|
208
|
+
|
|
209
|
+
/* Botón primario (primaryAction) */
|
|
210
|
+
.primary-btn { --background: var(--primary); --color: var(--primary-contrast); }
|
|
211
|
+
|
|
212
|
+
/* Selects de la toolbar: fondo + borde visibles (como el buscador y la pastilla de fechas) para
|
|
213
|
+
* que se distingan como controles en claro y oscuro (sin fondo eran invisibles en dark). */
|
|
214
|
+
.tk-cols { min-width: 6.5rem; max-width: 9rem; min-height: 38px; font-size: 13px; background: var(--background); color: var(--color); border: 1px solid var(--control-border); border-radius: 10px; --padding-start: 0.6rem; --padding-end: 0.4rem; --padding-top: 0.3rem; --padding-bottom: 0.3rem; }
|
|
215
|
+
.vsep { width: 1px; align-self: stretch; background: var(--border-color); margin: 0.3rem 0.25rem; }
|
|
216
|
+
|
|
217
|
+
/* Selector de filas/página en la toolbar (consolidado) */
|
|
218
|
+
/* max-width: ion-select es display:block (sin core.css el host estira a la
|
|
219
|
+
* línea entera cuando .bar-end hace wrap) — se capa como .tk-cols. */
|
|
220
|
+
.tk-psize { min-width: 4.25rem; max-width: 5.5rem; min-height: 38px; font-size: 13px; background: var(--background); color: var(--color); border: 1px solid var(--control-border); border-radius: 10px; --padding-start: 0.6rem; --padding-end: 0.4rem; --padding-top: 0.35rem; --padding-bottom: 0.35rem; }
|
|
221
|
+
|
|
222
|
+
/* Filtros EN LÍNEA en la toolbar (select / rango de fechas) */
|
|
223
|
+
.tk-filter { min-width: 8.5rem; max-width: 13rem; min-height: 38px; font-size: 13px; background: var(--background); color: var(--color); border: 1px solid var(--control-border); border-radius: 10px; --padding-start: 0.7rem; --padding-end: 0.5rem; --padding-top: 0.35rem; --padding-bottom: 0.35rem; }
|
|
224
|
+
.tk-daterange { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0.6rem; min-height: 38px; border: 1px solid var(--control-border); border-radius: 10px; background: var(--background); color: var(--color-muted); font-size: 13px; }
|
|
225
|
+
.tk-daterange ion-icon { font-size: 15px; flex: 0 0 auto; }
|
|
226
|
+
.tk-daterange ion-input { --background: transparent; --padding-start: 0; --padding-end: 0; --padding-top: 2px; --padding-bottom: 2px; --color: var(--color); min-height: 26px; width: 6.8rem; font-size: 13px; }
|
|
227
|
+
.tk-daterange .arr { color: var(--color-muted); }
|
|
228
|
+
|
|
229
|
+
/* Barra contextual de selección */
|
|
230
|
+
.selbar { display: flex; align-items: center; gap: 0.6rem; padding: 0.4rem 0.7rem; border-radius: 10px;
|
|
231
|
+
font-size: 13px; color: var(--primary);
|
|
232
|
+
background: color-mix(in srgb, var(--primary) 12%, transparent); }
|
|
233
|
+
.selbar .sel-clear { margin-left: auto; display: inline-flex; align-items: center; gap: 0.25rem; cursor: pointer; font-weight: 500; color: inherit; background: none; border: 0; font: inherit; }
|
|
234
|
+
.selbar .sel-clear:hover { text-decoration: underline; }
|
|
235
|
+
|
|
236
|
+
/* Acordeones (alta / filtros en modo tarjetas) */
|
|
237
|
+
.panel { padding: 0.85rem 1rem; border-bottom: 1px solid var(--border-color); background: var(--header-background); }
|
|
238
|
+
.filters-panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0.6rem; }
|
|
239
|
+
|
|
240
|
+
/* ── Vista lista en CSS GRID (no <table>): permite ancho por columna ──────────────────── */
|
|
241
|
+
.scroll { overflow-x: auto; }
|
|
242
|
+
.grid { min-width: max-content; font-size: 14px; }
|
|
243
|
+
.grow { display: grid; align-items: center; gap: 0.5rem; padding: 0 1rem; }
|
|
244
|
+
.ghead { position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--border-color);
|
|
245
|
+
background: var(--header-background); padding-top: 0.55rem; padding-bottom: 0.55rem; }
|
|
246
|
+
.gcell { display: flex; align-items: center; min-width: 0; }
|
|
247
|
+
.gcell > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
248
|
+
.gcell.right { justify-content: flex-end; text-align: right; }
|
|
249
|
+
.gcell.center { justify-content: center; text-align: center; }
|
|
250
|
+
.gh { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-muted); }
|
|
251
|
+
.gh.sortable { cursor: pointer; user-select: none; white-space: nowrap; transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease), transform 120ms ease; }
|
|
252
|
+
@media (hover: hover) {
|
|
253
|
+
.gh.sortable:hover { color: var(--color); }
|
|
254
|
+
}
|
|
255
|
+
/* Caret de orden (3 estados, icono Ionic): neutral atenuado / activo en color primario. */
|
|
256
|
+
.caret { display: inline-flex; align-items: center; margin-left: 0.25rem; flex: 0 0 auto; font-size: 13px; opacity: 0.3; }
|
|
257
|
+
.caret.on { opacity: 1; color: var(--primary); }
|
|
258
|
+
.grow-data { border-bottom: 1px solid var(--border-color-soft); padding-top: 0.6rem; padding-bottom: 0.6rem; transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease), transform 120ms ease; }
|
|
259
|
+
.grow-data:last-child { border-bottom: 0; }
|
|
260
|
+
@media (hover: hover) {
|
|
261
|
+
.grow-data:hover { background: var(--row-hover); }
|
|
262
|
+
}
|
|
263
|
+
.grow-data:active { transform: scale(0.995); }
|
|
264
|
+
.grow-data.selected { background: color-mix(in srgb, var(--primary) 10%, transparent); }
|
|
265
|
+
.selcb { display: flex; align-items: center; justify-content: center; }
|
|
266
|
+
.filters-grow { padding-top: 0.4rem; padding-bottom: 0.6rem; }
|
|
267
|
+
.filters-grow input, .filters-grow select { width: 100%; box-sizing: border-box; font: inherit; font-size: 13px; padding: 0.3rem 0.4rem; border: 1px solid var(--border-color); border-radius: 6px; background: var(--background); color: var(--color); }
|
|
268
|
+
.range { display: flex; gap: 0.25rem; }
|
|
269
|
+
|
|
270
|
+
/* ── Vista tarjetas ──────────────────────────────────────────────────────────────────── */
|
|
271
|
+
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 0.75rem; padding: 1rem; }
|
|
272
|
+
/* Flat: sin borde ni elevación — las tarjetas se delimitan por la superficie (no por sombra). */
|
|
273
|
+
.rcard { display: flex; flex-direction: column; border: 0; border-radius: 12px; overflow: hidden; background: var(--header-background); box-shadow: none; transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease), transform 120ms ease; }
|
|
274
|
+
@media (hover: hover) {
|
|
275
|
+
.rcard:hover { background: var(--row-hover); }
|
|
276
|
+
}
|
|
277
|
+
.rcard:active { transform: scale(0.995); }
|
|
278
|
+
@media (prefers-reduced-motion: reduce) {
|
|
279
|
+
.gh.sortable:hover, .gh.sortable:active,
|
|
280
|
+
.grow-data:hover, .grow-data:active,
|
|
281
|
+
.rcard:hover, .rcard:active { transform: none; }
|
|
282
|
+
}
|
|
283
|
+
.rcard.selected { background: color-mix(in srgb, var(--primary) 12%, var(--header-background)); }
|
|
284
|
+
.rcard-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--border-color); background: var(--header-background); }
|
|
285
|
+
.rcard-head .rc-icon { display: inline-flex; color: var(--primary); }
|
|
286
|
+
.rcard-head .rc-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
|
|
287
|
+
.rcard-body { flex: 1; padding: 0.6rem 0.85rem; display: flex; flex-direction: column; gap: 0.4rem; }
|
|
288
|
+
.rrow { display: flex; justify-content: space-between; gap: 0.5rem; font-size: 13px; }
|
|
289
|
+
.rrow .rk { color: var(--color-muted); }
|
|
290
|
+
.rrow .rv { font-weight: 500; text-align: right; }
|
|
291
|
+
.ractions { display: flex; justify-content: flex-end; gap: 0.25rem; padding: 0.25rem 0.5rem; border-top: 1px solid var(--border-color-soft); background: var(--header-background); }
|
|
292
|
+
|
|
293
|
+
/* ── Estado vacío ────────────────────────────────────────────────────────────────────── */
|
|
294
|
+
.empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; padding: 3.5rem 1rem; text-align: center; color: var(--color-muted); }
|
|
295
|
+
.empty .empty-ic { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; border-radius: 999px; background: var(--header-background); font-size: 26px; }
|
|
296
|
+
|
|
297
|
+
.actions { display: flex; gap: 0.25rem; justify-content: flex-end; }
|
|
298
|
+
|
|
299
|
+
/* ── Pie: contador + paginación ──────────────────────────────────────────────────────── */
|
|
300
|
+
.pager { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.55rem 1rem; border-top: 1px solid var(--border-color); background: var(--header-background); font-size: 12.5px; color: var(--color-muted); }
|
|
301
|
+
.pager .left { display: flex; align-items: center; gap: 0.6rem; }
|
|
302
|
+
.pager .strong { font-weight: 600; color: var(--color); }
|
|
303
|
+
.psize { font: inherit; font-size: 12.5px; padding: 0.2rem 0.35rem; border: 1px solid var(--border-color); border-radius: 6px; background: var(--background); color: var(--color); }
|
|
304
|
+
.pager .nav { display: flex; align-items: center; gap: 0.2rem; }
|
|
305
|
+
.pager .nav .pp { font-weight: 600; color: var(--color); padding: 0 0.25rem; }
|
|
306
|
+
/* Pager numerado: botón por página + «…» en los saltos (look del Hub). */
|
|
307
|
+
.pnum { min-width: 1.75rem; height: 1.75rem; padding: 0 0.4rem; border: 1px solid transparent; border-radius: 8px; background: none; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--color); cursor: pointer; transition: background 0.12s, border-color 0.12s; }
|
|
308
|
+
.pnum:hover { background: var(--row-hover); }
|
|
309
|
+
.pnum.on { background: color-mix(in srgb, var(--primary) 14%, transparent); color: var(--primary); border-color: color-mix(in srgb, var(--primary) 40%, transparent); }
|
|
310
|
+
.pgap { padding: 0 0.15rem; color: var(--color-muted); }
|
|
311
|
+
ion-button { --box-shadow: none; }
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
314
|
+
// ── i18n: textos efectivos (default inglés ← overrides de `.labels`) ──────────────────────
|
|
315
|
+
get t() {
|
|
316
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
317
|
+
}
|
|
318
|
+
/** Placeholder efectivo del buscador (prop explícita → label i18n → default inglés). */
|
|
319
|
+
get effSearchPlaceholder() {
|
|
320
|
+
return this.searchPlaceholder ?? this.t.search;
|
|
321
|
+
}
|
|
322
|
+
/** Mensaje efectivo de estado vacío (prop explícita → label i18n → default inglés). */
|
|
323
|
+
get effEmptyMessage() {
|
|
324
|
+
return this.emptyMessage ?? this.t.empty;
|
|
325
|
+
}
|
|
326
|
+
// ── Resolución de alias (compat + documentados) ──────────────────────────────────────────
|
|
327
|
+
get effPageSizes() {
|
|
328
|
+
return this.pageSizes ?? this.pageSizeOptions;
|
|
329
|
+
}
|
|
330
|
+
get effColumnPicker() {
|
|
331
|
+
return this.columnPicker || this.columnSelector;
|
|
332
|
+
}
|
|
333
|
+
get effExport() {
|
|
334
|
+
return this.csv || this.exportable;
|
|
335
|
+
}
|
|
336
|
+
get effImport() {
|
|
337
|
+
return this.csv || this.importable;
|
|
338
|
+
}
|
|
339
|
+
/** ¿Está habilitado el conmutador de vista lista/tarjetas? */
|
|
340
|
+
get viewToggle() {
|
|
341
|
+
if (Array.isArray(this.views)) return this.views.length > 1;
|
|
342
|
+
return this.views === true;
|
|
343
|
+
}
|
|
344
|
+
/** ¿Está disponible la vista tarjetas? (presente en `views` o `views === true`). */
|
|
345
|
+
get cardViewEnabled() {
|
|
346
|
+
if (Array.isArray(this.views)) return this.views.some((v) => v === "cards" || v === "card");
|
|
347
|
+
return this.views === true;
|
|
348
|
+
}
|
|
349
|
+
/** Columnas actualmente visibles (respeta el column chooser). */
|
|
350
|
+
get visibleColumns() {
|
|
351
|
+
return this.hiddenKeys.size ? this.columns.filter((c) => !this.hiddenKeys.has(c.key)) : this.columns;
|
|
352
|
+
}
|
|
353
|
+
setVisibleColumns(keys) {
|
|
354
|
+
const visible = new Set(keys);
|
|
355
|
+
this.hiddenKeys = new Set(this.columns.map((c) => c.key).filter((k) => !visible.has(k)));
|
|
356
|
+
this.emit("columnsChange", { visible: keys });
|
|
357
|
+
}
|
|
358
|
+
// ── Selección ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
+
keyOf(row) {
|
|
360
|
+
if (typeof this.rowKey === "function") return String(this.rowKey(row) ?? "");
|
|
361
|
+
if (typeof this.rowKey === "string") return String(row[this.rowKey] ?? "");
|
|
362
|
+
return String(row[this.rowKeyField] ?? "");
|
|
363
|
+
}
|
|
364
|
+
get selection() {
|
|
365
|
+
return this.selectedKeys ?? this.internalSelection;
|
|
366
|
+
}
|
|
367
|
+
setSelection(next) {
|
|
368
|
+
if (!this.selectedKeys) this.internalSelection = next;
|
|
369
|
+
this.emit("selectionChange", { keys: [...next] });
|
|
370
|
+
this.requestUpdate();
|
|
371
|
+
}
|
|
372
|
+
toggleRow(key) {
|
|
373
|
+
const next = new Set(this.selection);
|
|
374
|
+
if (next.has(key)) next.delete(key);
|
|
375
|
+
else next.add(key);
|
|
376
|
+
this.setSelection(next);
|
|
377
|
+
}
|
|
378
|
+
toggleAll(visible) {
|
|
379
|
+
const keys = visible.map((r) => this.keyOf(r));
|
|
380
|
+
const allOn = keys.length > 0 && keys.every((k) => this.selection.has(k));
|
|
381
|
+
const next = new Set(this.selection);
|
|
382
|
+
if (allOn) keys.forEach((k) => next.delete(k));
|
|
383
|
+
else keys.forEach((k) => next.add(k));
|
|
384
|
+
this.setSelection(next);
|
|
385
|
+
}
|
|
386
|
+
// ── CSV ─────────────────────────────────────────────────────────────────────────────────────
|
|
387
|
+
csvEscape(v) {
|
|
388
|
+
const s = v === null || v === void 0 ? "" : String(v);
|
|
389
|
+
return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
390
|
+
}
|
|
391
|
+
/** Exporta las filas a CSV (cabeceras = column.key). Si no hay filas, exporta solo la estructura. */
|
|
392
|
+
exportCsv() {
|
|
393
|
+
const cols = this.columns;
|
|
394
|
+
const head = cols.map((c) => this.csvEscape(c.key)).join(",");
|
|
395
|
+
const lines = this.rows.map((r) => cols.map((c) => this.csvEscape(r[c.key])).join(","));
|
|
396
|
+
const csv = [head, ...lines].join("\r\n");
|
|
397
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
398
|
+
const url = URL.createObjectURL(blob);
|
|
399
|
+
const a = document.createElement("a");
|
|
400
|
+
a.href = url;
|
|
401
|
+
a.download = this.csvName;
|
|
402
|
+
a.click();
|
|
403
|
+
URL.revokeObjectURL(url);
|
|
404
|
+
this.emit("csvExport", { rows: this.rows.length });
|
|
405
|
+
this.emit("export", { rows: this.rows.length });
|
|
406
|
+
}
|
|
407
|
+
parseCsv(text) {
|
|
408
|
+
const out = [];
|
|
409
|
+
let row = [];
|
|
410
|
+
let field = "";
|
|
411
|
+
let q = false;
|
|
412
|
+
for (let i = 0; i < text.length; i++) {
|
|
413
|
+
const c = text[i];
|
|
414
|
+
if (q) {
|
|
415
|
+
if (c === '"') {
|
|
416
|
+
if (text[i + 1] === '"') {
|
|
417
|
+
field += '"';
|
|
418
|
+
i++;
|
|
419
|
+
} else q = false;
|
|
420
|
+
} else field += c;
|
|
421
|
+
} else if (c === '"') q = true;
|
|
422
|
+
else if (c === ",") {
|
|
423
|
+
row.push(field);
|
|
424
|
+
field = "";
|
|
425
|
+
} else if (c === "\n" || c === "\r") {
|
|
426
|
+
if (c === "\r" && text[i + 1] === "\n") i++;
|
|
427
|
+
row.push(field);
|
|
428
|
+
field = "";
|
|
429
|
+
if (row.length > 1 || row[0] !== "") out.push(row);
|
|
430
|
+
row = [];
|
|
431
|
+
} else field += c;
|
|
432
|
+
}
|
|
433
|
+
if (field !== "" || row.length) {
|
|
434
|
+
row.push(field);
|
|
435
|
+
out.push(row);
|
|
436
|
+
}
|
|
437
|
+
const headers = out.shift() ?? [];
|
|
438
|
+
const rows = out.map((r) => Object.fromEntries(headers.map((h, i) => [h, r[i] ?? ""])));
|
|
439
|
+
return { headers, rows };
|
|
440
|
+
}
|
|
441
|
+
async onImportFile(ev) {
|
|
442
|
+
const input = ev.target;
|
|
443
|
+
const file = input.files?.[0];
|
|
444
|
+
if (!file) return;
|
|
445
|
+
const text = await file.text();
|
|
446
|
+
const { headers, rows } = this.parseCsv(text);
|
|
447
|
+
this.emit("csvImport", { headers, rows });
|
|
448
|
+
this.emit("import", { headers, rows });
|
|
449
|
+
input.value = "";
|
|
450
|
+
}
|
|
451
|
+
toggle(p) {
|
|
452
|
+
if (p === "filters" && this.panel !== "filters") {
|
|
453
|
+
this.filterDraft = this.cloneFilters(this.clientFilters);
|
|
454
|
+
}
|
|
455
|
+
this.panel = this.panel === p ? "none" : p;
|
|
456
|
+
}
|
|
457
|
+
// ── Filtros en memoria (modo cliente): borrador → aplicar. ───────────────────────────────────
|
|
458
|
+
cloneFilters(src) {
|
|
459
|
+
const out = {};
|
|
460
|
+
for (const [k, f] of Object.entries(src)) {
|
|
461
|
+
out[k] = { values: f.values ? new Set(f.values) : void 0, from: f.from, to: f.to };
|
|
462
|
+
}
|
|
463
|
+
return out;
|
|
464
|
+
}
|
|
465
|
+
toggleFilterValue(key, value) {
|
|
466
|
+
const next = this.cloneFilters(this.filterDraft);
|
|
467
|
+
const values = new Set(next[key]?.values ?? []);
|
|
468
|
+
if (values.has(value)) values.delete(value);
|
|
469
|
+
else values.add(value);
|
|
470
|
+
next[key] = { ...next[key], values };
|
|
471
|
+
this.filterDraft = next;
|
|
472
|
+
}
|
|
473
|
+
setFilterRange(key, edge, value) {
|
|
474
|
+
const next = this.cloneFilters(this.filterDraft);
|
|
475
|
+
next[key] = { ...next[key], [edge]: value };
|
|
476
|
+
this.filterDraft = next;
|
|
477
|
+
}
|
|
478
|
+
applyFilters() {
|
|
479
|
+
const clean = {};
|
|
480
|
+
for (const [k, f] of Object.entries(this.filterDraft)) {
|
|
481
|
+
if (f.values && f.values.size > 0 || f.from || f.to) clean[k] = f;
|
|
482
|
+
}
|
|
483
|
+
this.clientFilters = clean;
|
|
484
|
+
this.clientPage = 0;
|
|
485
|
+
this.panel = "none";
|
|
486
|
+
this.emit("filterChange", { filters: this.serializeFilters(clean) });
|
|
487
|
+
}
|
|
488
|
+
clearFilters() {
|
|
489
|
+
this.filterDraft = {};
|
|
490
|
+
}
|
|
491
|
+
serializeFilters(src) {
|
|
492
|
+
const out = {};
|
|
493
|
+
for (const [k, f] of Object.entries(src)) {
|
|
494
|
+
if (f.values && f.values.size > 0) out[k] = [...f.values];
|
|
495
|
+
else if (f.from || f.to) out[k] = { from: f.from ?? "", to: f.to ?? "" };
|
|
496
|
+
}
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
/** Abre el panel lateral (API pública para el módulo, p.ej. "editar" abre el form pre-rellenado). */
|
|
500
|
+
open(panel = "create") {
|
|
501
|
+
this.panel = panel;
|
|
502
|
+
}
|
|
503
|
+
/** Cierra el panel lateral. */
|
|
504
|
+
close() {
|
|
505
|
+
this.panel = "none";
|
|
506
|
+
}
|
|
507
|
+
emit(type, detail) {
|
|
508
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
509
|
+
}
|
|
510
|
+
get hasSearch() {
|
|
511
|
+
return this.searchable || this.searchKeys.length > 0;
|
|
512
|
+
}
|
|
513
|
+
/** Columnas filtrables (con control en el panel de filtros). En cliente y en servidor. */
|
|
514
|
+
get filterColumns() {
|
|
515
|
+
return this.columns.filter((c) => c.filterable);
|
|
516
|
+
}
|
|
517
|
+
/** ¿Hay que mostrar el botón de Filtros? (cualquier columna filtrable). */
|
|
518
|
+
get hasFilterRow() {
|
|
519
|
+
return this.filterColumns.length > 0;
|
|
520
|
+
}
|
|
521
|
+
/** Nº de filtros activos (modo cliente) → badge del botón Filtros. */
|
|
522
|
+
get activeFilterCount() {
|
|
523
|
+
return Object.values(this.clientFilters).filter(
|
|
524
|
+
(f) => f.values && f.values.size > 0 || f.from || f.to
|
|
525
|
+
).length;
|
|
526
|
+
}
|
|
527
|
+
/** Valor crudo de una columna para ordenar/filtrar (usa format si lo hay, si no row[key]). */
|
|
528
|
+
rawValue(col, row) {
|
|
529
|
+
if (col.format) return col.format(row);
|
|
530
|
+
return row[col.key];
|
|
531
|
+
}
|
|
532
|
+
/** Valores distintos de una columna (para los chips del filtro multi-select). */
|
|
533
|
+
distinctValues(col) {
|
|
534
|
+
const set = /* @__PURE__ */ new Set();
|
|
535
|
+
for (const row of this.rows) {
|
|
536
|
+
const v = this.rawValue(col, row);
|
|
537
|
+
if (v != null && v !== "") set.add(String(v));
|
|
538
|
+
}
|
|
539
|
+
return [...set].sort((a, b) => a.localeCompare(b));
|
|
540
|
+
}
|
|
541
|
+
/** Filas tras buscar + filtrar + ordenar EN MEMORIA (solo modo cliente). */
|
|
542
|
+
get clientFiltered() {
|
|
543
|
+
let result = this.rows;
|
|
544
|
+
const needle = this.q.trim().toLowerCase();
|
|
545
|
+
if (needle && this.searchKeys.length) {
|
|
546
|
+
result = result.filter(
|
|
547
|
+
(r) => this.searchKeys.some((k) => String(r[k] ?? "").toLowerCase().includes(needle))
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const fkeys = Object.keys(this.clientFilters);
|
|
551
|
+
if (fkeys.length) {
|
|
552
|
+
result = result.filter(
|
|
553
|
+
(row) => fkeys.every((key) => {
|
|
554
|
+
const f = this.clientFilters[key];
|
|
555
|
+
const col = this.columns.find((c) => c.key === key);
|
|
556
|
+
if (!col) return true;
|
|
557
|
+
if (f.values && f.values.size > 0) {
|
|
558
|
+
return f.values.has(String(this.rawValue(col, row) ?? ""));
|
|
559
|
+
}
|
|
560
|
+
if (f.from || f.to) {
|
|
561
|
+
const raw = this.rawValue(col, row);
|
|
562
|
+
const t = raw == null ? NaN : new Date(raw).getTime();
|
|
563
|
+
const from = f.from ? new Date(f.from).getTime() : -Infinity;
|
|
564
|
+
const to = f.to ? new Date(f.to).getTime() + 864e5 - 1 : Infinity;
|
|
565
|
+
return !Number.isNaN(t) && t >= from && t <= to;
|
|
566
|
+
}
|
|
567
|
+
return true;
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if (this.clientSort) {
|
|
572
|
+
const col = this.columns.find((c) => c.key === this.clientSort);
|
|
573
|
+
if (col) {
|
|
574
|
+
const dir = this.clientSortDir === "asc" ? 1 : -1;
|
|
575
|
+
result = [...result].sort((a, b) => {
|
|
576
|
+
const va = this.rawValue(col, a);
|
|
577
|
+
const vb = this.rawValue(col, b);
|
|
578
|
+
if (va == null) return 1;
|
|
579
|
+
if (vb == null) return -1;
|
|
580
|
+
if (va < vb) return -1 * dir;
|
|
581
|
+
if (va > vb) return 1 * dir;
|
|
582
|
+
return 0;
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
cell(col, row) {
|
|
589
|
+
if (col.format) return col.format(row);
|
|
590
|
+
const v = row[col.key];
|
|
591
|
+
return v === null || v === void 0 ? "" : String(v);
|
|
592
|
+
}
|
|
593
|
+
/** ¿Es ordenable la columna? Servidor: opt-in (`sortable`). Cliente: por defecto SÍ (como el Hub),
|
|
594
|
+
* salvo `sortable: false` explícito. */
|
|
595
|
+
isSortable(col) {
|
|
596
|
+
return this.serverSide ? !!col.sortable : col.sortable !== false;
|
|
597
|
+
}
|
|
598
|
+
onHeaderClick(col) {
|
|
599
|
+
if (!this.isSortable(col)) return;
|
|
600
|
+
if (this.serverSide) {
|
|
601
|
+
const dir = this.sort === col.key && this.sortDir === "asc" ? "desc" : "asc";
|
|
602
|
+
this.emit("sortChange", { sort: col.key, dir });
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (this.clientSort === col.key) {
|
|
606
|
+
this.clientSortDir = this.clientSortDir === "asc" ? "desc" : "asc";
|
|
607
|
+
} else {
|
|
608
|
+
this.clientSort = col.key;
|
|
609
|
+
this.clientSortDir = "asc";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
onFilterInput(col, ev) {
|
|
613
|
+
const value = ev.target.value ?? "";
|
|
614
|
+
this.emit("filterChange", { col: col.key, value });
|
|
615
|
+
}
|
|
616
|
+
onRangeInput(col, edge, ev) {
|
|
617
|
+
const raw = ev.target.value ?? "";
|
|
618
|
+
const v = raw === "" ? "" : Number(raw);
|
|
619
|
+
this.emit("filterChange", { col: col.key, value: { [edge]: v } });
|
|
620
|
+
}
|
|
621
|
+
onDateRangeInput(col, edge, ev) {
|
|
622
|
+
const v = ev.target.value ?? "";
|
|
623
|
+
this.emit("filterChange", { col: col.key, value: { [edge]: v } });
|
|
624
|
+
}
|
|
625
|
+
// ── Filtros EN LÍNEA (toolbar) ────────────────────────────────────────────────────────────
|
|
626
|
+
// En modo cliente escriben directamente `clientFilters` (filtran en memoria); en servidor solo
|
|
627
|
+
// emiten `filterChange`. Reutilizan la misma forma de filtro que el drawer (values / from / to).
|
|
628
|
+
setClientFilter(key, patch) {
|
|
629
|
+
const next = { ...this.clientFilters };
|
|
630
|
+
const merged = { ...next[key], ...patch };
|
|
631
|
+
const empty = (!merged.values || merged.values.size === 0) && !merged.from && !merged.to;
|
|
632
|
+
if (empty) delete next[key];
|
|
633
|
+
else next[key] = merged;
|
|
634
|
+
this.clientFilters = next;
|
|
635
|
+
this.clientPage = 0;
|
|
636
|
+
}
|
|
637
|
+
// ion-select (select/multiselect) del panel de filtros (renderFilterControl). En servidor emite
|
|
638
|
+
// `filterChange`; en cliente escribe `clientFilters` (multiselect ⇒ filtra por inclusión).
|
|
639
|
+
onFilterSelect(col, value, multi) {
|
|
640
|
+
if (this.serverSide) {
|
|
641
|
+
this.emit("filterChange", { col: col.key, value: value ?? (multi ? [] : "") });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (multi) {
|
|
645
|
+
const arr = Array.isArray(value) ? value.map((v) => String(v)) : value != null && value !== "" ? [String(value)] : [];
|
|
646
|
+
this.setClientFilter(col.key, { values: arr.length ? new Set(arr) : void 0 });
|
|
647
|
+
} else {
|
|
648
|
+
const v = String(value ?? "");
|
|
649
|
+
this.setClientFilter(col.key, { values: v ? /* @__PURE__ */ new Set([v]) : void 0 });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
onInlineRange(col, edge, ev) {
|
|
653
|
+
const v = ev.target.value ?? "";
|
|
654
|
+
if (this.serverSide) {
|
|
655
|
+
this.emit("filterChange", { col: col.key, value: { [edge]: v } });
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.setClientFilter(col.key, { [edge]: v || void 0 });
|
|
659
|
+
}
|
|
660
|
+
// Menú overflow: ancla el popover al botón vía el evento de click (compatible con Shadow DOM).
|
|
661
|
+
openMenu(ev) {
|
|
662
|
+
this.menuEv = ev;
|
|
663
|
+
this.menuOpen = true;
|
|
664
|
+
}
|
|
665
|
+
setViewMode(mode) {
|
|
666
|
+
if (this.viewMode === mode) return;
|
|
667
|
+
this.viewMode = mode;
|
|
668
|
+
this.emit("viewChange", mode);
|
|
669
|
+
}
|
|
670
|
+
// Control de filtro de una columna, con componentes Ionic (mismos inputs que el form de alta).
|
|
671
|
+
renderFilterControl(col) {
|
|
672
|
+
if (!col.filterable) return nothing;
|
|
673
|
+
const type = col.filterType ?? "text";
|
|
674
|
+
if (type === "select" || type === "multiselect") {
|
|
675
|
+
const multi = type === "multiselect";
|
|
676
|
+
const opts = col.options ?? this.distinctValues(col).map((v) => ({ value: v, label: v }));
|
|
677
|
+
return html`
|
|
678
|
+
<ion-select
|
|
679
|
+
label=${col.header}
|
|
680
|
+
label-placement="stacked"
|
|
681
|
+
?multiple=${multi}
|
|
682
|
+
interface="modal"
|
|
683
|
+
.interfaceOptions=${{ cssClass: "ok-overlay" }}
|
|
684
|
+
placeholder=${col.header}
|
|
685
|
+
@ionChange=${(e) => this.onFilterSelect(col, e.detail.value, multi)}
|
|
686
|
+
>
|
|
687
|
+
${multi ? nothing : html`<ion-select-option value="">${col.header}</ion-select-option>`}
|
|
688
|
+
${opts.map((o) => html`<ion-select-option value=${o.value}>${o.label}</ion-select-option>`)}
|
|
689
|
+
</ion-select>
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
if (type === "range" || type === "daterange") {
|
|
693
|
+
const t = type === "daterange" ? "date" : "number";
|
|
694
|
+
const onEdge = type === "daterange" ? this.onDateRangeInput.bind(this) : this.onRangeInput.bind(this);
|
|
695
|
+
return html`
|
|
696
|
+
<div class="fblock">
|
|
697
|
+
<span class="flabel">${col.header}</span>
|
|
698
|
+
<div class="frange">
|
|
699
|
+
<ion-input type=${t} fill="outline" placeholder=${type === "daterange" ? this.t.from : this.t.gte}
|
|
700
|
+
@ionInput=${(e) => onEdge(col, "from", e)}></ion-input>
|
|
701
|
+
<ion-input type=${t} fill="outline" placeholder=${type === "daterange" ? this.t.to : this.t.lte}
|
|
702
|
+
@ionInput=${(e) => onEdge(col, "to", e)}></ion-input>
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
`;
|
|
706
|
+
}
|
|
707
|
+
const inputType = type === "number" ? "number" : type === "date" ? "date" : "text";
|
|
708
|
+
return html`
|
|
709
|
+
<ion-input
|
|
710
|
+
type=${inputType}
|
|
711
|
+
fill="outline"
|
|
712
|
+
label=${col.header}
|
|
713
|
+
label-placement="stacked"
|
|
714
|
+
placeholder=${this.t.filterPlaceholder}
|
|
715
|
+
@ionInput=${(e) => this.onFilterInput(col, e)}
|
|
716
|
+
></ion-input>
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
719
|
+
// Controles de filtro COMPACTOS para la toolbar (modo `inlineFilters`). Solo select y rango de
|
|
720
|
+
// fechas (los del screenshot); el resto de tipos siguen disponibles vía el drawer si no se activa
|
|
721
|
+
// `inlineFilters`. Look: «Todos los Estados» (placeholder) / «01/10/25 → 18/10/25».
|
|
722
|
+
renderInlineFilters() {
|
|
723
|
+
const cols = this.filterColumns.filter((c) => {
|
|
724
|
+
const t = c.filterType ?? "text";
|
|
725
|
+
return t === "select" || t === "multiselect" || t === "date" || t === "daterange";
|
|
726
|
+
});
|
|
727
|
+
if (!cols.length) return nothing;
|
|
728
|
+
return html`${cols.map((c) => this.renderInlineFilter(c))}`;
|
|
729
|
+
}
|
|
730
|
+
renderInlineFilter(col) {
|
|
731
|
+
const type = col.filterType ?? "text";
|
|
732
|
+
const f = this.clientFilters[col.key];
|
|
733
|
+
if (type === "select" || type === "multiselect") {
|
|
734
|
+
const multi = type === "multiselect";
|
|
735
|
+
const opts = col.options ?? this.distinctValues(col).map((v) => ({ value: v, label: v }));
|
|
736
|
+
const current = multi ? [...f?.values ?? /* @__PURE__ */ new Set()] : f?.values && f.values.size ? [...f.values][0] : "";
|
|
737
|
+
return html`
|
|
738
|
+
<ion-select
|
|
739
|
+
class="tk-filter"
|
|
740
|
+
?multiple=${multi}
|
|
741
|
+
interface="modal"
|
|
742
|
+
.interfaceOptions=${{ cssClass: "ok-overlay" }}
|
|
743
|
+
aria-label=${col.header}
|
|
744
|
+
placeholder=${col.header}
|
|
745
|
+
.value=${current}
|
|
746
|
+
@ionChange=${(e) => this.onFilterSelect(col, e.detail.value, multi)}
|
|
747
|
+
>
|
|
748
|
+
${multi ? nothing : html`<ion-select-option value="">${col.header}</ion-select-option>`}
|
|
749
|
+
${opts.map((o) => html`<ion-select-option value=${o.value}>${o.label}</ion-select-option>`)}
|
|
750
|
+
</ion-select>
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
return html`
|
|
754
|
+
<span class="tk-daterange" role="group" aria-label=${col.header}>
|
|
755
|
+
<ion-icon name="calendar-outline"></ion-icon>
|
|
756
|
+
<ion-input type="date" aria-label=${this.t.fromOf.replace("{label}", col.header)} .value=${f?.from ?? ""} @ionChange=${(e) => this.onInlineRange(col, "from", e)}></ion-input>
|
|
757
|
+
<span class="arr">→</span>
|
|
758
|
+
<ion-input type="date" aria-label=${this.t.toOf.replace("{label}", col.header)} .value=${f?.to ?? ""} @ionChange=${(e) => this.onInlineRange(col, "to", e)}></ion-input>
|
|
759
|
+
</span>
|
|
760
|
+
`;
|
|
761
|
+
}
|
|
762
|
+
// Menú overflow («⋮») con ion-popover anclado por evento (Shadow-DOM-safe).
|
|
763
|
+
renderOverflowMenu() {
|
|
764
|
+
if (!this.menuActions.length) return nothing;
|
|
765
|
+
return html`
|
|
766
|
+
<ion-button class="toolbtn" fill="clear" aria-label=${this.t.moreActions} @click=${(e) => this.openMenu(e)}>
|
|
767
|
+
<ion-icon slot="icon-only" name="ellipsis-vertical"></ion-icon>
|
|
768
|
+
</ion-button>
|
|
769
|
+
<ion-popover
|
|
770
|
+
.isOpen=${this.menuOpen}
|
|
771
|
+
.event=${this.menuEv}
|
|
772
|
+
dismiss-on-select="true"
|
|
773
|
+
@didDismiss=${() => this.menuOpen = false}
|
|
774
|
+
>
|
|
775
|
+
<ion-content>
|
|
776
|
+
<ion-list lines="none">
|
|
777
|
+
${this.menuActions.map(
|
|
778
|
+
(a) => html`
|
|
779
|
+
<ion-item button .detail=${false} @click=${() => {
|
|
780
|
+
this.menuOpen = false;
|
|
781
|
+
this.emit("menuAction", { actionId: a.id });
|
|
782
|
+
}}>
|
|
783
|
+
${a.icon ? html`<ion-icon slot="start" name=${a.icon} color=${a.color ?? nothing}></ion-icon>` : nothing}
|
|
784
|
+
<ion-label color=${a.color ?? nothing}>${a.label}</ion-label>
|
|
785
|
+
</ion-item>
|
|
786
|
+
`
|
|
787
|
+
)}
|
|
788
|
+
</ion-list>
|
|
789
|
+
</ion-content>
|
|
790
|
+
</ion-popover>
|
|
791
|
+
`;
|
|
792
|
+
}
|
|
793
|
+
// Botones de acción de una fila (compartido por vista tabla y tarjetas).
|
|
794
|
+
actionButtons(row) {
|
|
795
|
+
if (!this.actions.length) return nothing;
|
|
796
|
+
return html`
|
|
797
|
+
<div class="actions">
|
|
798
|
+
${this.actions.map(
|
|
799
|
+
(a) => html`
|
|
800
|
+
<ion-button
|
|
801
|
+
size="small"
|
|
802
|
+
fill="clear"
|
|
803
|
+
color=${a.color ?? "medium"}
|
|
804
|
+
@click=${() => this.emit("rowAction", { actionId: a.id, row })}
|
|
805
|
+
>
|
|
806
|
+
${a.icon ? html`<ion-icon slot="icon-only" name=${a.icon}></ion-icon>` : a.label}
|
|
807
|
+
</ion-button>
|
|
808
|
+
`
|
|
809
|
+
)}
|
|
810
|
+
</div>
|
|
811
|
+
`;
|
|
812
|
+
}
|
|
813
|
+
// Botón de barra icon-only (filtros / alta / conmutador de vista). `on` = estado activo.
|
|
814
|
+
// `badge` opcional → contador (p.ej. nº de filtros activos), look del Hub.
|
|
815
|
+
toolButton(icon, on, onClick, label, badge) {
|
|
816
|
+
return html`
|
|
817
|
+
<ion-button class="toolbtn" size="small" fill=${on ? "solid" : "outline"} title=${label} aria-label=${label} @click=${onClick}>
|
|
818
|
+
<ion-icon slot="icon-only" name=${icon}></ion-icon>
|
|
819
|
+
${badge && badge > 0 ? html`<span class="badge">${badge}</span>` : nothing}
|
|
820
|
+
</ion-button>
|
|
821
|
+
`;
|
|
822
|
+
}
|
|
823
|
+
/** Plantilla de columnas del grid de la vista lista: [checkbox] [columnas…] [acciones]. */
|
|
824
|
+
gridTemplate() {
|
|
825
|
+
return [
|
|
826
|
+
this.selectable ? "2.75rem" : null,
|
|
827
|
+
...this.visibleColumns.map((c) => c.width ?? "minmax(8rem,1fr)"),
|
|
828
|
+
this.actions.length ? "auto" : null
|
|
829
|
+
].filter(Boolean).join(" ");
|
|
830
|
+
}
|
|
831
|
+
/** Lista de páginas a mostrar en el pager numerado (1-based): primera, última, vecinas de la
|
|
832
|
+
* actual y «…» donde haya saltos. P.ej. en página 1 de 52 → [1,2,3,'…',52]. */
|
|
833
|
+
pageList(cur1, total) {
|
|
834
|
+
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
|
|
835
|
+
const want = /* @__PURE__ */ new Set([1, total, cur1, cur1 - 1, cur1 + 1]);
|
|
836
|
+
if (cur1 <= 3) [2, 3].forEach((p) => want.add(p));
|
|
837
|
+
if (cur1 >= total - 2) [total - 1, total - 2].forEach((p) => want.add(p));
|
|
838
|
+
const sorted = [...want].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
|
|
839
|
+
const out = [];
|
|
840
|
+
let prev = 0;
|
|
841
|
+
for (const p of sorted) {
|
|
842
|
+
if (p - prev > 1) out.push("…");
|
|
843
|
+
out.push(p);
|
|
844
|
+
prev = p;
|
|
845
|
+
}
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
render() {
|
|
849
|
+
const ps = this.serverSide ? this.pageSize : this.clientPageSize || this.pageSize;
|
|
850
|
+
let visible;
|
|
851
|
+
let pages;
|
|
852
|
+
let current;
|
|
853
|
+
let count;
|
|
854
|
+
if (this.serverSide) {
|
|
855
|
+
visible = this.rows;
|
|
856
|
+
count = this.total;
|
|
857
|
+
pages = Math.max(1, Math.ceil(this.total / ps));
|
|
858
|
+
current = Math.min(this.page, pages - 1);
|
|
859
|
+
} else {
|
|
860
|
+
const filtered = this.clientFiltered;
|
|
861
|
+
count = filtered.length;
|
|
862
|
+
pages = Math.max(1, Math.ceil(filtered.length / ps));
|
|
863
|
+
current = Math.min(this.clientPage, pages - 1);
|
|
864
|
+
visible = filtered.slice(current * ps, current * ps + ps);
|
|
865
|
+
}
|
|
866
|
+
const goTo = (p) => {
|
|
867
|
+
if (this.serverSide) this.emit("pageChange", p);
|
|
868
|
+
else this.clientPage = p;
|
|
869
|
+
};
|
|
870
|
+
const setPageSize = (n) => {
|
|
871
|
+
if (this.serverSide) this.emit("pageSizeChange", n);
|
|
872
|
+
else {
|
|
873
|
+
this.clientPageSize = n;
|
|
874
|
+
this.clientPage = 0;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
const searchbar = this.serverSide ? html`<ion-searchbar class="ion-no-border" placeholder=${this.effSearchPlaceholder} debounce="250" @ionInput=${this.onSearch}></ion-searchbar>` : html`<ion-searchbar class="ion-no-border" .value=${this.q} placeholder=${this.effSearchPlaceholder} debounce="250" @ionInput=${this.onSearch}></ion-searchbar>`;
|
|
878
|
+
const selCount = this.selection.size;
|
|
879
|
+
const showTopbar = !!this.title || this.hasSearch || this.viewToggle || this.effColumnPicker || this.effExport || this.effImport || this.hasFilterRow || this.addable || !!this.primaryAction;
|
|
880
|
+
return html`
|
|
881
|
+
<div class="card">
|
|
882
|
+
${showTopbar ? html`
|
|
883
|
+
<div class="bar">
|
|
884
|
+
<div class="bar-main">
|
|
885
|
+
${this.title ? html`<div class="title-wrap"><h2 class="title">${this.title}</h2><span class="title-count">${count}</span></div>` : nothing}
|
|
886
|
+
${this.hasSearch ? html`<div class="search">${searchbar}</div>` : nothing}
|
|
887
|
+
${this.inlineFilters ? this.renderInlineFilters() : nothing}
|
|
888
|
+
<span class="tk-spacer"></span>
|
|
889
|
+
${this.effPageSizes.length ? html`
|
|
890
|
+
<ion-select
|
|
891
|
+
class="tk-psize"
|
|
892
|
+
interface="popover"
|
|
893
|
+
aria-label=${this.t.rowsPerPage}
|
|
894
|
+
.value=${ps}
|
|
895
|
+
@ionChange=${(e) => setPageSize(Number(e.detail.value))}
|
|
896
|
+
>
|
|
897
|
+
${this.effPageSizes.map((n) => html`<ion-select-option .value=${n}>${n}</ion-select-option>`)}
|
|
898
|
+
</ion-select>
|
|
899
|
+
` : nothing}
|
|
900
|
+
${this.viewToggle ? html`
|
|
901
|
+
<span class="viewseg">
|
|
902
|
+
${this.toolButton("list-outline", this.viewMode === "table", () => this.setViewMode("table"), this.t.viewList)}
|
|
903
|
+
${this.toolButton("grid-outline", this.viewMode === "cards", () => this.setViewMode("cards"), this.t.viewCards)}
|
|
904
|
+
</span>
|
|
905
|
+
` : nothing}
|
|
906
|
+
${this.effColumnPicker ? html`
|
|
907
|
+
<ion-select
|
|
908
|
+
class="tk-cols"
|
|
909
|
+
multiple
|
|
910
|
+
interface="popover"
|
|
911
|
+
aria-label=${this.t.columnsVisible}
|
|
912
|
+
.value=${this.visibleColumns.map((c) => c.key)}
|
|
913
|
+
.selectedText=${this.t.columns}
|
|
914
|
+
@ionChange=${(e) => this.setVisibleColumns(e.detail.value)}
|
|
915
|
+
>
|
|
916
|
+
${this.columns.map((c) => html`<ion-select-option value=${c.key}>${c.header}</ion-select-option>`)}
|
|
917
|
+
</ion-select>
|
|
918
|
+
` : nothing}
|
|
919
|
+
${this.hasFilterRow && !this.inlineFilters ? this.toolButton("funnel-outline", this.panel === "filters" || this.activeFilterCount > 0, () => this.toggle("filters"), this.t.filters, this.serverSide ? void 0 : this.activeFilterCount) : nothing}
|
|
920
|
+
${this.effImport ? html`
|
|
921
|
+
${this.toolButton("cloud-upload-outline", false, () => this.renderRoot.querySelector(".tk-file")?.click(), this.t.importCsv)}
|
|
922
|
+
<input class="tk-file" type="file" accept=".csv,text/csv" hidden @change=${(e) => this.onImportFile(e)} />
|
|
923
|
+
` : nothing}
|
|
924
|
+
${this.effExport ? this.toolButton("download-outline", false, () => this.exportCsv(), this.t.exportCsv) : nothing}
|
|
925
|
+
${this.addable ? this.toolButton("add", this.panel === "create", () => this.toggle("create"), this.t.add) : nothing}
|
|
926
|
+
${this.renderOverflowMenu()}
|
|
927
|
+
${this.primaryAction ? html`
|
|
928
|
+
<ion-button
|
|
929
|
+
class="primary-btn"
|
|
930
|
+
size="small"
|
|
931
|
+
title=${this.primaryAction.label}
|
|
932
|
+
aria-label=${this.primaryAction.label}
|
|
933
|
+
@click=${() => this.emit("primaryAction", {})}
|
|
934
|
+
><ion-icon slot="icon-only" name=${this.primaryAction.icon ?? "add"}></ion-icon></ion-button>
|
|
935
|
+
` : nothing}
|
|
936
|
+
<!-- El módulo proyecta aquí acciones globales adicionales. -->
|
|
937
|
+
<slot name="toolbar"></slot>
|
|
938
|
+
</div>
|
|
939
|
+
${this.selectable && selCount > 0 ? html`
|
|
940
|
+
<div class="selbar">
|
|
941
|
+
<strong>${this.t.selected.replace("{n}", String(selCount))}</strong>
|
|
942
|
+
<button class="sel-clear" @click=${() => this.setSelection(/* @__PURE__ */ new Set())}>
|
|
943
|
+
<ion-icon name="close" style="font-size:14px"></ion-icon> ${this.t.clear}
|
|
944
|
+
</button>
|
|
945
|
+
</div>
|
|
946
|
+
` : nothing}
|
|
947
|
+
</div>
|
|
948
|
+
` : nothing}
|
|
949
|
+
|
|
950
|
+
${this.viewMode === "cards" && this.cardViewEnabled ? this.renderCards(visible) : this.renderTable(visible)}
|
|
951
|
+
|
|
952
|
+
${pages > 1 || this.effPageSizes.length ? html`
|
|
953
|
+
<div class="pager">
|
|
954
|
+
<div class="left">
|
|
955
|
+
<span>
|
|
956
|
+
${pages > 1 ? html`${this.t.showing.replace("{from}", String(current * ps + 1)).replace("{to}", String(Math.min((current + 1) * ps, count)))} ` : nothing}
|
|
957
|
+
<span class="strong">${count}</span> ${count === 1 ? this.t.recordSingular : this.t.recordPlural}
|
|
958
|
+
</span>
|
|
959
|
+
${!showTopbar && this.effPageSizes.length ? html`
|
|
960
|
+
<select class="psize" @change=${(e) => setPageSize(Number(e.target.value))}>
|
|
961
|
+
${this.effPageSizes.map((n) => html`<option value=${n} ?selected=${n === ps}>${this.t.perPageShort.replace("{n}", String(n))}</option>`)}
|
|
962
|
+
</select>
|
|
963
|
+
` : nothing}
|
|
964
|
+
</div>
|
|
965
|
+
${pages > 1 ? html`
|
|
966
|
+
<div class="nav">
|
|
967
|
+
<ion-button size="small" fill="clear" ?disabled=${current === 0} @click=${() => goTo(current - 1)}><ion-icon slot="icon-only" name="chevron-back"></ion-icon></ion-button>
|
|
968
|
+
${this.pageList(current + 1, pages).map(
|
|
969
|
+
(p) => p === "…" ? html`<span class="pgap">…</span>` : html`<button class=${`pnum${p === current + 1 ? " on" : ""}`} @click=${() => goTo(p - 1)}>${p}</button>`
|
|
970
|
+
)}
|
|
971
|
+
<ion-button size="small" fill="clear" ?disabled=${current >= pages - 1} @click=${() => goTo(current + 1)}><ion-icon slot="icon-only" name="chevron-forward"></ion-icon></ion-button>
|
|
972
|
+
</div>
|
|
973
|
+
` : nothing}
|
|
974
|
+
</div>
|
|
975
|
+
` : nothing}
|
|
976
|
+
|
|
977
|
+
${this.panel !== "none" ? this.renderDrawer() : nothing}
|
|
978
|
+
</div>
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
// Panel lateral derecho DENTRO de la tabla (no empuja contenido; igual en lista y tarjetas).
|
|
982
|
+
renderDrawer() {
|
|
983
|
+
const isFilters = this.panel === "filters";
|
|
984
|
+
const clientFilters = isFilters && !this.serverSide;
|
|
985
|
+
return html`
|
|
986
|
+
<div class="tk-scrim" @click=${() => this.close()}></div>
|
|
987
|
+
<aside class="drawer" role="dialog" aria-label=${isFilters ? this.t.filters : this.t.form}>
|
|
988
|
+
<header class="dh">
|
|
989
|
+
<strong>${isFilters ? this.t.filters : this.t.newRecord}</strong>
|
|
990
|
+
<ion-button fill="clear" size="small" aria-label=${this.t.close} @click=${() => this.close()}><ion-icon slot="icon-only" name="close"></ion-icon></ion-button>
|
|
991
|
+
</header>
|
|
992
|
+
<div class="db">
|
|
993
|
+
${isFilters ? clientFilters ? this.filterColumns.map((c) => this.renderClientFilter(c)) : this.filterColumns.map((c) => html`<div class="fblock">${this.renderFilterControl(c)}</div>`) : html`<slot name="create"></slot>`}
|
|
994
|
+
</div>
|
|
995
|
+
${clientFilters ? html`
|
|
996
|
+
<footer class="df">
|
|
997
|
+
<button class="sel-clear df-clear" ?disabled=${Object.keys(this.filterDraft).length === 0} @click=${() => this.clearFilters()}>${this.t.clear}</button>
|
|
998
|
+
<ion-button class="primary-btn" size="small" @click=${() => this.applyFilters()}>${this.t.apply}</ion-button>
|
|
999
|
+
</footer>
|
|
1000
|
+
` : nothing}
|
|
1001
|
+
</aside>
|
|
1002
|
+
`;
|
|
1003
|
+
}
|
|
1004
|
+
// Control de filtro CLIENTE de una columna: chips multi-select (select) o rango de fechas.
|
|
1005
|
+
renderClientFilter(col) {
|
|
1006
|
+
const label = col.header;
|
|
1007
|
+
if (col.filterType === "daterange" || col.filterType === "date") {
|
|
1008
|
+
const f = this.filterDraft[col.key] ?? {};
|
|
1009
|
+
return html`
|
|
1010
|
+
<div class="fblock">
|
|
1011
|
+
<span class="flabel">${label}</span>
|
|
1012
|
+
<div class="daterange">
|
|
1013
|
+
<ion-input type="date" label=${this.t.from} label-placement="stacked" fill="outline" .value=${f.from ?? ""} @ionChange=${(e) => this.setFilterRange(col.key, "from", e.detail.value ?? "")}></ion-input>
|
|
1014
|
+
<ion-input type="date" label=${this.t.to} label-placement="stacked" fill="outline" .value=${f.to ?? ""} @ionChange=${(e) => this.setFilterRange(col.key, "to", e.detail.value ?? "")}></ion-input>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
`;
|
|
1018
|
+
}
|
|
1019
|
+
const distinct = this.distinctValues(col);
|
|
1020
|
+
const selected = this.filterDraft[col.key]?.values ?? /* @__PURE__ */ new Set();
|
|
1021
|
+
return html`
|
|
1022
|
+
<div class="fblock">
|
|
1023
|
+
<span class="flabel">${label}</span>
|
|
1024
|
+
<div class="chips">
|
|
1025
|
+
${distinct.length === 0 ? html`<span class="chip-empty">${this.t.noValues}</span>` : distinct.map((v) => {
|
|
1026
|
+
const on = selected.has(v);
|
|
1027
|
+
return html`
|
|
1028
|
+
<button class=${`chip${on ? " on" : ""}`} @click=${() => this.toggleFilterValue(col.key, v)}>
|
|
1029
|
+
${on ? html`<ion-icon name="checkmark-outline"></ion-icon>` : nothing}${v}
|
|
1030
|
+
</button>
|
|
1031
|
+
`;
|
|
1032
|
+
})}
|
|
1033
|
+
</div>
|
|
1034
|
+
</div>
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
emptyState() {
|
|
1038
|
+
return html`
|
|
1039
|
+
<div class="empty">
|
|
1040
|
+
<span class="empty-ic"><ion-icon name="file-tray-outline"></ion-icon></span>
|
|
1041
|
+
<span>${this.effEmptyMessage}</span>
|
|
1042
|
+
</div>
|
|
1043
|
+
`;
|
|
1044
|
+
}
|
|
1045
|
+
// Vista LISTA en CSS GRID (no <table>): permite ancho por columna y cabecera sticky.
|
|
1046
|
+
renderTable(visible) {
|
|
1047
|
+
if (visible.length === 0) return this.emptyState();
|
|
1048
|
+
const cols = this.visibleColumns;
|
|
1049
|
+
const tpl = { gridTemplateColumns: this.gridTemplate() };
|
|
1050
|
+
const allOn = this.selectable && visible.length > 0 && visible.every((r) => this.selection.has(this.keyOf(r)));
|
|
1051
|
+
const alignCls = (a) => a === "right" ? "right" : a === "center" ? "center" : "left";
|
|
1052
|
+
return html`
|
|
1053
|
+
<div class="scroll">
|
|
1054
|
+
<div class="grid" role="table">
|
|
1055
|
+
<!-- Cabecera -->
|
|
1056
|
+
<div class="grow ghead" role="row" style=${styleMap(tpl)}>
|
|
1057
|
+
${this.selectable ? html`<span class="selcb"><ion-checkbox .checked=${allOn} aria-label=${this.t.selectAll} @ionChange=${() => this.toggleAll(visible)}></ion-checkbox></span>` : nothing}
|
|
1058
|
+
${cols.map((c) => {
|
|
1059
|
+
const sortable = this.isSortable(c);
|
|
1060
|
+
const active = sortable && (this.serverSide ? this.sort === c.key : this.clientSort === c.key);
|
|
1061
|
+
const dir = this.serverSide ? this.sortDir : this.clientSortDir;
|
|
1062
|
+
const caretIcon = !active ? "swap-vertical-outline" : dir === "asc" ? "chevron-up-outline" : "chevron-down-outline";
|
|
1063
|
+
return html`
|
|
1064
|
+
<div
|
|
1065
|
+
class=${`gcell gh ${alignCls(c.align)}${sortable ? " sortable" : ""}`}
|
|
1066
|
+
role="columnheader"
|
|
1067
|
+
@click=${() => this.onHeaderClick(c)}
|
|
1068
|
+
>
|
|
1069
|
+
<span>${c.header}</span>
|
|
1070
|
+
${sortable ? html`<span class=${`caret${active ? " on" : ""}`}><ion-icon name=${caretIcon}></ion-icon></span>` : nothing}
|
|
1071
|
+
</div>
|
|
1072
|
+
`;
|
|
1073
|
+
})}
|
|
1074
|
+
${this.actions.length ? html`<div class="gcell gh right" role="columnheader">${this.t.actions}</div>` : nothing}
|
|
1075
|
+
</div>
|
|
1076
|
+
|
|
1077
|
+
<!-- Filas -->
|
|
1078
|
+
${repeat(
|
|
1079
|
+
visible,
|
|
1080
|
+
(row) => this.keyOf(row),
|
|
1081
|
+
(row) => {
|
|
1082
|
+
const key = this.keyOf(row);
|
|
1083
|
+
const selected = this.selectable && this.selection.has(key);
|
|
1084
|
+
return html`
|
|
1085
|
+
<div class=${`grow grow-data${selected ? " selected" : ""}`} role="row" style=${styleMap(tpl)}>
|
|
1086
|
+
${this.selectable ? html`<span class="selcb"><ion-checkbox .checked=${selected} aria-label=${this.t.selectRow} @ionChange=${() => this.toggleRow(key)}></ion-checkbox></span>` : nothing}
|
|
1087
|
+
${cols.map(
|
|
1088
|
+
(c) => html`<div class=${`gcell ${alignCls(c.align)}`} role="cell">${c.render ? c.render(row) : html`<span>${this.cell(c, row)}</span>`}</div>`
|
|
1089
|
+
)}
|
|
1090
|
+
${this.actions.length ? html`<div class="gcell right" role="cell">${this.actionButtons(row)}</div>` : nothing}
|
|
1091
|
+
</div>
|
|
1092
|
+
`;
|
|
1093
|
+
}
|
|
1094
|
+
)}
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
1097
|
+
`;
|
|
1098
|
+
}
|
|
1099
|
+
renderCards(visible) {
|
|
1100
|
+
if (visible.length === 0) return this.emptyState();
|
|
1101
|
+
const hasHead = !!this.cardTitle || !!this.cardIcon || this.selectable;
|
|
1102
|
+
return html`
|
|
1103
|
+
<div class="cards-grid">
|
|
1104
|
+
${repeat(
|
|
1105
|
+
visible,
|
|
1106
|
+
(row) => this.keyOf(row),
|
|
1107
|
+
(row) => {
|
|
1108
|
+
const key = this.keyOf(row);
|
|
1109
|
+
const selected = this.selectable && this.selection.has(key);
|
|
1110
|
+
const icon = this.cardIcon?.(row);
|
|
1111
|
+
return html`
|
|
1112
|
+
<div class=${`rcard${selected ? " selected" : ""}`}>
|
|
1113
|
+
${hasHead ? html`
|
|
1114
|
+
<header class="rcard-head">
|
|
1115
|
+
${icon != null && icon !== "" ? html`<span class="rc-icon">${typeof icon === "string" ? html`<ion-icon name=${icon}></ion-icon>` : icon}</span>` : nothing}
|
|
1116
|
+
<span class="rc-title">${this.cardTitle ? this.cardTitle(row) : nothing}</span>
|
|
1117
|
+
${this.selectable ? html`<ion-checkbox .checked=${selected} aria-label=${this.t.select} @ionChange=${() => this.toggleRow(key)}></ion-checkbox>` : nothing}
|
|
1118
|
+
</header>
|
|
1119
|
+
` : nothing}
|
|
1120
|
+
<div class="rcard-body">
|
|
1121
|
+
${this.renderCard ? this.renderCard(row) : this.visibleColumns.map(
|
|
1122
|
+
(c) => html`<div class="rrow"><span class="rk">${c.header}</span><span class="rv">${c.render ? c.render(row) : this.cell(c, row)}</span></div>`
|
|
1123
|
+
)}
|
|
1124
|
+
</div>
|
|
1125
|
+
${this.actions.length ? html`<div class="ractions">${this.actionButtons(row)}</div>` : nothing}
|
|
1126
|
+
</div>
|
|
1127
|
+
`;
|
|
1128
|
+
}
|
|
1129
|
+
)}
|
|
1130
|
+
</div>
|
|
1131
|
+
`;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
__decorateClass([
|
|
1135
|
+
property({ attribute: false })
|
|
1136
|
+
], OkDataTable.prototype, "columns");
|
|
1137
|
+
__decorateClass([
|
|
1138
|
+
property({ attribute: false })
|
|
1139
|
+
], OkDataTable.prototype, "rows");
|
|
1140
|
+
__decorateClass([
|
|
1141
|
+
property({ attribute: false })
|
|
1142
|
+
], OkDataTable.prototype, "searchKeys");
|
|
1143
|
+
__decorateClass([
|
|
1144
|
+
property({ attribute: "row-key-field" })
|
|
1145
|
+
], OkDataTable.prototype, "rowKeyField");
|
|
1146
|
+
__decorateClass([
|
|
1147
|
+
property({ attribute: false })
|
|
1148
|
+
], OkDataTable.prototype, "rowKey");
|
|
1149
|
+
__decorateClass([
|
|
1150
|
+
property({ type: Number, attribute: "page-size" })
|
|
1151
|
+
], OkDataTable.prototype, "pageSize");
|
|
1152
|
+
__decorateClass([
|
|
1153
|
+
property({ attribute: "empty-message" })
|
|
1154
|
+
], OkDataTable.prototype, "emptyMessage");
|
|
1155
|
+
__decorateClass([
|
|
1156
|
+
property({ attribute: "search-placeholder" })
|
|
1157
|
+
], OkDataTable.prototype, "searchPlaceholder");
|
|
1158
|
+
__decorateClass([
|
|
1159
|
+
property({ attribute: false })
|
|
1160
|
+
], OkDataTable.prototype, "labels");
|
|
1161
|
+
__decorateClass([
|
|
1162
|
+
property({ attribute: false })
|
|
1163
|
+
], OkDataTable.prototype, "actions");
|
|
1164
|
+
__decorateClass([
|
|
1165
|
+
property({ type: Boolean })
|
|
1166
|
+
], OkDataTable.prototype, "addable");
|
|
1167
|
+
__decorateClass([
|
|
1168
|
+
property({ attribute: false })
|
|
1169
|
+
], OkDataTable.prototype, "pageSizeOptions");
|
|
1170
|
+
__decorateClass([
|
|
1171
|
+
property({ type: Boolean, reflect: true })
|
|
1172
|
+
], OkDataTable.prototype, "fill");
|
|
1173
|
+
__decorateClass([
|
|
1174
|
+
property({ type: Boolean, attribute: "column-picker" })
|
|
1175
|
+
], OkDataTable.prototype, "columnPicker");
|
|
1176
|
+
__decorateClass([
|
|
1177
|
+
property({ type: Boolean })
|
|
1178
|
+
], OkDataTable.prototype, "csv");
|
|
1179
|
+
__decorateClass([
|
|
1180
|
+
property({ attribute: "csv-name" })
|
|
1181
|
+
], OkDataTable.prototype, "csvName");
|
|
1182
|
+
__decorateClass([
|
|
1183
|
+
property({ type: Boolean, attribute: "server-side" })
|
|
1184
|
+
], OkDataTable.prototype, "serverSide");
|
|
1185
|
+
__decorateClass([
|
|
1186
|
+
property({ type: Number })
|
|
1187
|
+
], OkDataTable.prototype, "total");
|
|
1188
|
+
__decorateClass([
|
|
1189
|
+
property({ type: Number })
|
|
1190
|
+
], OkDataTable.prototype, "page");
|
|
1191
|
+
__decorateClass([
|
|
1192
|
+
property({ type: Boolean })
|
|
1193
|
+
], OkDataTable.prototype, "searchable");
|
|
1194
|
+
__decorateClass([
|
|
1195
|
+
property({ type: String })
|
|
1196
|
+
], OkDataTable.prototype, "sort");
|
|
1197
|
+
__decorateClass([
|
|
1198
|
+
property({ attribute: "sort-dir" })
|
|
1199
|
+
], OkDataTable.prototype, "sortDir");
|
|
1200
|
+
__decorateClass([
|
|
1201
|
+
property()
|
|
1202
|
+
], OkDataTable.prototype, "title");
|
|
1203
|
+
__decorateClass([
|
|
1204
|
+
property({ attribute: false })
|
|
1205
|
+
], OkDataTable.prototype, "views");
|
|
1206
|
+
__decorateClass([
|
|
1207
|
+
property({ type: Boolean })
|
|
1208
|
+
], OkDataTable.prototype, "exportable");
|
|
1209
|
+
__decorateClass([
|
|
1210
|
+
property({ type: Boolean })
|
|
1211
|
+
], OkDataTable.prototype, "importable");
|
|
1212
|
+
__decorateClass([
|
|
1213
|
+
property({ type: Boolean, attribute: "column-selector" })
|
|
1214
|
+
], OkDataTable.prototype, "columnSelector");
|
|
1215
|
+
__decorateClass([
|
|
1216
|
+
property({ attribute: false })
|
|
1217
|
+
], OkDataTable.prototype, "pageSizes");
|
|
1218
|
+
__decorateClass([
|
|
1219
|
+
property({ type: Boolean })
|
|
1220
|
+
], OkDataTable.prototype, "selectable");
|
|
1221
|
+
__decorateClass([
|
|
1222
|
+
property({ attribute: false })
|
|
1223
|
+
], OkDataTable.prototype, "selectedKeys");
|
|
1224
|
+
__decorateClass([
|
|
1225
|
+
property({ attribute: false })
|
|
1226
|
+
], OkDataTable.prototype, "primaryAction");
|
|
1227
|
+
__decorateClass([
|
|
1228
|
+
property({ type: Boolean })
|
|
1229
|
+
], OkDataTable.prototype, "inlineFilters");
|
|
1230
|
+
__decorateClass([
|
|
1231
|
+
property({ attribute: false })
|
|
1232
|
+
], OkDataTable.prototype, "menuActions");
|
|
1233
|
+
__decorateClass([
|
|
1234
|
+
property({ attribute: false })
|
|
1235
|
+
], OkDataTable.prototype, "cardTitle");
|
|
1236
|
+
__decorateClass([
|
|
1237
|
+
property({ attribute: false })
|
|
1238
|
+
], OkDataTable.prototype, "cardIcon");
|
|
1239
|
+
__decorateClass([
|
|
1240
|
+
property({ attribute: false })
|
|
1241
|
+
], OkDataTable.prototype, "renderCard");
|
|
1242
|
+
__decorateClass([
|
|
1243
|
+
state()
|
|
1244
|
+
], OkDataTable.prototype, "q");
|
|
1245
|
+
__decorateClass([
|
|
1246
|
+
state()
|
|
1247
|
+
], OkDataTable.prototype, "clientPage");
|
|
1248
|
+
__decorateClass([
|
|
1249
|
+
state()
|
|
1250
|
+
], OkDataTable.prototype, "clientPageSize");
|
|
1251
|
+
__decorateClass([
|
|
1252
|
+
state()
|
|
1253
|
+
], OkDataTable.prototype, "clientSort");
|
|
1254
|
+
__decorateClass([
|
|
1255
|
+
state()
|
|
1256
|
+
], OkDataTable.prototype, "clientSortDir");
|
|
1257
|
+
__decorateClass([
|
|
1258
|
+
state()
|
|
1259
|
+
], OkDataTable.prototype, "clientFilters");
|
|
1260
|
+
__decorateClass([
|
|
1261
|
+
state()
|
|
1262
|
+
], OkDataTable.prototype, "filterDraft");
|
|
1263
|
+
__decorateClass([
|
|
1264
|
+
state()
|
|
1265
|
+
], OkDataTable.prototype, "panel");
|
|
1266
|
+
__decorateClass([
|
|
1267
|
+
state()
|
|
1268
|
+
], OkDataTable.prototype, "viewMode");
|
|
1269
|
+
__decorateClass([
|
|
1270
|
+
state()
|
|
1271
|
+
], OkDataTable.prototype, "hiddenKeys");
|
|
1272
|
+
__decorateClass([
|
|
1273
|
+
state()
|
|
1274
|
+
], OkDataTable.prototype, "internalSelection");
|
|
1275
|
+
__decorateClass([
|
|
1276
|
+
state()
|
|
1277
|
+
], OkDataTable.prototype, "menuOpen");
|
|
1278
|
+
define("ok-data-table", OkDataTable);
|
|
1279
|
+
export {
|
|
1280
|
+
OkDataTable
|
|
1281
|
+
};
|