@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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +457 -0
  3. package/dist/base/anchor.d.ts +13 -0
  4. package/dist/base/define.d.ts +1 -0
  5. package/dist/base/relay.d.ts +1 -0
  6. package/dist/cdn.d.ts +96 -0
  7. package/dist/components/ok-app-launcher/ok-app-launcher.d.ts +57 -0
  8. package/dist/components/ok-audio/ok-audio.d.ts +45 -0
  9. package/dist/components/ok-avatar/ok-avatar.d.ts +36 -0
  10. package/dist/components/ok-avatar-group/ok-avatar-group.d.ts +38 -0
  11. package/dist/components/ok-bar-list/ok-bar-list.d.ts +36 -0
  12. package/dist/components/ok-bento/ok-bento.d.ts +17 -0
  13. package/dist/components/ok-bento-item/ok-bento-item.d.ts +34 -0
  14. package/dist/components/ok-calculator/ok-calculator.d.ts +46 -0
  15. package/dist/components/ok-calendar/ok-calendar.d.ts +63 -0
  16. package/dist/components/ok-carousel/ok-carousel.d.ts +48 -0
  17. package/dist/components/ok-chart/ok-chart.d.ts +55 -0
  18. package/dist/components/ok-chat/ok-chat.d.ts +54 -0
  19. package/dist/components/ok-coachmark/ok-coachmark.d.ts +69 -0
  20. package/dist/components/ok-code/ok-code.d.ts +28 -0
  21. package/dist/components/ok-color-picker/ok-color-picker.d.ts +63 -0
  22. package/dist/components/ok-combo/ok-combo.d.ts +46 -0
  23. package/dist/components/ok-command-palette/ok-command-palette.d.ts +72 -0
  24. package/dist/components/ok-contact-form/ok-contact-form.d.ts +54 -0
  25. package/dist/components/ok-cropper/ok-cropper.d.ts +60 -0
  26. package/dist/components/ok-cta-band/ok-cta-band.d.ts +18 -0
  27. package/dist/components/ok-currency/ok-currency.d.ts +31 -0
  28. package/dist/components/ok-data-table/ok-data-table.d.ts +312 -0
  29. package/dist/components/ok-date-picker/ok-date-picker.d.ts +81 -0
  30. package/dist/components/ok-detail-list/ok-detail-list.d.ts +30 -0
  31. package/dist/components/ok-diff/ok-diff.d.ts +38 -0
  32. package/dist/components/ok-donut/ok-donut.d.ts +38 -0
  33. package/dist/components/ok-drawer/ok-drawer.d.ts +56 -0
  34. package/dist/components/ok-dropzone/ok-dropzone.d.ts +48 -0
  35. package/dist/components/ok-empty-state/ok-empty-state.d.ts +16 -0
  36. package/dist/components/ok-error-page/ok-error-page.d.ts +77 -0
  37. package/dist/components/ok-event-card/ok-event-card.d.ts +56 -0
  38. package/dist/components/ok-feature-card/ok-feature-card.d.ts +23 -0
  39. package/dist/components/ok-file-item/ok-file-item.d.ts +31 -0
  40. package/dist/components/ok-file-manager/ok-file-manager.d.ts +145 -0
  41. package/dist/components/ok-footer/ok-footer.d.ts +10 -0
  42. package/dist/components/ok-funnel/ok-funnel.d.ts +31 -0
  43. package/dist/components/ok-gallery/ok-gallery.d.ts +34 -0
  44. package/dist/components/ok-gauge/ok-gauge.d.ts +49 -0
  45. package/dist/components/ok-heatmap/ok-heatmap.d.ts +45 -0
  46. package/dist/components/ok-hero/ok-hero.d.ts +10 -0
  47. package/dist/components/ok-hover-card/ok-hover-card.d.ts +76 -0
  48. package/dist/components/ok-icon-tile/ok-icon-tile.d.ts +24 -0
  49. package/dist/components/ok-image/ok-image.d.ts +56 -0
  50. package/dist/components/ok-inline-feedback/ok-inline-feedback.d.ts +33 -0
  51. package/dist/components/ok-invoice/ok-invoice.d.ts +137 -0
  52. package/dist/components/ok-json-viewer/ok-json-viewer.d.ts +31 -0
  53. package/dist/components/ok-kanban/ok-kanban.d.ts +56 -0
  54. package/dist/components/ok-kbd/ok-kbd.d.ts +21 -0
  55. package/dist/components/ok-keyboard/ok-keyboard.d.ts +35 -0
  56. package/dist/components/ok-kpi/ok-kpi.d.ts +24 -0
  57. package/dist/components/ok-language-select/ok-language-select.d.ts +31 -0
  58. package/dist/components/ok-lightbox/ok-lightbox.d.ts +59 -0
  59. package/dist/components/ok-logo-cloud/ok-logo-cloud.d.ts +14 -0
  60. package/dist/components/ok-loyalty-card/ok-loyalty-card.d.ts +35 -0
  61. package/dist/components/ok-mail/ok-mail.d.ts +117 -0
  62. package/dist/components/ok-menu/ok-menu.d.ts +75 -0
  63. package/dist/components/ok-menubar/ok-menubar.d.ts +75 -0
  64. package/dist/components/ok-navbar/ok-navbar.d.ts +42 -0
  65. package/dist/components/ok-notification-center/ok-notification-center.d.ts +79 -0
  66. package/dist/components/ok-org-chart/ok-org-chart.d.ts +67 -0
  67. package/dist/components/ok-otp/ok-otp.d.ts +31 -0
  68. package/dist/components/ok-page-header/ok-page-header.d.ts +23 -0
  69. package/dist/components/ok-pagination/ok-pagination.d.ts +44 -0
  70. package/dist/components/ok-pdf/ok-pdf.d.ts +32 -0
  71. package/dist/components/ok-phone/ok-phone.d.ts +48 -0
  72. package/dist/components/ok-pinpad/ok-pinpad.d.ts +29 -0
  73. package/dist/components/ok-pricing-card/ok-pricing-card.d.ts +31 -0
  74. package/dist/components/ok-product-card/ok-product-card.d.ts +25 -0
  75. package/dist/components/ok-qr/ok-qr.d.ts +24 -0
  76. package/dist/components/ok-qty-stepper/ok-qty-stepper.d.ts +35 -0
  77. package/dist/components/ok-range-dual/ok-range-dual.d.ts +38 -0
  78. package/dist/components/ok-rating/ok-rating.d.ts +33 -0
  79. package/dist/components/ok-receipt/ok-receipt.d.ts +103 -0
  80. package/dist/components/ok-reveal/ok-reveal.d.ts +21 -0
  81. package/dist/components/ok-rich-text/ok-rich-text.d.ts +46 -0
  82. package/dist/components/ok-scheduler/ok-scheduler.d.ts +74 -0
  83. package/dist/components/ok-select-card/ok-select-card.d.ts +37 -0
  84. package/dist/components/ok-signature/ok-signature.d.ts +55 -0
  85. package/dist/components/ok-skeleton/ok-skeleton.d.ts +40 -0
  86. package/dist/components/ok-sparkline/ok-sparkline.d.ts +27 -0
  87. package/dist/components/ok-split-button/ok-split-button.d.ts +49 -0
  88. package/dist/components/ok-splitter/ok-splitter.d.ts +36 -0
  89. package/dist/components/ok-stat/ok-stat.d.ts +16 -0
  90. package/dist/components/ok-status-dot/ok-status-dot.d.ts +24 -0
  91. package/dist/components/ok-status-pill/ok-status-pill.d.ts +22 -0
  92. package/dist/components/ok-stepper/ok-stepper.d.ts +33 -0
  93. package/dist/components/ok-store/ok-store.d.ts +33 -0
  94. package/dist/components/ok-tag-input/ok-tag-input.d.ts +39 -0
  95. package/dist/components/ok-testimonial/ok-testimonial.d.ts +21 -0
  96. package/dist/components/ok-time-picker/ok-time-picker.d.ts +50 -0
  97. package/dist/components/ok-timeline/ok-timeline.d.ts +33 -0
  98. package/dist/components/ok-tree/ok-tree.d.ts +46 -0
  99. package/dist/components/ok-video/ok-video.d.ts +49 -0
  100. package/dist/components/ok-widget-board/ok-widget-board.d.ts +71 -0
  101. package/dist/components/ok-wizard/ok-wizard.d.ts +30 -0
  102. package/dist/define.js +8 -0
  103. package/dist/erplora.css +112 -0
  104. package/dist/index.d.ts +158 -0
  105. package/dist/index.js +197 -0
  106. package/dist/layout.css +338 -0
  107. package/dist/ok-app-launcher.js +396 -0
  108. package/dist/ok-audio.js +308 -0
  109. package/dist/ok-avatar-group.js +158 -0
  110. package/dist/ok-avatar.js +179 -0
  111. package/dist/ok-bar-list.js +189 -0
  112. package/dist/ok-bento-item.js +168 -0
  113. package/dist/ok-bento.js +63 -0
  114. package/dist/ok-calculator.js +406 -0
  115. package/dist/ok-calendar.js +541 -0
  116. package/dist/ok-carousel.js +352 -0
  117. package/dist/ok-chart.js +325 -0
  118. package/dist/ok-chat.js +320 -0
  119. package/dist/ok-coachmark.js +500 -0
  120. package/dist/ok-code.js +190 -0
  121. package/dist/ok-color-picker.js +569 -0
  122. package/dist/ok-combo.js +294 -0
  123. package/dist/ok-command-palette.js +448 -0
  124. package/dist/ok-contact-form.js +288 -0
  125. package/dist/ok-cropper.js +404 -0
  126. package/dist/ok-cta-band.js +134 -0
  127. package/dist/ok-currency.js +172 -0
  128. package/dist/ok-data-table.js +1281 -0
  129. package/dist/ok-date-picker.js +736 -0
  130. package/dist/ok-detail-list.js +156 -0
  131. package/dist/ok-diff.js +200 -0
  132. package/dist/ok-donut.js +280 -0
  133. package/dist/ok-drawer.js +357 -0
  134. package/dist/ok-dropzone.js +376 -0
  135. package/dist/ok-empty-state.js +104 -0
  136. package/dist/ok-error-page.js +547 -0
  137. package/dist/ok-event-card.js +384 -0
  138. package/dist/ok-feature-card.js +152 -0
  139. package/dist/ok-file-item.js +259 -0
  140. package/dist/ok-file-manager.js +1116 -0
  141. package/dist/ok-footer.js +67 -0
  142. package/dist/ok-funnel.js +181 -0
  143. package/dist/ok-gallery.js +293 -0
  144. package/dist/ok-gauge.js +385 -0
  145. package/dist/ok-heatmap.js +268 -0
  146. package/dist/ok-hero.js +43 -0
  147. package/dist/ok-hover-card.js +480 -0
  148. package/dist/ok-icon-tile.js +123 -0
  149. package/dist/ok-image.js +471 -0
  150. package/dist/ok-inline-feedback.js +221 -0
  151. package/dist/ok-invoice.js +229 -0
  152. package/dist/ok-json-viewer.js +330 -0
  153. package/dist/ok-kanban.js +427 -0
  154. package/dist/ok-kbd.js +159 -0
  155. package/dist/ok-keyboard.js +402 -0
  156. package/dist/ok-kpi.js +147 -0
  157. package/dist/ok-language-select.js +188 -0
  158. package/dist/ok-lightbox.js +490 -0
  159. package/dist/ok-logo-cloud.js +92 -0
  160. package/dist/ok-loyalty-card.js +353 -0
  161. package/dist/ok-mail.js +562 -0
  162. package/dist/ok-menu.js +529 -0
  163. package/dist/ok-menubar.js +628 -0
  164. package/dist/ok-navbar.js +306 -0
  165. package/dist/ok-notification-center.js +545 -0
  166. package/dist/ok-org-chart.js +619 -0
  167. package/dist/ok-otp.js +199 -0
  168. package/dist/ok-page-header.js +202 -0
  169. package/dist/ok-pagination.js +366 -0
  170. package/dist/ok-pdf.js +160 -0
  171. package/dist/ok-phone.js +225 -0
  172. package/dist/ok-pinpad.js +171 -0
  173. package/dist/ok-pricing-card.js +184 -0
  174. package/dist/ok-product-card.js +178 -0
  175. package/dist/ok-qr.js +652 -0
  176. package/dist/ok-qty-stepper.js +212 -0
  177. package/dist/ok-range-dual.js +280 -0
  178. package/dist/ok-rating.js +199 -0
  179. package/dist/ok-receipt.js +183 -0
  180. package/dist/ok-reveal.js +94 -0
  181. package/dist/ok-rich-text.js +538 -0
  182. package/dist/ok-scheduler.js +518 -0
  183. package/dist/ok-select-card.js +231 -0
  184. package/dist/ok-signature.js +267 -0
  185. package/dist/ok-skeleton.js +345 -0
  186. package/dist/ok-sparkline.js +150 -0
  187. package/dist/ok-split-button.js +251 -0
  188. package/dist/ok-splitter.js +289 -0
  189. package/dist/ok-stat.js +77 -0
  190. package/dist/ok-status-dot.js +163 -0
  191. package/dist/ok-status-pill.js +123 -0
  192. package/dist/ok-stepper.js +299 -0
  193. package/dist/ok-store.js +83 -0
  194. package/dist/ok-tag-input.js +358 -0
  195. package/dist/ok-testimonial.js +136 -0
  196. package/dist/ok-time-picker.js +472 -0
  197. package/dist/ok-timeline.js +251 -0
  198. package/dist/ok-tree.js +266 -0
  199. package/dist/ok-video.js +362 -0
  200. package/dist/ok-widget-board.js +265 -0
  201. package/dist/ok-wizard.js +153 -0
  202. package/dist/outfitkit.js +96 -0
  203. package/dist/shared/anchor.js +14 -0
  204. package/dist/store/controller.d.ts +17 -0
  205. package/dist/store/idb.d.ts +16 -0
  206. package/dist/store/store.d.ts +39 -0
  207. package/dist/store-controller.js +31 -0
  208. package/dist/store.js +182 -0
  209. package/dist/theme.example.css +70 -0
  210. package/package.json +147 -0
