@erplora/outfitkit 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +5 -6
  2. package/dist/cdn.d.ts +0 -5
  3. package/dist/index.d.ts +0 -9
  4. package/dist/index.js +0 -10
  5. package/dist/layout.css +58 -5
  6. package/dist/ok-data-table.js +1 -1
  7. package/dist/ok-file-manager.js +1 -1
  8. package/dist/ok-lightbox.js +1 -1
  9. package/dist/outfitkit.bundle.js +3634 -5572
  10. package/dist/outfitkit.js +0 -5
  11. package/dist/shared/icons.js +3 -3
  12. package/dist/showcase/auth-login-saas-parity.test.d.ts +1 -0
  13. package/dist/showcase/component-catalog-completeness.test.d.ts +1 -0
  14. package/dist/showcase/current-page-contract.test.d.ts +1 -0
  15. package/dist/showcase/hub-access-dashboard-parity.test.d.ts +1 -0
  16. package/dist/showcase/hub-apps-billing-pages-parity.test.d.ts +1 -0
  17. package/dist/showcase/hub-employee-forms-parity.test.d.ts +1 -0
  18. package/dist/showcase/hub-shell-parity.test.d.ts +1 -0
  19. package/dist/showcase/hub-system-settings-shell-parity.test.d.ts +1 -0
  20. package/dist/showcase/hub-table-pages-parity.test.d.ts +1 -0
  21. package/dist/showcase/ionic-boundary.test.d.ts +1 -0
  22. package/dist/showcase/ionic-ios-default.test.d.ts +1 -0
  23. package/dist/showcase/module-appointments-parity.test.d.ts +1 -0
  24. package/dist/showcase/module-backup-pages-parity.test.d.ts +1 -0
  25. package/dist/showcase/module-cart-checkout-pages-parity.test.d.ts +1 -0
  26. package/dist/showcase/module-cash-register-parity.test.d.ts +1 -0
  27. package/dist/showcase/module-customers-fields-parity.test.d.ts +1 -0
  28. package/dist/showcase/module-customers-groups-parity.test.d.ts +1 -0
  29. package/dist/showcase/module-customers-list-parity.test.d.ts +1 -0
  30. package/dist/showcase/module-customers-tags-parity.test.d.ts +1 -0
  31. package/dist/showcase/module-inventory-categories-parity.test.d.ts +1 -0
  32. package/dist/showcase/module-inventory-dashboard-parity.test.d.ts +1 -0
  33. package/dist/showcase/module-inventory-products-parity.test.d.ts +1 -0
  34. package/dist/showcase/module-invoice-list-parity.test.d.ts +1 -0
  35. package/dist/showcase/module-invoice-series-list-parity.test.d.ts +1 -0
  36. package/dist/showcase/module-invoice-settings-parity.test.d.ts +1 -0
  37. package/dist/showcase/module-kitchen-pages-parity.test.d.ts +1 -0
  38. package/dist/showcase/module-online-booking-pages-parity.test.d.ts +1 -0
  39. package/dist/showcase/module-pages-data.test.d.ts +1 -0
  40. package/dist/showcase/module-payment-gateways-parity.test.d.ts +1 -0
  41. package/dist/showcase/module-payments-list-parity.test.d.ts +1 -0
  42. package/dist/showcase/module-pricing-lists-parity.test.d.ts +1 -0
  43. package/dist/showcase/module-printing-pages-parity.test.d.ts +1 -0
  44. package/dist/showcase/module-reservations-availability-parity.test.d.ts +1 -0
  45. package/dist/showcase/module-reservations-list-parity.test.d.ts +1 -0
  46. package/dist/showcase/module-reservations-waitlist-parity.test.d.ts +1 -0
  47. package/dist/showcase/module-sales-list-parity.test.d.ts +1 -0
  48. package/dist/showcase/module-sales-pos-parity.test.d.ts +1 -0
  49. package/dist/showcase/module-schedules-hours-parity.test.d.ts +1 -0
  50. package/dist/showcase/module-services-parity.test.d.ts +1 -0
  51. package/dist/showcase/module-staff-members-parity.test.d.ts +1 -0
  52. package/dist/showcase/module-staff-roles-parity.test.d.ts +1 -0
  53. package/dist/showcase/module-staff-schedules-parity.test.d.ts +1 -0
  54. package/dist/showcase/module-staff-time-off-parity.test.d.ts +1 -0
  55. package/dist/showcase/module-tables-pages-parity.test.d.ts +1 -0
  56. package/dist/showcase/module-tasks-list-parity.test.d.ts +1 -0
  57. package/dist/showcase/module-tasks-projects-parity.test.d.ts +1 -0
  58. package/dist/showcase/module-taxes-pages-parity.test.d.ts +1 -0
  59. package/dist/showcase/module-tickets-list-parity.test.d.ts +1 -0
  60. package/dist/showcase/module-tickets-sla-parity.test.d.ts +1 -0
  61. package/dist/showcase/module-verifactu-pages-parity.test.d.ts +1 -0
  62. package/dist/showcase/module-whatsapp-inbox-pages-parity.test.d.ts +1 -0
  63. package/dist/showcase/page-catalog.test.d.ts +1 -0
  64. package/dist/showcase/page-navigation.test.d.ts +1 -0
  65. package/dist/showcase/saas-account-help-pages-parity.test.d.ts +1 -0
  66. package/dist/showcase/saas-auth-account-pages-parity.test.d.ts +1 -0
  67. package/dist/showcase/saas-billing-table-pages-parity.test.d.ts +1 -0
  68. package/dist/showcase/saas-dashboard-shell-parity.test.d.ts +1 -0
  69. package/dist/showcase/saas-developer-pages-parity.test.d.ts +1 -0
  70. package/dist/showcase/saas-developer-table-pages-parity.test.d.ts +1 -0
  71. package/dist/showcase/saas-hub-detail-pages-parity.test.d.ts +1 -0
  72. package/dist/showcase/saas-hubs-table-pages-parity.test.d.ts +1 -0
  73. package/dist/showcase/saas-operations-marketplace-pages-parity.test.d.ts +1 -0
  74. package/dist/showcase/saas-organizations-page-parity.test.d.ts +1 -0
  75. package/dist/showcase/saas-organizations-users-detail-pages-parity.test.d.ts +1 -0
  76. package/dist/showcase/saas-public-error-pages-parity.test.d.ts +1 -0
  77. package/package.json +2 -6
  78. package/dist/components/ok-date-picker/ok-date-picker.d.ts +0 -81
  79. package/dist/components/ok-drawer/ok-drawer.d.ts +0 -56
  80. package/dist/components/ok-range-dual/ok-range-dual.d.ts +0 -38
  81. package/dist/components/ok-skeleton/ok-skeleton.d.ts +0 -40
  82. package/dist/components/ok-time-picker/ok-time-picker.d.ts +0 -50
  83. package/dist/ok-date-picker.js +0 -737
  84. package/dist/ok-drawer.js +0 -358
  85. package/dist/ok-range-dual.js +0 -280
  86. package/dist/ok-skeleton.js +0 -345
  87. package/dist/ok-time-picker.js +0 -472
