@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,1116 @@
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
+ upload: "Subir archivo",
15
+ import: "Importar",
16
+ search: "Buscar archivos…",
17
+ folders: "Carpetas",
18
+ space: "Espacio",
19
+ empty: "Sin archivos",
20
+ download: "Descargar",
21
+ delete: "Eliminar",
22
+ open: "Abrir",
23
+ newFolder: "Nueva carpeta",
24
+ noLimit: "Sin límite"
25
+ };
26
+ class OkFileManager extends LitElement {
27
+ constructor() {
28
+ super(...arguments);
29
+ this.folders = [];
30
+ this.files = [];
31
+ this.path = [];
32
+ this.selected = "";
33
+ this.view = "grid";
34
+ this.title = "";
35
+ this.searchable = true;
36
+ this.uploadable = true;
37
+ this.loading = false;
38
+ this.labels = {};
39
+ this.expandedIds = /* @__PURE__ */ new Set();
40
+ this.dragging = false;
41
+ this.seeded = false;
42
+ }
43
+ static {
44
+ this.styles = css`
45
+ :host {
46
+ /* Tokens propios estilo Ionic: cadena --ok-* → --ion-* → hex. */
47
+ --bg: var(--ok-surface, var(--ion-background-color, #ffffff));
48
+ --bg-2: var(--ok-surface-step-50, var(--ion-color-step-50, #f7f8fa));
49
+ --border: var(--ok-border-color, var(--ion-border-color, #e4e7ec));
50
+ --border-hover: var(--ok-color-step-200, var(--ion-color-step-200, #cdd1d8));
51
+ --radius: var(--ok-radius-md, 12px);
52
+ --radius-sm: var(--ok-radius-sm, 8px);
53
+ --ink: var(--ok-text-color, var(--ion-text-color, #1f2933));
54
+ --ink-2: var(--ok-color-step-600, var(--ion-color-step-600, #565a63));
55
+ --ink-3: var(--ok-color-medium, var(--ion-color-medium, #92949c));
56
+ --badge-bg: var(--ok-color-step-100, var(--ion-color-step-100, #eef0f3));
57
+ --hover-bg: var(--ok-hover, rgba(var(--ion-text-color-rgb, 28, 27, 23), 0.05));
58
+ --brand: var(--ok-color-primary, var(--ion-color-primary, #0091ce));
59
+ --brand-rgb: var(--ok-color-primary-rgb, var(--ion-color-primary-rgb, 0, 145, 206));
60
+ --danger: var(--ok-color-danger, var(--ion-color-danger, #eb445a));
61
+ --leaf: var(--ok-color-success, var(--ion-color-success, #2dd55b));
62
+ --info: var(--ok-color-primary, var(--ion-color-primary, #0091ce));
63
+ --warn: var(--ok-color-warning, var(--ion-color-warning, #ffc409));
64
+ --doc: var(--ok-color-primary, var(--ion-color-primary, #0091ce));
65
+ --track: var(--ok-color-step-100, var(--ion-color-step-100, #eef0f3));
66
+ --tree-width: var(--ok-fm-tree-width, 260px);
67
+ --font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
68
+
69
+ display: block;
70
+ width: 100%;
71
+ color: var(--ink);
72
+ font-family: var(--font);
73
+ font-size: 0.95rem;
74
+ }
75
+
76
+ /* Disposición en 2 columnas: árbol + main. */
77
+ .wrap {
78
+ display: grid;
79
+ grid-template-columns: var(--tree-width) 1fr;
80
+ gap: 16px;
81
+ align-items: start;
82
+ }
83
+
84
+ .panel {
85
+ background: var(--bg-2);
86
+ border: 1px solid var(--border);
87
+ border-radius: var(--radius);
88
+ box-sizing: border-box;
89
+ }
90
+
91
+ /* ---- Árbol lateral ---- */
92
+ .aside {
93
+ padding: 12px;
94
+ position: sticky;
95
+ top: 0;
96
+ }
97
+ .eyebrow {
98
+ font-size: 11px;
99
+ font-weight: 600;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.06em;
102
+ color: var(--ink-3);
103
+ padding: 4px 8px 8px;
104
+ }
105
+ [role='tree'],
106
+ [role='group'] {
107
+ margin: 0;
108
+ padding: 0;
109
+ list-style: none;
110
+ }
111
+ [role='group'] {
112
+ margin-left: 14px;
113
+ padding-left: 8px;
114
+ border-left: 1px solid var(--border);
115
+ }
116
+ .trow {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 6px;
120
+ width: 100%;
121
+ box-sizing: border-box;
122
+ padding: 7px 8px;
123
+ border-radius: var(--radius-sm);
124
+ cursor: pointer;
125
+ user-select: none;
126
+ color: var(--ink-2);
127
+ transition: background 0.15s ease, color 0.15s ease;
128
+ }
129
+ @media (hover: hover) {
130
+ .trow:hover {
131
+ background: var(--hover-bg);
132
+ }
133
+ }
134
+ .trow.active {
135
+ background: color-mix(in srgb, var(--brand) 14%, transparent);
136
+ color: var(--brand);
137
+ font-weight: 600;
138
+ }
139
+ .caret {
140
+ flex: 0 0 auto;
141
+ display: inline-flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ width: 18px;
145
+ height: 18px;
146
+ padding: 0;
147
+ border: 0;
148
+ background: none;
149
+ color: inherit;
150
+ cursor: pointer;
151
+ border-radius: 4px;
152
+ }
153
+ .caret.leaf {
154
+ visibility: hidden;
155
+ }
156
+ .caret ion-icon {
157
+ font-size: 0.95rem;
158
+ transition: transform 0.18s ease;
159
+ }
160
+ .caret.open ion-icon {
161
+ transform: rotate(90deg);
162
+ }
163
+ .ticon {
164
+ flex: 0 0 auto;
165
+ display: inline-flex;
166
+ align-items: center;
167
+ font-size: 1.05rem;
168
+ color: var(--ink-3);
169
+ }
170
+ .trow.active .ticon {
171
+ color: inherit;
172
+ }
173
+ .tlabel {
174
+ flex: 1 1 auto;
175
+ min-width: 0;
176
+ overflow: hidden;
177
+ text-overflow: ellipsis;
178
+ white-space: nowrap;
179
+ }
180
+ .tcount {
181
+ flex: 0 0 auto;
182
+ font-size: 11.5px;
183
+ font-variant-numeric: tabular-nums;
184
+ color: var(--ink-3);
185
+ }
186
+ .trow.active .tcount {
187
+ color: inherit;
188
+ }
189
+
190
+ /* Medidor de espacio. */
191
+ .quota {
192
+ margin-top: 12px;
193
+ padding: 12px 8px 4px;
194
+ border-top: 1px solid var(--border);
195
+ }
196
+ .meter {
197
+ height: 8px;
198
+ background: var(--track);
199
+ border-radius: 999px;
200
+ overflow: hidden;
201
+ }
202
+ .meter > i {
203
+ display: block;
204
+ height: 100%;
205
+ background: var(--brand);
206
+ border-radius: inherit;
207
+ transition: width 0.4s ease-out;
208
+ }
209
+ .quota-meta {
210
+ display: flex;
211
+ justify-content: space-between;
212
+ margin-top: 6px;
213
+ font-size: 11.5px;
214
+ color: var(--ink-3);
215
+ font-variant-numeric: tabular-nums;
216
+ }
217
+
218
+ /* Selector de carpeta para móvil (oculto en desktop). */
219
+ .folder-select {
220
+ display: none;
221
+ }
222
+
223
+ /* ---- Main ---- */
224
+ .main {
225
+ overflow: hidden;
226
+ position: relative;
227
+ }
228
+ .main.dragging::after {
229
+ content: '';
230
+ position: absolute;
231
+ inset: 0;
232
+ pointer-events: none;
233
+ border: 2px dashed var(--brand);
234
+ border-radius: var(--radius);
235
+ background: rgba(var(--brand-rgb), 0.08);
236
+ z-index: 2;
237
+ }
238
+
239
+ /* Toolbar. */
240
+ .toolbar {
241
+ display: flex;
242
+ flex-wrap: wrap;
243
+ align-items: center;
244
+ gap: 8px;
245
+ padding: 12px;
246
+ border-bottom: 1px solid var(--border);
247
+ }
248
+ .crumbs {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 4px;
252
+ flex: 1 1 200px;
253
+ min-width: 0;
254
+ font-size: 12px;
255
+ color: var(--ink-3);
256
+ flex-wrap: wrap;
257
+ }
258
+ .fm-title {
259
+ flex: 0 0 100%;
260
+ font-size: 14px;
261
+ font-weight: 600;
262
+ color: var(--ink);
263
+ margin-bottom: 2px;
264
+ }
265
+ .crumb {
266
+ background: none;
267
+ border: 0;
268
+ padding: 0;
269
+ cursor: pointer;
270
+ color: var(--brand);
271
+ font: inherit;
272
+ text-decoration: none;
273
+ }
274
+ .crumb:hover {
275
+ text-decoration: underline;
276
+ }
277
+ .crumb.current {
278
+ color: var(--ink);
279
+ font-weight: 500;
280
+ cursor: default;
281
+ }
282
+ .crumb.current:hover {
283
+ text-decoration: none;
284
+ }
285
+ .crumb-sep {
286
+ display: inline-flex;
287
+ color: var(--ink-3);
288
+ font-size: 0.8rem;
289
+ }
290
+ .search {
291
+ flex: 0 1 240px;
292
+ min-width: 140px;
293
+ }
294
+ /* Buscador plano: sin borde ni sombra, se funde con la toolbar. */
295
+ ion-searchbar {
296
+ --background: transparent;
297
+ --border-radius: var(--radius-sm);
298
+ --box-shadow: none;
299
+ --border-width: 0;
300
+ padding: 0;
301
+ min-height: 36px;
302
+ }
303
+ .view-toggle {
304
+ display: inline-flex;
305
+ gap: 2px;
306
+ background: var(--badge-bg);
307
+ border-radius: var(--radius-sm);
308
+ padding: 2px;
309
+ }
310
+ .view-btn {
311
+ display: inline-flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ width: 30px;
315
+ height: 30px;
316
+ padding: 0;
317
+ border: 0;
318
+ background: none;
319
+ color: var(--ink-3);
320
+ cursor: pointer;
321
+ border-radius: 6px;
322
+ transition: background 0.15s ease, color 0.15s ease;
323
+ }
324
+ .view-btn[aria-pressed='true'] {
325
+ background: var(--bg);
326
+ color: var(--brand);
327
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
328
+ }
329
+ .view-btn svg {
330
+ width: 17px;
331
+ height: 17px;
332
+ }
333
+ .tbtn {
334
+ display: inline-flex;
335
+ align-items: center;
336
+ gap: 5px;
337
+ height: 34px;
338
+ padding: 0 12px;
339
+ border: 1px solid var(--border);
340
+ border-radius: var(--radius-sm);
341
+ background: var(--bg);
342
+ color: var(--ink-2);
343
+ font: inherit;
344
+ font-size: 13px;
345
+ font-weight: 500;
346
+ cursor: pointer;
347
+ transition: border-color 0.15s ease, background 0.15s ease;
348
+ }
349
+ .tbtn:hover {
350
+ border-color: var(--border-hover);
351
+ }
352
+ .tbtn.primary {
353
+ background: var(--brand);
354
+ border-color: var(--brand);
355
+ color: var(--ok-color-primary-contrast, var(--ion-color-primary-contrast, #fff));
356
+ }
357
+ .tbtn.primary:hover {
358
+ filter: brightness(0.95);
359
+ }
360
+ .tbtn svg {
361
+ width: 16px;
362
+ height: 16px;
363
+ }
364
+ /* Variante solo-icono: botón cuadrado, sin texto (a11y vía aria-label/title). */
365
+ .tbtn.icon {
366
+ width: 34px;
367
+ padding: 0;
368
+ gap: 0;
369
+ justify-content: center;
370
+ }
371
+
372
+ /* Badge cuadrado tintado por extensión (mismo idioma visual que ok-file-item). */
373
+ .badge {
374
+ display: inline-flex;
375
+ align-items: center;
376
+ justify-content: center;
377
+ border-radius: var(--radius-sm);
378
+ background: var(--badge-bg);
379
+ color: var(--ink-2);
380
+ font-family: var(--ok-font-mono, ui-monospace, 'SFMono-Regular', 'Menlo', monospace);
381
+ font-weight: 600;
382
+ line-height: 1;
383
+ text-transform: uppercase;
384
+ letter-spacing: 0.03em;
385
+ overflow: hidden;
386
+ }
387
+ .badge img {
388
+ width: 100%;
389
+ height: 100%;
390
+ object-fit: cover;
391
+ }
392
+ .badge.pdf {
393
+ background: color-mix(in srgb, var(--danger) 16%, transparent);
394
+ color: var(--danger);
395
+ }
396
+ .badge.xls {
397
+ background: color-mix(in srgb, var(--leaf) 16%, transparent);
398
+ color: var(--leaf);
399
+ }
400
+ .badge.img {
401
+ background: color-mix(in srgb, var(--info) 16%, transparent);
402
+ color: var(--info);
403
+ }
404
+ .badge.zip {
405
+ background: color-mix(in srgb, var(--warn) 22%, transparent);
406
+ color: var(--warn);
407
+ }
408
+ .badge.doc {
409
+ background: color-mix(in srgb, var(--doc) 16%, transparent);
410
+ color: var(--doc);
411
+ }
412
+
413
+ /* ---- Vista grid: cards auto-fill ---- */
414
+ .grid {
415
+ display: grid;
416
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
417
+ gap: 12px;
418
+ padding: 14px;
419
+ }
420
+ .card {
421
+ position: relative;
422
+ display: flex;
423
+ flex-direction: column;
424
+ gap: 8px;
425
+ box-sizing: border-box;
426
+ padding: 12px;
427
+ background: var(--bg);
428
+ border: 1px solid var(--border);
429
+ border-radius: var(--radius-sm);
430
+ cursor: pointer;
431
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
432
+ }
433
+ .card:hover {
434
+ border-color: var(--border-hover);
435
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
436
+ }
437
+ .card .badge {
438
+ width: 42px;
439
+ height: 42px;
440
+ font-size: 11px;
441
+ }
442
+ .card-name {
443
+ font-size: 13px;
444
+ font-weight: 500;
445
+ color: var(--ink);
446
+ overflow: hidden;
447
+ text-overflow: ellipsis;
448
+ white-space: nowrap;
449
+ }
450
+ .card-meta {
451
+ font-size: 11.5px;
452
+ color: var(--ink-3);
453
+ font-variant-numeric: tabular-nums;
454
+ }
455
+ /* Acciones flotantes (aparecen al hover). */
456
+ .card-actions {
457
+ position: absolute;
458
+ top: 8px;
459
+ right: 8px;
460
+ display: flex;
461
+ gap: 2px;
462
+ opacity: 0;
463
+ transition: opacity 0.15s ease;
464
+ }
465
+ .card:hover .card-actions,
466
+ .card:focus-within .card-actions {
467
+ opacity: 1;
468
+ }
469
+
470
+ /* ---- Vista lista: filas ---- */
471
+ .list {
472
+ display: flex;
473
+ flex-direction: column;
474
+ }
475
+ .lrow {
476
+ display: grid;
477
+ grid-template-columns: 36px 1fr auto auto auto;
478
+ gap: 12px;
479
+ align-items: center;
480
+ box-sizing: border-box;
481
+ padding: 10px 14px;
482
+ border-bottom: 1px solid var(--border);
483
+ cursor: pointer;
484
+ transition: background 0.12s ease;
485
+ }
486
+ .lrow:hover {
487
+ background: var(--hover-bg);
488
+ }
489
+ .lrow:last-child {
490
+ border-bottom: 0;
491
+ }
492
+ .lrow .badge {
493
+ width: 34px;
494
+ height: 34px;
495
+ font-size: 10px;
496
+ }
497
+ .lname {
498
+ min-width: 0;
499
+ font-size: 13px;
500
+ font-weight: 500;
501
+ color: var(--ink);
502
+ overflow: hidden;
503
+ text-overflow: ellipsis;
504
+ white-space: nowrap;
505
+ }
506
+ .lsize {
507
+ font-size: 12px;
508
+ color: var(--ink-2);
509
+ font-variant-numeric: tabular-nums;
510
+ text-align: right;
511
+ white-space: nowrap;
512
+ }
513
+ .lmod {
514
+ font-size: 12px;
515
+ color: var(--ink-3);
516
+ font-variant-numeric: tabular-nums;
517
+ white-space: nowrap;
518
+ }
519
+ .lactions {
520
+ display: flex;
521
+ gap: 2px;
522
+ justify-content: flex-end;
523
+ }
524
+
525
+ /* Botón de acción de fila/card (fantasma). */
526
+ .action {
527
+ display: inline-flex;
528
+ align-items: center;
529
+ justify-content: center;
530
+ width: 28px;
531
+ height: 28px;
532
+ padding: 0;
533
+ border: 0;
534
+ background: var(--bg);
535
+ color: var(--ink-3);
536
+ cursor: pointer;
537
+ border-radius: 6px;
538
+ transition: background 0.15s ease, color 0.15s ease;
539
+ }
540
+ .action:hover {
541
+ background: var(--badge-bg);
542
+ color: var(--ink);
543
+ }
544
+ .action.danger:hover {
545
+ color: var(--danger);
546
+ }
547
+ .action svg {
548
+ width: 15px;
549
+ height: 15px;
550
+ }
551
+
552
+ /* Estado vacío. */
553
+ .empty {
554
+ display: flex;
555
+ flex-direction: column;
556
+ align-items: center;
557
+ justify-content: center;
558
+ gap: 10px;
559
+ padding: 48px 16px;
560
+ color: var(--ink-3);
561
+ text-align: center;
562
+ }
563
+ .empty ion-icon {
564
+ font-size: 2.4rem;
565
+ color: var(--ink-3);
566
+ opacity: 0.6;
567
+ }
568
+
569
+ /* Esqueletos de carga. */
570
+ .skeleton {
571
+ padding: 14px;
572
+ display: flex;
573
+ flex-direction: column;
574
+ gap: 10px;
575
+ }
576
+ .sk-row {
577
+ height: 48px;
578
+ border-radius: var(--radius-sm);
579
+ background: linear-gradient(
580
+ 90deg,
581
+ var(--badge-bg) 25%,
582
+ var(--track) 37%,
583
+ var(--badge-bg) 63%
584
+ );
585
+ background-size: 400% 100%;
586
+ animation: sk 1.4s ease infinite;
587
+ }
588
+ @keyframes sk {
589
+ 0% {
590
+ background-position: 100% 50%;
591
+ }
592
+ 100% {
593
+ background-position: 0 50%;
594
+ }
595
+ }
596
+ @media (prefers-reduced-motion: reduce) {
597
+ .sk-row {
598
+ animation: none;
599
+ }
600
+ }
601
+
602
+ input[type='file'] {
603
+ display: none;
604
+ }
605
+
606
+ /* ---- Responsive ≤768px: el árbol colapsa a un select y el main se apila ---- */
607
+ @media (max-width: 768px) {
608
+ .wrap {
609
+ grid-template-columns: 1fr;
610
+ }
611
+ .aside {
612
+ position: static;
613
+ }
614
+ .tree-host {
615
+ display: none;
616
+ }
617
+ .folder-select {
618
+ display: block;
619
+ }
620
+ .folder-select select {
621
+ width: 100%;
622
+ box-sizing: border-box;
623
+ height: 40px;
624
+ padding: 0 10px;
625
+ border: 1px solid var(--border);
626
+ border-radius: var(--radius-sm);
627
+ background: var(--bg);
628
+ color: var(--ink);
629
+ font: inherit;
630
+ }
631
+ .grid {
632
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
633
+ }
634
+ .lrow {
635
+ grid-template-columns: 34px 1fr auto;
636
+ }
637
+ .lmod {
638
+ display: none;
639
+ }
640
+ .lsize {
641
+ display: none;
642
+ }
643
+ }
644
+ `;
645
+ }
646
+ // Textos efectivos.
647
+ get t() {
648
+ return { ...DEFAULT_LABELS, ...this.labels };
649
+ }
650
+ // Siembra: expande todas las carpetas con hijos la primera vez.
651
+ seedExpanded(folders) {
652
+ for (const f of folders) {
653
+ if (f.children?.length) {
654
+ this.expandedIds.add(f.id);
655
+ this.seedExpanded(f.children);
656
+ }
657
+ }
658
+ }
659
+ // ---- Emisores de eventos ----
660
+ emit(name, detail) {
661
+ this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
662
+ }
663
+ navigate(id) {
664
+ this.emit("ok-navigate", { id });
665
+ }
666
+ open(id) {
667
+ this.emit("ok-open", { id });
668
+ }
669
+ download(file) {
670
+ this.emit("ok-download", { id: file.id, url: file.url });
671
+ }
672
+ deleteFile(id) {
673
+ this.emit("ok-delete", { id });
674
+ }
675
+ createFolder() {
676
+ this.emit("ok-create-folder", { parent: this.selected });
677
+ }
678
+ changeView(view) {
679
+ if (view === this.view) return;
680
+ this.view = view;
681
+ this.emit("ok-view-change", { view });
682
+ }
683
+ // Búsqueda con debounce (250ms).
684
+ onSearch(e) {
685
+ const value = e.target?.value ?? "";
686
+ if (this.searchTimer) clearTimeout(this.searchTimer);
687
+ this.searchTimer = setTimeout(() => {
688
+ this.emit("ok-search", { query: value });
689
+ }, 250);
690
+ }
691
+ // ---- Subida ----
692
+ openPicker() {
693
+ this.fileInput?.click();
694
+ }
695
+ onPicked(e) {
696
+ const input = e.target;
697
+ const files = input.files ? Array.from(input.files) : [];
698
+ if (files.length) this.emit("ok-upload", { files });
699
+ input.value = "";
700
+ }
701
+ toggle(folder) {
702
+ const next = new Set(this.expandedIds);
703
+ if (next.has(folder.id)) next.delete(folder.id);
704
+ else next.add(folder.id);
705
+ this.expandedIds = next;
706
+ }
707
+ // ---- Drag & drop sobre el main ----
708
+ onDragOver(e) {
709
+ if (!this.uploadable) return;
710
+ e.preventDefault();
711
+ this.dragging = true;
712
+ }
713
+ onDragLeave(e) {
714
+ if (!this.uploadable) return;
715
+ e.preventDefault();
716
+ this.dragging = false;
717
+ }
718
+ onDrop(e) {
719
+ if (!this.uploadable) return;
720
+ e.preventDefault();
721
+ this.dragging = false;
722
+ const files = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : [];
723
+ if (files.length) this.emit("ok-upload", { files });
724
+ }
725
+ // Deriva la extensión: usa `ext` o la cola del nombre.
726
+ extOf(file) {
727
+ if (file.ext) return file.ext.trim().toLowerCase();
728
+ const dot = file.name.lastIndexOf(".");
729
+ return dot >= 0 ? file.name.slice(dot + 1).toLowerCase() : "";
730
+ }
731
+ // Normaliza la extensión a una de las variantes tintadas conocidas.
732
+ variant(ext) {
733
+ if (!ext) return "";
734
+ if (ext === "pdf") return "pdf";
735
+ if (["xls", "xlsx", "csv", "ods", "numbers"].includes(ext)) return "xls";
736
+ if (["img", "jpg", "jpeg", "png", "gif", "webp", "svg", "heic", "avif"].includes(ext))
737
+ return "img";
738
+ if (["zip", "rar", "7z", "tar", "gz", "bz2"].includes(ext)) return "zip";
739
+ if (["doc", "docx", "odt", "rtf", "txt", "md"].includes(ext)) return "doc";
740
+ return "";
741
+ }
742
+ // Etiqueta corta del badge (máx 4 chars).
743
+ badgeLabel(ext) {
744
+ return (ext || "?").slice(0, 4).toUpperCase();
745
+ }
746
+ // Render del badge tintado (miniatura si hay thumb de imagen).
747
+ renderBadge(file) {
748
+ const ext = this.extOf(file);
749
+ const v = this.variant(ext);
750
+ if (file.thumb && v === "img") {
751
+ return html`<span class="badge ${v}"><img src=${file.thumb} alt="" /></span>`;
752
+ }
753
+ return html`<span class="badge ${v}" aria-hidden="true">${this.badgeLabel(ext)}</span>`;
754
+ }
755
+ // ---- Render del árbol (recursivo) ----
756
+ renderFolder(folder) {
757
+ const hasChildren = !!folder.children?.length;
758
+ const expanded = hasChildren && this.expandedIds.has(folder.id);
759
+ const active = folder.id === this.selected;
760
+ return html`<li role="treeitem" aria-selected=${active ? "true" : "false"} aria-expanded=${hasChildren ? String(expanded) : ""}>
761
+ <div class=${`trow ${active ? "active" : ""}`.trim()} @click=${() => this.navigate(folder.id)}>
762
+ <button
763
+ type="button"
764
+ class=${`caret ${hasChildren ? "" : "leaf"} ${expanded ? "open" : ""}`.trim()}
765
+ tabindex=${hasChildren ? "0" : "-1"}
766
+ aria-hidden=${hasChildren ? "false" : "true"}
767
+ aria-label=${expanded ? "Colapsar" : "Expandir"}
768
+ @click=${(e) => {
769
+ e.stopPropagation();
770
+ this.toggle(folder);
771
+ }}
772
+ >
773
+ <ion-icon name="chevron-forward-outline"></ion-icon>
774
+ </button>
775
+ <span class="ticon"
776
+ ><ion-icon name=${folder.icon || "folder-outline"}></ion-icon
777
+ ></span>
778
+ <span class="tlabel" title=${folder.label}>${folder.label}</span>
779
+ ${folder.count != null ? html`<span class="tcount">${folder.count}</span>` : ""}
780
+ </div>
781
+ ${expanded ? html`<ul role="group">
782
+ ${folder.children.map((c) => this.renderFolder(c))}
783
+ </ul>` : ""}
784
+ </li>`;
785
+ }
786
+ // Aplana el árbol para el <select> de móvil (con sangría por nivel).
787
+ flatFolders(folders, depth = 0, out = []) {
788
+ for (const f of folders) {
789
+ out.push({ f, d: depth });
790
+ if (f.children?.length) this.flatFolders(f.children, depth + 1, out);
791
+ }
792
+ return out;
793
+ }
794
+ // ---- Render del medidor de espacio ----
795
+ renderQuota() {
796
+ if (!this.quota) return "";
797
+ const unlimited = this.quota.unlimited || this.quota.totalLabel == null;
798
+ if (unlimited) {
799
+ return html`<div class="quota">
800
+ <div class="eyebrow" style="padding:0 0 6px;">${this.t.space}</div>
801
+ <div class="quota-meta">
802
+ <span>${this.quota.usedLabel}</span><span>${this.t.noLimit}</span>
803
+ </div>
804
+ </div>`;
805
+ }
806
+ const pct = Math.max(0, Math.min(100, (this.quota.fraction ?? 0) * 100));
807
+ return html`<div class="quota">
808
+ <div class="eyebrow" style="padding:0 0 6px;">${this.t.space}</div>
809
+ <div
810
+ class="meter"
811
+ role="progressbar"
812
+ aria-valuemin="0"
813
+ aria-valuemax="100"
814
+ aria-valuenow=${Math.round(pct)}
815
+ >
816
+ <i style="width:${pct}%"></i>
817
+ </div>
818
+ <div class="quota-meta">
819
+ <span>${this.quota.usedLabel}</span><span>${this.quota.totalLabel}</span>
820
+ </div>
821
+ </div>`;
822
+ }
823
+ // ---- Render de la toolbar ----
824
+ renderToolbar() {
825
+ const crumbs = this.path;
826
+ return html`<div class="toolbar">
827
+ <nav class="crumbs" aria-label="Ruta">
828
+ ${this.title ? html`<span class="fm-title">${this.title}</span>` : ""}
829
+ ${crumbs.map((c, i) => {
830
+ const isLast = i === crumbs.length - 1;
831
+ return html`${i > 0 ? html`<span class="crumb-sep" aria-hidden="true"
832
+ ><ion-icon name="chevron-forward-outline"></ion-icon
833
+ ></span>` : ""}<button
834
+ type="button"
835
+ class=${`crumb ${isLast ? "current" : ""}`.trim()}
836
+ aria-current=${isLast ? "page" : "false"}
837
+ @click=${() => !isLast && this.navigate(c.id)}
838
+ >
839
+ ${c.label}
840
+ </button>`;
841
+ })}
842
+ </nav>
843
+
844
+ ${this.searchable ? html`<div class="search">
845
+ <ion-searchbar
846
+ placeholder=${this.t.search}
847
+ debounce="0"
848
+ @ionInput=${(e) => this.onSearch(e)}
849
+ ></ion-searchbar>
850
+ </div>` : ""}
851
+
852
+ <div class="view-toggle" role="group" aria-label="Vista">
853
+ <button
854
+ type="button"
855
+ class="view-btn"
856
+ aria-pressed=${this.view === "list"}
857
+ aria-label="Vista lista"
858
+ title="Lista"
859
+ @click=${() => this.changeView("list")}
860
+ >
861
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
862
+ stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18" /></svg>
863
+ </button>
864
+ <button
865
+ type="button"
866
+ class="view-btn"
867
+ aria-pressed=${this.view === "grid"}
868
+ aria-label="Vista cuadrícula"
869
+ title="Cuadrícula"
870
+ @click=${() => this.changeView("grid")}
871
+ >
872
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
873
+ <rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" />
874
+ <rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" />
875
+ </svg>
876
+ </button>
877
+ </div>
878
+
879
+ <button
880
+ type="button"
881
+ class="tbtn icon"
882
+ aria-label=${this.t.newFolder}
883
+ title=${this.t.newFolder}
884
+ @click=${() => this.createFolder()}
885
+ >
886
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
887
+ stroke-linecap="round" stroke-linejoin="round">
888
+ <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
889
+ <path d="M12 11v6M9 14h6" />
890
+ </svg>
891
+ </button>
892
+
893
+ ${this.uploadable ? html`<button
894
+ type="button"
895
+ class="tbtn primary icon"
896
+ aria-label=${this.t.upload}
897
+ title=${this.t.upload}
898
+ @click=${() => this.openPicker()}
899
+ >
900
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
901
+ stroke-linecap="round" stroke-linejoin="round">
902
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
903
+ <polyline points="17 8 12 3 7 8" /><line x1="12" y1="3" x2="12" y2="15" />
904
+ </svg>
905
+ </button>` : ""}
906
+ </div>`;
907
+ }
908
+ // SVG reutilizables de las acciones de fila.
909
+ get iconOpen() {
910
+ return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
911
+ stroke-linecap="round" stroke-linejoin="round">
912
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" />
913
+ </svg>`;
914
+ }
915
+ get iconDownload() {
916
+ return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
917
+ stroke-linecap="round" stroke-linejoin="round">
918
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
919
+ <polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" />
920
+ </svg>`;
921
+ }
922
+ get iconDelete() {
923
+ return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
924
+ stroke-linecap="round" stroke-linejoin="round">
925
+ <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
926
+ </svg>`;
927
+ }
928
+ // Acciones de un archivo (abrir/descargar/eliminar), reutilizadas en grid y lista.
929
+ fileActions(file) {
930
+ return html`<button
931
+ type="button"
932
+ class="action"
933
+ aria-label=${this.t.open}
934
+ title=${this.t.open}
935
+ @click=${(e) => {
936
+ e.stopPropagation();
937
+ this.open(file.id);
938
+ }}
939
+ >
940
+ ${this.iconOpen}
941
+ </button>
942
+ <button
943
+ type="button"
944
+ class="action"
945
+ aria-label=${this.t.download}
946
+ title=${this.t.download}
947
+ @click=${(e) => {
948
+ e.stopPropagation();
949
+ this.download(file);
950
+ }}
951
+ >
952
+ ${this.iconDownload}
953
+ </button>
954
+ <button
955
+ type="button"
956
+ class="action danger"
957
+ aria-label=${this.t.delete}
958
+ title=${this.t.delete}
959
+ @click=${(e) => {
960
+ e.stopPropagation();
961
+ this.deleteFile(file.id);
962
+ }}
963
+ >
964
+ ${this.iconDelete}
965
+ </button>`;
966
+ }
967
+ // ---- Render del contenido (grid/lista/vacío/loading) ----
968
+ renderContent() {
969
+ if (this.loading) {
970
+ return html`<div class="skeleton">
971
+ ${[0, 1, 2, 3, 4].map(() => html`<div class="sk-row"></div>`)}
972
+ </div>`;
973
+ }
974
+ if (!this.files.length) {
975
+ return html`<div class="empty">
976
+ <ion-icon name="folder-open-outline"></ion-icon>
977
+ <span>${this.t.empty}</span>
978
+ </div>`;
979
+ }
980
+ return this.view === "grid" ? this.renderGrid() : this.renderList();
981
+ }
982
+ renderGrid() {
983
+ return html`<div class="grid">
984
+ ${this.files.map(
985
+ (file) => html`<article
986
+ class="card"
987
+ tabindex="0"
988
+ @dblclick=${() => this.open(file.id)}
989
+ @keydown=${(e) => {
990
+ if (e.key === "Enter") this.open(file.id);
991
+ }}
992
+ >
993
+ <div class="card-actions">${this.fileActions(file)}</div>
994
+ ${this.renderBadge(file)}
995
+ <div class="card-name" title=${file.name}>${file.name}</div>
996
+ <div class="card-meta">
997
+ ${[file.sizeLabel, file.modified].filter(Boolean).join(" · ")}
998
+ </div>
999
+ </article>`
1000
+ )}
1001
+ </div>`;
1002
+ }
1003
+ renderList() {
1004
+ return html`<div class="list" role="list">
1005
+ ${this.files.map(
1006
+ (file) => html`<div
1007
+ class="lrow"
1008
+ role="listitem"
1009
+ tabindex="0"
1010
+ @dblclick=${() => this.open(file.id)}
1011
+ @keydown=${(e) => {
1012
+ if (e.key === "Enter") this.open(file.id);
1013
+ }}
1014
+ >
1015
+ ${this.renderBadge(file)}
1016
+ <span class="lname" title=${file.name}>${file.name}</span>
1017
+ <span class="lsize">${file.sizeLabel ?? ""}</span>
1018
+ <span class="lmod">${file.modified ?? ""}</span>
1019
+ <div class="lactions">${this.fileActions(file)}</div>
1020
+ </div>`
1021
+ )}
1022
+ </div>`;
1023
+ }
1024
+ render() {
1025
+ if (!this.seeded && this.folders.length) {
1026
+ this.seedExpanded(this.folders);
1027
+ this.seeded = true;
1028
+ }
1029
+ const flat = this.flatFolders(this.folders);
1030
+ return html`<div class="wrap">
1031
+ <aside class="panel aside">
1032
+ <!-- Árbol (desktop) -->
1033
+ <div class="tree-host">
1034
+ <div class="eyebrow">${this.t.folders}</div>
1035
+ <ul role="tree" aria-label=${this.t.folders}>
1036
+ ${this.folders.map((f) => this.renderFolder(f))}
1037
+ </ul>
1038
+ </div>
1039
+
1040
+ <!-- Selector de carpeta (móvil) -->
1041
+ <div class="folder-select">
1042
+ <div class="eyebrow">${this.t.folders}</div>
1043
+ <select
1044
+ aria-label=${this.t.folders}
1045
+ @change=${(e) => this.navigate(e.target.value)}
1046
+ >
1047
+ ${flat.map(
1048
+ ({ f, d }) => html`<option value=${f.id} ?selected=${f.id === this.selected}>
1049
+ ${"  ".repeat(d)}${f.label}${f.count != null ? ` (${f.count})` : ""}
1050
+ </option>`
1051
+ )}
1052
+ </select>
1053
+ </div>
1054
+
1055
+ ${this.renderQuota()}
1056
+ </aside>
1057
+
1058
+ <section
1059
+ class=${`panel main ${this.dragging ? "dragging" : ""}`.trim()}
1060
+ @dragover=${this.onDragOver}
1061
+ @dragleave=${this.onDragLeave}
1062
+ @drop=${this.onDrop}
1063
+ >
1064
+ ${this.renderToolbar()} ${this.renderContent()}
1065
+ </section>
1066
+ </div>
1067
+
1068
+ <input type="file" multiple @change=${this.onPicked} />`;
1069
+ }
1070
+ }
1071
+ __decorateClass([
1072
+ property({ attribute: false })
1073
+ ], OkFileManager.prototype, "folders");
1074
+ __decorateClass([
1075
+ property({ attribute: false })
1076
+ ], OkFileManager.prototype, "files");
1077
+ __decorateClass([
1078
+ property({ attribute: false })
1079
+ ], OkFileManager.prototype, "path");
1080
+ __decorateClass([
1081
+ property()
1082
+ ], OkFileManager.prototype, "selected");
1083
+ __decorateClass([
1084
+ property()
1085
+ ], OkFileManager.prototype, "view");
1086
+ __decorateClass([
1087
+ property({ attribute: false })
1088
+ ], OkFileManager.prototype, "quota");
1089
+ __decorateClass([
1090
+ property()
1091
+ ], OkFileManager.prototype, "title");
1092
+ __decorateClass([
1093
+ property({ type: Boolean })
1094
+ ], OkFileManager.prototype, "searchable");
1095
+ __decorateClass([
1096
+ property({ type: Boolean })
1097
+ ], OkFileManager.prototype, "uploadable");
1098
+ __decorateClass([
1099
+ property({ type: Boolean })
1100
+ ], OkFileManager.prototype, "loading");
1101
+ __decorateClass([
1102
+ property({ attribute: false })
1103
+ ], OkFileManager.prototype, "labels");
1104
+ __decorateClass([
1105
+ state()
1106
+ ], OkFileManager.prototype, "expandedIds");
1107
+ __decorateClass([
1108
+ state()
1109
+ ], OkFileManager.prototype, "dragging");
1110
+ __decorateClass([
1111
+ query('input[type="file"]')
1112
+ ], OkFileManager.prototype, "fileInput");
1113
+ define("ok-file-manager", OkFileManager);
1114
+ export {
1115
+ OkFileManager
1116
+ };