@@ -0,0 +1,396 @@
1
+ import { LitElement, css, render, nothing, html } from "lit";
2
+ import { property, state } 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
+ const DEFAULT_LABELS = {
14
+ apps: "Apps",
15
+ empty: "No apps",
16
+ close: "Close"
17
+ };
18
+ class OkAppLauncher extends LitElement {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.apps = [];
22
+ this.labels = {};
23
+ this.open = false;
24
+ this.shown = false;
25
+ this.onKeydown = (e) => {
26
+ if (e.key === "Escape") this.close();
27
+ };
28
+ this.portalRoot = null;
29
+ }
30
+ static {
31
+ this.styles = css`
32
+ :host {
33
+ /* Vars overridable (estilo Ionic), default = cadena --ok-* → --ion-* → hex */
34
+ --color: var(--ok-text, var(--ion-text-color, #1c1b17));
35
+ --color-muted: var(--ok-text-muted, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.6));
36
+ --primary-color: var(--ok-primary, var(--ion-color-primary, #3880ff));
37
+ --primary-contrast: var(--ok-primary-contrast, var(--ion-color-primary-contrast, #ffffff));
38
+ --hover-bg: var(--ok-hover, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.06));
39
+ --panel-bg: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #ffffff)));
40
+ --border-color: var(--ok-border, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.12));
41
+ --border-radius: var(--ok-radius, 12px);
42
+ --shadow: var(--ok-shadow, 0 -8px 28px rgba(0, 0, 0, 0.18));
43
+ --font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
44
+ --scrim: var(--ok-scrim, rgba(0, 0, 0, 0.4));
45
+
46
+ /* INLINE: solo lo que mide el botón. */
47
+ display: inline-block;
48
+ position: relative;
49
+ color: var(--color);
50
+ font-family: var(--font);
51
+ }
52
+ /* Disparador: botón con icono de rejilla. */
53
+ .trigger {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ width: 40px;
58
+ height: 40px;
59
+ padding: 0;
60
+ border: 0;
61
+ background: none;
62
+ color: inherit;
63
+ cursor: pointer;
64
+ border-radius: 50%;
65
+ transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease),
66
+ border-color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease),
67
+ transform 120ms ease;
68
+ }
69
+ @media (hover: hover) {
70
+ .trigger:hover {
71
+ background: var(--hover-bg);
72
+ }
73
+ }
74
+ .trigger:active {
75
+ transform: scale(var(--ok-press-scale, 0.97));
76
+ }
77
+ .trigger ion-icon {
78
+ font-size: 1.4rem;
79
+ }
80
+
81
+ /* Scrim a pantalla completa tras la hoja. */
82
+ .scrim {
83
+ position: fixed;
84
+ inset: 0;
85
+ z-index: 1000;
86
+ background: var(--scrim);
87
+ opacity: 0;
88
+ transition: opacity var(--ok-transition, 200ms ease);
89
+ }
90
+ .scrim.shown {
91
+ opacity: 1;
92
+ }
93
+
94
+ /* Hoja de acción: anclada abajo, full-width, esquinas superiores redondeadas, desliza hacia
95
+ arriba. position:fixed para cubrir el viewport con independencia de los ancestros. */
96
+ .sheet {
97
+ position: fixed;
98
+ left: 0;
99
+ right: 0;
100
+ bottom: 0;
101
+ z-index: 1001;
102
+ max-height: 85vh;
103
+ overflow-y: auto;
104
+ padding: 0.5rem 1rem calc(1rem + env(safe-area-inset-bottom, 0px));
105
+ background: var(--panel-bg);
106
+ border-top-left-radius: var(--ok-sheet-radius, 16px);
107
+ border-top-right-radius: var(--ok-sheet-radius, 16px);
108
+ box-shadow: var(--shadow);
109
+ box-sizing: border-box;
110
+ transform: translateY(100%);
111
+ transition: transform var(--ok-transition, 260ms cubic-bezier(0.32, 0.72, 0, 1));
112
+ }
113
+ .sheet.shown {
114
+ transform: translateY(0);
115
+ }
116
+ @media (prefers-reduced-motion: reduce) {
117
+ .scrim,
118
+ .sheet {
119
+ transition: none;
120
+ }
121
+ }
122
+ /* Barra/agarre superior, decorativa (estilo sheet modal de Ionic). */
123
+ .handle {
124
+ width: 36px;
125
+ height: 5px;
126
+ margin: 0.25rem auto 0.5rem;
127
+ border-radius: 999px;
128
+ background: var(--border-color);
129
+ }
130
+ /* Cabecera de la hoja: título + botón cerrar. */
131
+ .head {
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: space-between;
135
+ gap: 0.5rem;
136
+ padding: 0 0.25rem 0.5rem;
137
+ }
138
+ .head .title {
139
+ font-size: 0.95rem;
140
+ font-weight: 600;
141
+ color: var(--color);
142
+ }
143
+ .close-btn {
144
+ display: inline-flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ width: 34px;
148
+ height: 34px;
149
+ padding: 0;
150
+ border: 0;
151
+ background: none;
152
+ color: var(--color-muted);
153
+ cursor: pointer;
154
+ border-radius: 50%;
155
+ transition: background-color var(--ok-transition, 150ms ease);
156
+ }
157
+ @media (hover: hover) {
158
+ .close-btn:hover {
159
+ background: var(--hover-bg);
160
+ }
161
+ }
162
+ .close-btn ion-icon {
163
+ font-size: 1.3rem;
164
+ }
165
+
166
+ /* Rejilla de apps: auto-fit en función del ancho (más columnas en pantallas anchas). */
167
+ .grid {
168
+ display: grid;
169
+ grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
170
+ gap: 0.5rem;
171
+ padding-bottom: 0.25rem;
172
+ }
173
+ .app {
174
+ display: flex;
175
+ flex-direction: column;
176
+ align-items: center;
177
+ gap: 0.4rem;
178
+ padding: 0.75rem 0.3rem;
179
+ border: 0;
180
+ background: none;
181
+ color: inherit;
182
+ font: inherit;
183
+ cursor: pointer;
184
+ border-radius: 12px;
185
+ text-align: center;
186
+ text-decoration: none;
187
+ transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease),
188
+ border-color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease),
189
+ transform 120ms ease;
190
+ }
191
+ @media (hover: hover) {
192
+ .app:hover {
193
+ background: var(--hover-bg);
194
+ transform: translateY(-1px);
195
+ }
196
+ }
197
+ .app:active {
198
+ transform: scale(var(--ok-press-scale, 0.97));
199
+ }
200
+ @media (prefers-reduced-motion: reduce) {
201
+ .trigger:hover,
202
+ .trigger:active,
203
+ .app:hover,
204
+ .app:active {
205
+ transform: none;
206
+ }
207
+ }
208
+ /* Cuadro del icono grande (color de la app o primary por defecto). */
209
+ .app .box {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ width: 52px;
214
+ height: 52px;
215
+ border-radius: 14px;
216
+ background: var(--app-color, var(--primary-color));
217
+ color: var(--primary-contrast);
218
+ }
219
+ .app .box ion-icon {
220
+ font-size: 1.7rem;
221
+ }
222
+ .app .label {
223
+ font-size: 0.78rem;
224
+ line-height: 1.2;
225
+ color: var(--color-muted);
226
+ max-width: 100%;
227
+ overflow: hidden;
228
+ text-overflow: ellipsis;
229
+ white-space: nowrap;
230
+ }
231
+ .empty {
232
+ padding: 1.5rem 0.75rem;
233
+ font-size: 0.85rem;
234
+ color: var(--color-muted);
235
+ text-align: center;
236
+ }
237
+ `;
238
+ }
239
+ // Textos efectivos: defaults en inglés + overrides del consumidor.
240
+ get t() {
241
+ return { ...DEFAULT_LABELS, ...this.labels };
242
+ }
243
+ disconnectedCallback() {
244
+ super.disconnectedCallback();
245
+ this.unbind();
246
+ const host = this.portalRoot?.host;
247
+ this.portalRoot = null;
248
+ if (host && host.parentNode) host.parentNode.removeChild(host);
249
+ }
250
+ // Crea (una vez) el portal: un div en `document.body` con shadow propio que ADOPTA la misma hoja
251
+ // de estilos del componente, de modo que `.scrim`/`.sheet`/`.app` se ven idénticos fuera del host.
252
+ ensurePortal() {
253
+ if (this.portalRoot) return this.portalRoot;
254
+ const host = document.createElement("div");
255
+ host.setAttribute("data-ok-app-launcher-portal", "");
256
+ document.body.appendChild(host);
257
+ const root = host.attachShadow({ mode: "open" });
258
+ const styles = this.constructor.elementStyles ?? [];
259
+ root.adoptedStyleSheets = styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet).filter((s) => !!s);
260
+ this.portalRoot = root;
261
+ return root;
262
+ }
263
+ // Renderiza el overlay (o nada) en el portal. Se llama tras cada update del host. No crea el
264
+ // portal hasta la primera apertura (evita divs vacíos en el body por cada launcher montado).
265
+ updated() {
266
+ if (!this.open && !this.portalRoot) return;
267
+ render(this.open ? this.overlayTemplate() : nothing, this.ensurePortal());
268
+ }
269
+ bind() {
270
+ document.addEventListener("keydown", this.onKeydown);
271
+ }
272
+ unbind() {
273
+ document.removeEventListener("keydown", this.onKeydown);
274
+ }
275
+ toggle() {
276
+ this.open ? this.close() : this.openSheet();
277
+ }
278
+ openSheet() {
279
+ if (this.open) return;
280
+ this.open = true;
281
+ this.bind();
282
+ requestAnimationFrame(() => requestAnimationFrame(() => this.shown = true));
283
+ this.dispatchEvent(
284
+ new CustomEvent("ok-open", { detail: { open: true }, bubbles: true, composed: true })
285
+ );
286
+ }
287
+ close() {
288
+ if (!this.open) return;
289
+ this.unbind();
290
+ this.shown = false;
291
+ const sheet = this.portalRoot?.querySelector(".sheet");
292
+ const finish = () => {
293
+ this.open = false;
294
+ };
295
+ if (sheet && !this.prefersReducedMotion()) {
296
+ sheet.addEventListener("transitionend", finish, { once: true });
297
+ } else {
298
+ finish();
299
+ }
300
+ this.dispatchEvent(
301
+ new CustomEvent("ok-open", { detail: { open: false }, bubbles: true, composed: true })
302
+ );
303
+ }
304
+ prefersReducedMotion() {
305
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
306
+ }
307
+ // Selecciona una app: si tiene `href` navega; si no, emite `ok-app-select`. Siempre cierra.
308
+ selectApp(app) {
309
+ if (app.href) {
310
+ this.close();
311
+ window.location.href = app.href;
312
+ return;
313
+ }
314
+ this.dispatchEvent(
315
+ new CustomEvent("ok-app-select", {
316
+ detail: { id: app.id, app },
317
+ bubbles: true,
318
+ composed: true
319
+ })
320
+ );
321
+ this.close();
322
+ }
323
+ // Un icono es «SVG ya resuelto» si es un string `<svg…>` o un `data:` URI; entonces va a la
324
+ // prop `icon` de ion-icon (no a `name`), que es como los hosts con iconos horneados/offline
325
+ // los pintan sin tocar la red.
326
+ isResolvedSvg(icon) {
327
+ return /^\s*</.test(icon) || icon.startsWith("data:");
328
+ }
329
+ renderApp(app) {
330
+ const boxStyle = app.color ? `--app-color:${app.color}` : "";
331
+ const icon = app.icon ?? "apps-outline";
332
+ return html`<button
333
+ type="button"
334
+ class="app"
335
+ style=${boxStyle}
336
+ role="menuitem"
337
+ @click=${() => this.selectApp(app)}
338
+ >
339
+ <span class="box">
340
+ ${this.isResolvedSvg(icon) ? html`<ion-icon .icon=${icon}></ion-icon>` : html`<ion-icon .name=${icon}></ion-icon>`}
341
+ </span>
342
+ <span class="label">${app.label}</span>
343
+ </button>`;
344
+ }
345
+ render() {
346
+ return html`
347
+ <button
348
+ type="button"
349
+ class="trigger"
350
+ aria-haspopup="menu"
351
+ aria-expanded=${this.open ? "true" : "false"}
352
+ aria-label=${this.t.apps}
353
+ @click=${() => this.toggle()}
354
+ >
355
+ <ion-icon name="apps-outline"></ion-icon>
356
+ </button>
357
+ `;
358
+ }
359
+ // Overlay (scrim + hoja inferior). Se renderiza en el portal de `document.body` vía `updated()`.
360
+ overlayTemplate() {
361
+ return html`
362
+ <div class="scrim ${this.shown ? "shown" : ""}" @click=${() => this.close()}></div>
363
+ <div class="sheet ${this.shown ? "shown" : ""}" role="menu" aria-label=${this.t.apps}>
364
+ <div class="handle"></div>
365
+ <div class="head">
366
+ <span class="title">${this.t.apps}</span>
367
+ <button
368
+ type="button"
369
+ class="close-btn"
370
+ aria-label=${this.t.close}
371
+ @click=${() => this.close()}
372
+ >
373
+ <ion-icon name="close-outline"></ion-icon>
374
+ </button>
375
+ </div>
376
+ ${this.apps.length ? html`<div class="grid">${this.apps.map((app) => this.renderApp(app))}</div>` : html`<div class="empty">${this.t.empty}</div>`}
377
+ </div>
378
+ `;
379
+ }
380
+ }
381
+ __decorateClass([
382
+ property({ attribute: false })
383
+ ], OkAppLauncher.prototype, "apps");
384
+ __decorateClass([
385
+ property({ attribute: false })
386
+ ], OkAppLauncher.prototype, "labels");
387
+ __decorateClass([
388
+ state()
389
+ ], OkAppLauncher.prototype, "open");
390
+ __decorateClass([
391
+ state()
392
+ ], OkAppLauncher.prototype, "shown");
393
+ define("ok-app-launcher", OkAppLauncher);
394
+ export {
395
+ OkAppLauncher
396
+ };
@@ -0,0 +1,308 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { property, state, query } 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
+ const DEFAULT_LABELS = {
14
+ play: "Play",
15
+ pause: "Pause",
16
+ mute: "Mute",
17
+ unmute: "Unmute",
18
+ volume: "Volume"
19
+ };
20
+ class OkAudio extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.src = "";
24
+ this.title = "";
25
+ this.labels = {};
26
+ this.playing = false;
27
+ this.current = 0;
28
+ this.duration = 0;
29
+ this.volume = 1;
30
+ this.muted = false;
31
+ }
32
+ static {
33
+ this.styles = css`
34
+ :host {
35
+ /* Vars overridable (estilo Ionic), default = cadena --ok-* → --ion-* → hex */
36
+ --color: var(--ok-text, var(--ion-text-color, #1c1b17));
37
+ --color-muted: var(--ok-text-muted, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.55));
38
+ --primary-color: var(--ok-primary, var(--ion-color-primary, #3880ff));
39
+ --surface-bg: var(--ok-surface, var(--ion-card-background, #ffffff));
40
+ --track-bg: var(--ok-border-soft, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.14));
41
+ --border-color: var(--ok-border, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.1));
42
+ --border-radius: var(--ok-radius, 10px);
43
+ --font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
44
+
45
+ /* Por defecto ocupa el ancho del contenedor y es responsive. */
46
+ display: block;
47
+ width: 100%;
48
+ color: var(--color);
49
+ font-family: var(--font);
50
+ }
51
+ .player {
52
+ box-sizing: border-box;
53
+ width: 100%;
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 0.5rem;
57
+ padding: 0.6rem 0.75rem;
58
+ background: var(--surface-bg);
59
+ border: 1px solid var(--border-color);
60
+ border-radius: var(--border-radius);
61
+ }
62
+ .title {
63
+ font-size: 0.9rem;
64
+ font-weight: 600;
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ white-space: nowrap;
68
+ }
69
+ .controls {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 0.6rem;
73
+ }
74
+ ion-button {
75
+ --padding-start: 0;
76
+ --padding-end: 0;
77
+ margin: 0;
78
+ flex: 0 0 auto;
79
+ }
80
+ .play ion-icon {
81
+ font-size: 1.4rem;
82
+ }
83
+ /* Barra de progreso clicable: ocupa el espacio flexible disponible. */
84
+ .progress {
85
+ position: relative;
86
+ flex: 1 1 auto;
87
+ min-width: 0;
88
+ height: 6px;
89
+ border-radius: 999px;
90
+ background: var(--track-bg);
91
+ cursor: pointer;
92
+ }
93
+ .progress .fill {
94
+ position: absolute;
95
+ inset: 0 auto 0 0;
96
+ height: 100%;
97
+ border-radius: inherit;
98
+ background: var(--primary-color);
99
+ width: 0%;
100
+ }
101
+ .progress .knob {
102
+ position: absolute;
103
+ top: 50%;
104
+ width: 12px;
105
+ height: 12px;
106
+ border-radius: 50%;
107
+ background: var(--primary-color);
108
+ transform: translate(-50%, -50%);
109
+ left: 0%;
110
+ }
111
+ .time {
112
+ flex: 0 0 auto;
113
+ font-size: 0.78rem;
114
+ color: var(--color-muted);
115
+ font-variant-numeric: tabular-nums;
116
+ white-space: nowrap;
117
+ }
118
+ /* Bloque de volumen: el slider sólo se ve con espacio suficiente. */
119
+ .volume {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 0.25rem;
123
+ flex: 0 0 auto;
124
+ }
125
+ .volume input[type='range'] {
126
+ width: 70px;
127
+ accent-color: var(--primary-color);
128
+ cursor: pointer;
129
+ }
130
+ /* En contenedores estrechos se oculta el slider de volumen (queda el mute). */
131
+ @media (max-width: 360px) {
132
+ .volume input[type='range'] {
133
+ display: none;
134
+ }
135
+ }
136
+ `;
137
+ }
138
+ get t() {
139
+ return { ...DEFAULT_LABELS, ...this.labels };
140
+ }
141
+ // Alterna play/pausa sobre el elemento nativo.
142
+ togglePlay() {
143
+ if (!this.audioEl) return;
144
+ if (this.audioEl.paused) this.audioEl.play();
145
+ else this.audioEl.pause();
146
+ }
147
+ // Sincroniza el estado al darle play y re-emite `ok-play`.
148
+ onPlay() {
149
+ this.playing = true;
150
+ this.dispatchEvent(
151
+ new CustomEvent("ok-play", {
152
+ detail: { currentTime: this.audioEl?.currentTime ?? 0 },
153
+ bubbles: true,
154
+ composed: true
155
+ })
156
+ );
157
+ }
158
+ // Sincroniza el estado al pausar y re-emite `ok-pause`.
159
+ onPause() {
160
+ this.playing = false;
161
+ this.dispatchEvent(
162
+ new CustomEvent("ok-pause", {
163
+ detail: { currentTime: this.audioEl?.currentTime ?? 0 },
164
+ bubbles: true,
165
+ composed: true
166
+ })
167
+ );
168
+ }
169
+ // Fin de la reproducción: re-emite `ok-ended`.
170
+ onEnded() {
171
+ this.playing = false;
172
+ this.dispatchEvent(
173
+ new CustomEvent("ok-ended", {
174
+ detail: { duration: this.duration },
175
+ bubbles: true,
176
+ composed: true
177
+ })
178
+ );
179
+ }
180
+ onTimeUpdate() {
181
+ this.current = this.audioEl?.currentTime ?? 0;
182
+ }
183
+ onLoadedMeta() {
184
+ this.duration = this.audioEl?.duration ?? 0;
185
+ }
186
+ // Seek: traduce la posición del click dentro de la barra a un tiempo del audio.
187
+ seek(e) {
188
+ const bar = e.currentTarget;
189
+ const rect = bar.getBoundingClientRect();
190
+ const ratio = rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0;
191
+ const clamped = Math.min(1, Math.max(0, ratio));
192
+ if (this.audioEl && this.duration) {
193
+ this.audioEl.currentTime = clamped * this.duration;
194
+ }
195
+ }
196
+ // Cambia el volumen desde el slider (0..1) y desactiva mute si subimos volumen.
197
+ onVolume(e) {
198
+ const v = Number(e.target.value);
199
+ this.volume = v;
200
+ if (this.audioEl) {
201
+ this.audioEl.volume = v;
202
+ this.audioEl.muted = v === 0;
203
+ }
204
+ this.muted = v === 0;
205
+ }
206
+ // Alterna mute conservando el último volumen.
207
+ toggleMute() {
208
+ this.muted = !this.muted;
209
+ if (this.audioEl) this.audioEl.muted = this.muted;
210
+ }
211
+ // Formatea segundos a m:ss (sin libs de fechas).
212
+ fmt(secs) {
213
+ if (!isFinite(secs) || secs < 0) secs = 0;
214
+ const m = Math.floor(secs / 60);
215
+ const s = Math.floor(secs % 60);
216
+ return `${m}:${s.toString().padStart(2, "0")}`;
217
+ }
218
+ render() {
219
+ const pct = this.duration ? this.current / this.duration * 100 : 0;
220
+ const volIcon = this.muted || this.volume === 0 ? "volume-mute-outline" : this.volume < 0.5 ? "volume-low-outline" : "volume-high-outline";
221
+ return html`
222
+ <div class="player">
223
+ ${this.title ? html`<div class="title">${this.title}</div>` : ""}
224
+ <audio
225
+ .src=${this.src}
226
+ preload="metadata"
227
+ @play=${this.onPlay}
228
+ @pause=${this.onPause}
229
+ @ended=${this.onEnded}
230
+ @timeupdate=${this.onTimeUpdate}
231
+ @loadedmetadata=${this.onLoadedMeta}
232
+ ></audio>
233
+ <div class="controls">
234
+ <ion-button
235
+ class="play"
236
+ fill="clear"
237
+ size="small"
238
+ aria-label=${this.playing ? this.t.pause : this.t.play}
239
+ @click=${this.togglePlay}
240
+ >
241
+ <ion-icon
242
+ slot="icon-only"
243
+ name=${this.playing ? "pause" : "play"}
244
+ ></ion-icon>
245
+ </ion-button>
246
+
247
+ <div class="progress" @click=${this.seek}>
248
+ <div class="fill" style=${`width:${pct}%`}></div>
249
+ <div class="knob" style=${`left:${pct}%`}></div>
250
+ </div>
251
+
252
+ <span class="time">${this.fmt(this.current)} / ${this.fmt(this.duration)}</span>
253
+
254
+ <div class="volume">
255
+ <ion-button
256
+ fill="clear"
257
+ size="small"
258
+ aria-label=${this.muted ? this.t.unmute : this.t.mute}
259
+ @click=${this.toggleMute}
260
+ >
261
+ <ion-icon slot="icon-only" name=${volIcon}></ion-icon>
262
+ </ion-button>
263
+ <input
264
+ type="range"
265
+ min="0"
266
+ max="1"
267
+ step="0.01"
268
+ .value=${String(this.muted ? 0 : this.volume)}
269
+ @input=${this.onVolume}
270
+ aria-label=${this.t.volume}
271
+ />
272
+ </div>
273
+ </div>
274
+ </div>
275
+ `;
276
+ }
277
+ }
278
+ __decorateClass([
279
+ property()
280
+ ], OkAudio.prototype, "src");
281
+ __decorateClass([
282
+ property()
283
+ ], OkAudio.prototype, "title");
284
+ __decorateClass([
285
+ property({ attribute: false })
286
+ ], OkAudio.prototype, "labels");
287
+ __decorateClass([
288
+ state()
289
+ ], OkAudio.prototype, "playing");
290
+ __decorateClass([
291
+ state()
292
+ ], OkAudio.prototype, "current");
293
+ __decorateClass([
294
+ state()
295
+ ], OkAudio.prototype, "duration");
296
+ __decorateClass([
297
+ state()
298
+ ], OkAudio.prototype, "volume");
299
+ __decorateClass([
300
+ state()
301
+ ], OkAudio.prototype, "muted");
302
+ __decorateClass([
303
+ query("audio")
304
+ ], OkAudio.prototype, "audioEl");
305
+ define("ok-audio", OkAudio);
306
+ export {
307
+ OkAudio
308
+ };