package/dist/ok-drawer.js DELETED
@@ -1,358 +0,0 @@
1
- import { LitElement, css, html } from "lit";
2
- import { property, query } from "lit/decorators.js";
3
- import { define } from "./define.js";
4
- import { o as okIcon, v as iconCloseOutline } from "./shared/icons.js";
5
- var __defProp = Object.defineProperty;
6
- var __decorateClass = (decorators, target, key, kind) => {
7
- var result = void 0;
8
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
9
- if (decorator = decorators[i])
10
- result = decorator(target, key, result) || result;
11
- if (result) __defProp(target, key, result);
12
- return result;
13
- };
14
- const DEFAULT_LABELS = {
15
- close: "Close"
16
- };
17
- const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), ion-button:not([disabled]), ion-input, ion-searchbar, [tabindex]:not([tabindex="-1"])';
18
- class OkDrawer extends LitElement {
19
- constructor() {
20
- super(...arguments);
21
- this.open = false;
22
- this.side = "end";
23
- this.width = "420px";
24
- this.scrim = true;
25
- this.dismissible = true;
26
- this.labels = {};
27
- this.previousFocus = null;
28
- this.hasFooter = false;
29
- this.onDocKeydown = (e) => {
30
- if (!this.open) return;
31
- if (e.key === "Escape") {
32
- if (this.dismissible) {
33
- e.preventDefault();
34
- this.requestClose("esc");
35
- }
36
- return;
37
- }
38
- if (e.key === "Tab") this.trapFocus(e);
39
- };
40
- }
41
- static {
42
- this.styles = css`
43
- :host {
44
- /* Vars overridable (estilo Ionic), default = cadena --ok-* → --ion-* → hex */
45
- --color: var(--ok-text, var(--ion-text-color, #1c1b17));
46
- --color-muted: var(--ok-text-muted, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.6));
47
- --panel-bg: var(--ok-drawer-bg, var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #ffffff))));
48
- --scrim-bg: var(--ok-scrim, rgba(0, 0, 0, 0.4));
49
- --border-soft: var(--ok-border-soft, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.08));
50
- --shadow: var(--ok-shadow-md, 0 10px 40px rgba(0, 0, 0, 0.22));
51
- --font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
52
-
53
- /* El host no ocupa caja: scrim y panel van a position:fixed sobre todo. */
54
- display: contents;
55
- }
56
-
57
- /* Scrim a pantalla completa (cierra al click si dismissible). */
58
- .scrim {
59
- position: fixed;
60
- inset: 0;
61
- z-index: 2000;
62
- background: var(--scrim-bg);
63
- animation: fade-in 180ms ease;
64
- }
65
-
66
- /* Panel lateral fijo: columna header · body (scroll) · footer. */
67
- .panel {
68
- position: fixed;
69
- top: 0;
70
- bottom: 0;
71
- z-index: 2001;
72
- /* Cadena: token global → prop width (inyectada como var local) → 420px. */
73
- width: var(--ok-drawer-width, var(--drawer-width, 420px));
74
- max-width: 100vw;
75
- display: flex;
76
- flex-direction: column;
77
- box-sizing: border-box;
78
- background: var(--panel-bg);
79
- box-shadow: var(--shadow);
80
- font-family: var(--font);
81
- color: var(--color);
82
- /* Lado por defecto: end (derecha en LTR). */
83
- inset-inline-end: 0;
84
- border-inline-start: 1px solid var(--border-soft);
85
- animation: slide-in-end 200ms ease;
86
- }
87
- :host([side='start']) .panel {
88
- inset-inline-end: auto;
89
- inset-inline-start: 0;
90
- border-inline-start: 0;
91
- border-inline-end: 1px solid var(--border-soft);
92
- animation: slide-in-start 200ms ease;
93
- }
94
-
95
- @keyframes fade-in {
96
- from { opacity: 0; }
97
- to { opacity: 1; }
98
- }
99
- @keyframes slide-in-end {
100
- from { transform: translateX(100%); }
101
- to { transform: translateX(0); }
102
- }
103
- @keyframes slide-in-start {
104
- from { transform: translateX(-100%); }
105
- to { transform: translateX(0); }
106
- }
107
- @media (prefers-reduced-motion: reduce) {
108
- .scrim,
109
- .panel {
110
- animation: none;
111
- }
112
- }
113
-
114
- /* Cabecera: icono + título + acciones extra + botón X. */
115
- .header {
116
- flex: 0 0 auto;
117
- display: flex;
118
- align-items: center;
119
- gap: 0.6rem;
120
- padding: 0.85rem 1rem;
121
- border-bottom: 1px solid var(--border-soft);
122
- }
123
- .header > ion-icon {
124
- flex: 0 0 auto;
125
- font-size: 1.25rem;
126
- color: var(--color-muted);
127
- }
128
- .heading {
129
- flex: 1 1 auto;
130
- min-width: 0;
131
- margin: 0;
132
- font-size: 1.05rem;
133
- font-weight: 600;
134
- overflow: hidden;
135
- text-overflow: ellipsis;
136
- white-space: nowrap;
137
- }
138
- /* Acciones extra de cabecera (slot header-actions) + botón cerrar. */
139
- .header-actions {
140
- flex: 0 0 auto;
141
- display: flex;
142
- align-items: center;
143
- gap: 0.25rem;
144
- }
145
- .close {
146
- flex: 0 0 auto;
147
- display: inline-flex;
148
- align-items: center;
149
- justify-content: center;
150
- width: 32px;
151
- height: 32px;
152
- padding: 0;
153
- border: 0;
154
- border-radius: 8px;
155
- background: none;
156
- color: var(--color-muted);
157
- font-size: 1.25rem;
158
- cursor: pointer;
159
- transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease);
160
- }
161
- @media (hover: hover) {
162
- .close:hover {
163
- background: var(--ok-hover, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.06));
164
- color: var(--color);
165
- }
166
- }
167
-
168
- /* Cuerpo: scroll interno. */
169
- .body {
170
- flex: 1 1 auto;
171
- min-height: 0;
172
- overflow-y: auto;
173
- padding: 1rem;
174
- box-sizing: border-box;
175
- }
176
-
177
- /* Pie fijo opcional (se oculta si el slot está vacío). */
178
- .footer {
179
- flex: 0 0 auto;
180
- padding: 0.75rem 1rem;
181
- border-top: 1px solid var(--border-soft);
182
- }
183
- .footer.empty {
184
- display: none;
185
- }
186
-
187
- /* En móvil el panel ocupa todo el ancho. */
188
- @media (max-width: 640px) {
189
- .panel {
190
- width: 100%;
191
- border: 0;
192
- }
193
- }
194
- `;
195
- }
196
- /** Textos efectivos (defaults inglés + overrides). */
197
- get t() {
198
- return { ...DEFAULT_LABELS, ...this.labels };
199
- }
200
- disconnectedCallback() {
201
- super.disconnectedCallback();
202
- document.removeEventListener("keydown", this.onDocKeydown);
203
- }
204
- // ── Apertura / cierre ──────────────────────────────────────────────────────
205
- updated(changed) {
206
- if (!changed.has("open")) return;
207
- const was = changed.get("open");
208
- if (this.open && !was) this.afterOpen();
209
- else if (!this.open && was) this.afterClose();
210
- }
211
- // Al abrir: guarda el foco actual, enfoca el panel y arma el teclado global.
212
- afterOpen() {
213
- this.previousFocus = document.activeElement ?? null;
214
- document.addEventListener("keydown", this.onDocKeydown);
215
- this.updateComplete.then(() => {
216
- const first = this.focusables()[0];
217
- (first ?? this.panel)?.focus();
218
- });
219
- this.dispatchEvent(
220
- new CustomEvent("ok-open", { detail: { open: true }, bubbles: true, composed: true })
221
- );
222
- }
223
- // Al cerrar: desarma el teclado y restaura el foco previo.
224
- afterClose() {
225
- document.removeEventListener("keydown", this.onDocKeydown);
226
- this.previousFocus?.focus?.();
227
- this.previousFocus = null;
228
- }
229
- /**
230
- * Gesto de cierre (scrim/esc/botón): emite `ok-close` CANCELABLE con la razón. Si nadie hace
231
- * `preventDefault()` se auto-cierra (fallback no-controlado); un padre controlado puede vetar.
232
- */
233
- requestClose(reason) {
234
- const ev = new CustomEvent("ok-close", {
235
- detail: { reason },
236
- bubbles: true,
237
- composed: true,
238
- cancelable: true
239
- });
240
- const proceed = this.dispatchEvent(ev);
241
- if (proceed) this.open = false;
242
- }
243
- /** Abre el drawer (azúcar imperativo; equivale a `open = true`). */
244
- show() {
245
- this.open = true;
246
- }
247
- /** Cierra el drawer sin pasar por el gesto cancelable (azúcar imperativo). */
248
- hide() {
249
- this.open = false;
250
- }
251
- // ── Focus trap ─────────────────────────────────────────────────────────────
252
- // Enfocables del panel (shadow) + del contenido slotted (light DOM del host).
253
- focusables() {
254
- const inShadow = this.panel ? Array.from(this.panel.querySelectorAll(FOCUSABLE)) : [];
255
- const inLight = Array.from(this.querySelectorAll(FOCUSABLE));
256
- return [...inShadow, ...inLight].filter((el) => el.offsetParent !== null || el === document.activeElement);
257
- }
258
- // Mantiene Tab/Shift+Tab ciclando dentro del drawer mientras está abierto.
259
- trapFocus(e) {
260
- const list = this.focusables();
261
- if (!list.length) {
262
- e.preventDefault();
263
- this.panel?.focus();
264
- return;
265
- }
266
- const active = this.shadowRoot?.activeElement ?? document.activeElement;
267
- const idx = active ? list.indexOf(active) : -1;
268
- if (e.shiftKey) {
269
- if (idx <= 0) {
270
- e.preventDefault();
271
- list[list.length - 1].focus();
272
- }
273
- } else if (idx === -1 || idx === list.length - 1) {
274
- e.preventDefault();
275
- list[0].focus();
276
- }
277
- }
278
- // ── Interacción ────────────────────────────────────────────────────────────
279
- // Cierra al click en el scrim (solo si dismissible).
280
- onScrimClick() {
281
- if (this.dismissible) this.requestClose("scrim");
282
- }
283
- onFooterSlotChange(e) {
284
- const slot = e.target;
285
- const has = slot.assignedNodes({ flatten: true }).length > 0;
286
- if (has !== this.hasFooter) {
287
- this.hasFooter = has;
288
- this.requestUpdate();
289
- }
290
- }
291
- // ── Render ─────────────────────────────────────────────────────────────────
292
- render() {
293
- if (!this.open) return html``;
294
- return html`
295
- ${this.scrim ? html`<div class="scrim" @click=${() => this.onScrimClick()}></div>` : ""}
296
- <aside
297
- class="panel"
298
- style="--drawer-width:${this.width}"
299
- role="dialog"
300
- aria-modal="true"
301
- aria-label=${this.heading ?? "drawer"}
302
- tabindex="-1"
303
- >
304
- <header class="header">
305
- ${this.icon ? html`<ion-icon .icon=${okIcon(this.icon)} aria-hidden="true"></ion-icon>` : ""}
306
- ${this.heading ? html`<h2 class="heading">${this.heading}</h2>` : html`<span class="heading"></span>`}
307
- <div class="header-actions">
308
- <slot name="header-actions"></slot>
309
- ${this.dismissible ? html`<button
310
- class="close"
311
- type="button"
312
- aria-label=${this.t.close}
313
- title=${this.t.close}
314
- @click=${() => this.requestClose("button")}
315
- >
316
- <ion-icon .icon=${iconCloseOutline} aria-hidden="true"></ion-icon>
317
- </button>` : ""}
318
- </div>
319
- </header>
320
- <div class="body"><slot></slot></div>
321
- <footer class=${this.hasFooter ? "footer" : "footer empty"}>
322
- <slot name="footer" @slotchange=${(e) => this.onFooterSlotChange(e)}></slot>
323
- </footer>
324
- </aside>
325
- `;
326
- }
327
- }
328
- __decorateClass([
329
- property({ type: Boolean, reflect: true })
330
- ], OkDrawer.prototype, "open");
331
- __decorateClass([
332
- property({ reflect: true })
333
- ], OkDrawer.prototype, "side");
334
- __decorateClass([
335
- property()
336
- ], OkDrawer.prototype, "width");
337
- __decorateClass([
338
- property()
339
- ], OkDrawer.prototype, "heading");
340
- __decorateClass([
341
- property()
342
- ], OkDrawer.prototype, "icon");
343
- __decorateClass([
344
- property({ type: Boolean })
345
- ], OkDrawer.prototype, "scrim");
346
- __decorateClass([
347
- property({ type: Boolean })
348
- ], OkDrawer.prototype, "dismissible");
349
- __decorateClass([
350
- property({ attribute: false })
351
- ], OkDrawer.prototype, "labels");
352
- __decorateClass([
353
- query(".panel")
354
- ], OkDrawer.prototype, "panel");
355
- define("ok-drawer", OkDrawer);
356
- export {
357
- OkDrawer
358
- };
@@ -1,280 +0,0 @@
1
- import { LitElement, css, html } from "lit";
2
- import { property } from "lit/decorators.js";
3
- import { define } from "./define.js";
4
- var __defProp = Object.defineProperty;
5
- var __decorateClass = (decorators, target, key, kind) => {
6
- var result = void 0;
7
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
8
- if (decorator = decorators[i])
9
- result = decorator(target, key, result) || result;
10
- if (result) __defProp(target, key, result);
11
- return result;
12
- };
13
- class OkRangeDual extends LitElement {
14
- constructor() {
15
- super(...arguments);
16
- this.min = 0;
17
- this.max = 100;
18
- this.step = 1;
19
- this.low = 0;
20
- this.high = 100;
21
- this.prefix = "";
22
- this.suffix = "";
23
- this.disabled = false;
24
- }
25
- static {
26
- this.styles = css`
27
- :host {
28
- display: block;
29
- width: 100%;
30
- box-sizing: border-box;
31
- /* Tokens propios estilo Ionic (overridables): --ok-* → --ion-* → hex. */
32
- --track-color: var(--ok-range-track, var(--ion-color-step-150, #e0e2e6));
33
- --fill-color: var(--ok-range-fill, var(--ion-color-primary, #3880ff));
34
- --thumb-bg: var(--ok-range-thumb-bg, var(--ion-background-color, #ffffff));
35
- --thumb-border: var(--ok-range-thumb-border, var(--ion-color-primary, #3880ff));
36
- --label-color: var(--ok-color-medium, var(--ion-color-medium, #92949c));
37
- --value-color: var(--ok-text-color, var(--ion-text-color, #1f2933));
38
- --value-bg: var(--ok-range-value-bg, var(--ion-color-step-100, #f1f2f4));
39
- --focus-ring: var(--ok-range-focus, var(--ion-color-primary-tint, #4c8dff));
40
- }
41
-
42
- .block {
43
- display: grid;
44
- gap: 6px;
45
- width: 100%;
46
- }
47
-
48
- .head {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: baseline;
52
- font-size: 0.8125rem;
53
- }
54
-
55
- .label {
56
- color: var(--label-color);
57
- font-weight: 500;
58
- }
59
-
60
- .readout {
61
- color: var(--value-color);
62
- font-variant-numeric: tabular-nums;
63
- background: var(--value-bg);
64
- padding: 1px 8px;
65
- border-radius: 6px;
66
- font-size: 12px;
67
- white-space: nowrap;
68
- }
69
-
70
- /* Pista + thumbs apilados */
71
- .range {
72
- position: relative;
73
- height: 24px;
74
- }
75
-
76
- .track {
77
- position: absolute;
78
- top: 50%;
79
- left: 0;
80
- right: 0;
81
- height: 4px;
82
- transform: translateY(-50%);
83
- background: var(--track-color);
84
- border-radius: 999px;
85
- pointer-events: none;
86
- }
87
-
88
- .fill {
89
- position: absolute;
90
- top: 50%;
91
- transform: translateY(-50%);
92
- height: 4px;
93
- border-radius: 999px;
94
- background: var(--fill-color);
95
- pointer-events: none;
96
- }
97
-
98
- /* Ambos inputs ocupan toda la pista; solo el thumb captura el puntero */
99
- input[type='range'] {
100
- position: absolute;
101
- inset: 0;
102
- width: 100%;
103
- height: 100%;
104
- appearance: none;
105
- -webkit-appearance: none;
106
- background: transparent;
107
- pointer-events: none;
108
- margin: 0;
109
- }
110
-
111
- input[type='range']::-webkit-slider-thumb {
112
- -webkit-appearance: none;
113
- appearance: none;
114
- width: 16px;
115
- height: 16px;
116
- border-radius: 50%;
117
- background: var(--thumb-bg);
118
- border: 2px solid var(--thumb-border);
119
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
120
- pointer-events: auto;
121
- cursor: grab;
122
- transition: transform 0.12s ease;
123
- }
124
- input[type='range']::-webkit-slider-thumb:hover {
125
- transform: scale(1.15);
126
- }
127
- input[type='range']:active::-webkit-slider-thumb {
128
- cursor: grabbing;
129
- }
130
-
131
- input[type='range']::-moz-range-thumb {
132
- width: 14px;
133
- height: 14px;
134
- border-radius: 50%;
135
- background: var(--thumb-bg);
136
- border: 2px solid var(--thumb-border);
137
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
138
- pointer-events: auto;
139
- cursor: grab;
140
- }
141
-
142
- input[type='range']::-webkit-slider-runnable-track {
143
- background: transparent;
144
- height: 100%;
145
- }
146
- input[type='range']::-moz-range-track {
147
- background: transparent;
148
- height: 100%;
149
- }
150
-
151
- input[type='range']:focus-visible::-webkit-slider-thumb {
152
- outline: 2px solid var(--focus-ring);
153
- outline-offset: 3px;
154
- }
155
- input[type='range']:focus-visible::-moz-range-thumb {
156
- outline: 2px solid var(--focus-ring);
157
- outline-offset: 3px;
158
- }
159
-
160
- :host([disabled]) {
161
- opacity: 0.55;
162
- pointer-events: none;
163
- }
164
- `;
165
- }
166
- // Clampa low<=high respetando min/max.
167
- clamp() {
168
- const lo = Math.min(Math.max(this.low, this.min), this.max);
169
- const hi = Math.min(Math.max(this.high, this.min), this.max);
170
- return { low: Math.min(lo, hi), high: Math.max(lo, hi) };
171
- }
172
- onLow(e) {
173
- const v = Number(e.target.value);
174
- this.low = Math.min(v, this.high);
175
- this.high = Math.max(this.low, this.high);
176
- this.emit();
177
- }
178
- onHigh(e) {
179
- const v = Number(e.target.value);
180
- this.high = Math.max(v, this.low);
181
- this.low = Math.min(this.low, this.high);
182
- this.emit();
183
- }
184
- emit() {
185
- const { low, high } = this.clamp();
186
- this.dispatchEvent(
187
- new CustomEvent("ok-change", {
188
- detail: { low, high },
189
- bubbles: true,
190
- composed: true
191
- })
192
- );
193
- }
194
- // Formatea un valor con prefijo/sufijo.
195
- fmt(v) {
196
- return `${this.prefix}${v}${this.suffix}`;
197
- }
198
- render() {
199
- const { low, high } = this.clamp();
200
- const span = this.max - this.min || 1;
201
- const leftPct = (low - this.min) / span * 100;
202
- const rightPct = (this.max - high) / span * 100;
203
- return html`
204
- <div class="block">
205
- <div class="head">
206
- ${this.label ? html`<span class="label">${this.label}</span>` : html`<span></span>`}
207
- <span class="readout" aria-live="polite">${this.fmt(low)} – ${this.fmt(high)}</span>
208
- </div>
209
- <div class="range">
210
- <div class="track"></div>
211
- <div
212
- class="fill"
213
- style="left:${leftPct}%; right:${rightPct}%"
214
- ></div>
215
- <input
216
- type="range"
217
- .min=${String(this.min)}
218
- .max=${String(this.max)}
219
- .step=${String(this.step)}
220
- .value=${String(low)}
221
- ?disabled=${this.disabled}
222
- role="slider"
223
- aria-label=${(this.label ? this.label + " " : "") + "mínimo"}
224
- aria-valuemin=${this.min}
225
- aria-valuemax=${this.max}
226
- aria-valuenow=${low}
227
- aria-valuetext=${this.fmt(low)}
228
- @input=${this.onLow}
229
- />
230
- <input
231
- type="range"
232
- .min=${String(this.min)}
233
- .max=${String(this.max)}
234
- .step=${String(this.step)}
235
- .value=${String(high)}
236
- ?disabled=${this.disabled}
237
- role="slider"
238
- aria-label=${(this.label ? this.label + " " : "") + "máximo"}
239
- aria-valuemin=${this.min}
240
- aria-valuemax=${this.max}
241
- aria-valuenow=${high}
242
- aria-valuetext=${this.fmt(high)}
243
- @input=${this.onHigh}
244
- />
245
- </div>
246
- </div>
247
- `;
248
- }
249
- }
250
- __decorateClass([
251
- property()
252
- ], OkRangeDual.prototype, "label");
253
- __decorateClass([
254
- property({ type: Number })
255
- ], OkRangeDual.prototype, "min");
256
- __decorateClass([
257
- property({ type: Number })
258
- ], OkRangeDual.prototype, "max");
259
- __decorateClass([
260
- property({ type: Number })
261
- ], OkRangeDual.prototype, "step");
262
- __decorateClass([
263
- property({ type: Number })
264
- ], OkRangeDual.prototype, "low");
265
- __decorateClass([
266
- property({ type: Number })
267
- ], OkRangeDual.prototype, "high");
268
- __decorateClass([
269
- property()
270
- ], OkRangeDual.prototype, "prefix");
271
- __decorateClass([
272
- property()
273
- ], OkRangeDual.prototype, "suffix");
274
- __decorateClass([
275
- property({ type: Boolean, reflect: true })
276
- ], OkRangeDual.prototype, "disabled");
277
- define("ok-range-dual", OkRangeDual);
278
- export {
279
- OkRangeDual
280
- };