@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,562 @@
1
+ import { LitElement, css, nothing, html } from "lit";
2
+ import { property, state } from "lit/decorators.js";
3
+ import { repeat } from "lit/directives/repeat.js";
4
+ import { define } from "./define.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
+ compose: "Compose",
16
+ searchPlaceholder: "Search mail…",
17
+ unread: "Unread",
18
+ star: "Star",
19
+ unstar: "Remove star",
20
+ noMessages: "No messages",
21
+ selectMessage: "Select a message",
22
+ back: "Back to list",
23
+ reply: "Reply",
24
+ forward: "Forward",
25
+ archive: "Archive",
26
+ delete: "Delete",
27
+ to: "To:"
28
+ };
29
+ class OkMail extends LitElement {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.folders = [];
33
+ this.messages = [];
34
+ this.activeFolder = "";
35
+ this.activeMessage = "";
36
+ this.searchable = true;
37
+ this.labels = {};
38
+ this.q = "";
39
+ this.onSearch = (ev) => {
40
+ const value = ev.target.value ?? "";
41
+ this.q = value;
42
+ this.emit("ok-search", { query: value });
43
+ };
44
+ }
45
+ static {
46
+ this.styles = css`
47
+ :host {
48
+ /* Tokens overridables (estilo Ionic): default = cadena --ok-* → --ion-* → hex. */
49
+ --background: var(--ok-surface, var(--ion-background-color, #ffffff));
50
+ --surface-2: var(--ok-surface-2, var(--ion-color-step-50, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.04)));
51
+ --color: var(--ok-text, var(--ion-text-color, #1c1b17));
52
+ --color-muted: var(--ok-muted, var(--ion-color-medium, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.55)));
53
+ --border-color: var(--ok-border, var(--ion-color-step-150, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.12)));
54
+ --border-color-soft: var(--ok-border-soft, var(--ion-color-step-100, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.07)));
55
+ --row-hover: var(--ok-row-hover, var(--ion-color-step-50, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.03)));
56
+ --primary: var(--ok-primary, var(--ion-color-primary, #3880ff));
57
+ --primary-contrast: var(--ok-primary-contrast, var(--ion-color-primary-contrast, #ffffff));
58
+ --warning: var(--ok-warning, var(--ion-color-warning, #ffc409));
59
+ --font: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
60
+ --list-width: 360px;
61
+ --press-scale: var(--ok-press-scale, 0.98);
62
+
63
+ display: flex;
64
+ flex-direction: column;
65
+ height: 100%;
66
+ width: 100%;
67
+ min-height: 0;
68
+ color: var(--color);
69
+ font-family: var(--font);
70
+ background: var(--background);
71
+ }
72
+ * { box-sizing: border-box; }
73
+
74
+ /* ── Cabecera superior: Redactar + buscador ──────────────────────────────────────────── */
75
+ .topbar {
76
+ flex: 0 0 auto;
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 0.5rem;
80
+ padding: 0.6rem 0.75rem;
81
+ border-bottom: 1px solid var(--border-color-soft);
82
+ }
83
+ .topbar ion-button { --box-shadow: none; margin: 0; flex: 0 0 auto; }
84
+ .topbar ion-searchbar {
85
+ --background: var(--surface-2);
86
+ --border-radius: 12px;
87
+ --box-shadow: none;
88
+ padding: 0;
89
+ flex: 1 1 auto;
90
+ min-height: 40px;
91
+ }
92
+ .topbar ion-searchbar::part(native) { border: none; }
93
+
94
+ /* ── Barra de TABS de carpetas (scroll horizontal) ───────────────────────────────────── */
95
+ .tabs {
96
+ flex: 0 0 auto;
97
+ display: flex;
98
+ align-items: stretch;
99
+ gap: 0.35rem;
100
+ padding: 0.4rem 0.6rem 0;
101
+ overflow-x: auto;
102
+ overflow-y: hidden;
103
+ border-bottom: 1px solid var(--border-color);
104
+ scrollbar-width: none;
105
+ -ms-overflow-style: none;
106
+ scroll-snap-type: x proximity;
107
+ }
108
+ .tabs::-webkit-scrollbar { display: none; }
109
+ .tab {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ gap: 0.45rem;
113
+ flex: 0 0 auto;
114
+ padding: 0.55rem 0.85rem;
115
+ border: 0;
116
+ border-bottom: 2px solid transparent;
117
+ border-radius: 10px 10px 0 0;
118
+ background: none;
119
+ color: var(--color-muted);
120
+ font: inherit;
121
+ font-size: 14px;
122
+ font-weight: 600;
123
+ white-space: nowrap;
124
+ cursor: pointer;
125
+ scroll-snap-align: start;
126
+ transition: color 150ms ease, background-color 150ms ease, border-color 150ms ease;
127
+ }
128
+ .tab ion-icon { font-size: 17px; flex: 0 0 auto; }
129
+ .tab ion-badge { --background: var(--surface-2); --color: var(--color-muted); font-size: 11px; min-width: 18px; }
130
+ .tab.active { color: var(--primary); border-bottom-color: var(--primary); }
131
+ .tab.active ion-badge { --background: var(--primary); --color: var(--primary-contrast); }
132
+ @media (hover: hover) {
133
+ .tab:hover { color: var(--color); background: var(--row-hover); }
134
+ .tab.active:hover { color: var(--primary); }
135
+ }
136
+ .tab:active { transform: scale(var(--press-scale)); }
137
+
138
+ /* ── Cuerpo: dos paneles (lista + lectura) ───────────────────────────────────────────── */
139
+ .body {
140
+ flex: 1 1 auto;
141
+ display: flex;
142
+ min-height: 0;
143
+ }
144
+
145
+ /* ── Panel Lista de mensajes ─────────────────────────────────────────────────────────── */
146
+ .list {
147
+ flex: 0 0 var(--list-width);
148
+ width: var(--list-width);
149
+ display: flex;
150
+ flex-direction: column;
151
+ min-height: 0;
152
+ border-right: 1px solid var(--border-color);
153
+ }
154
+ .msg-list {
155
+ flex: 1 1 auto;
156
+ min-height: 0;
157
+ overflow: auto;
158
+ }
159
+ .msg {
160
+ display: grid;
161
+ grid-template-columns: auto 1fr auto;
162
+ grid-template-rows: auto auto;
163
+ gap: 0.1rem 0.6rem;
164
+ padding: 0.65rem 0.75rem;
165
+ border-bottom: 1px solid var(--border-color-soft);
166
+ cursor: pointer;
167
+ transition: background-color 150ms ease;
168
+ }
169
+ .msg .avatar {
170
+ grid-row: 1 / span 2;
171
+ align-self: start;
172
+ }
173
+ .msg ion-avatar {
174
+ width: 36px;
175
+ height: 36px;
176
+ display: grid;
177
+ place-items: center;
178
+ background: color-mix(in srgb, var(--primary) 16%, transparent);
179
+ color: var(--primary);
180
+ font-size: 13px;
181
+ font-weight: 600;
182
+ }
183
+ .msg .top { display: flex; align-items: baseline; gap: 0.4rem; min-width: 0; }
184
+ .msg .from { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
185
+ .msg .date { flex: 0 0 auto; font-size: 11.5px; color: var(--color-muted); }
186
+ .msg .subject { font-size: 13.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
187
+ .msg .preview { grid-column: 2 / span 2; font-size: 12.5px; color: var(--color-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
188
+ .msg .star {
189
+ grid-row: 1 / span 2;
190
+ align-self: start;
191
+ display: inline-flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ border: 0;
195
+ background: none;
196
+ padding: 0.15rem;
197
+ cursor: pointer;
198
+ color: var(--color-muted);
199
+ transition: color 150ms ease, transform 120ms ease;
200
+ }
201
+ .msg .star ion-icon { font-size: 17px; }
202
+ .msg .star.on { color: var(--warning); }
203
+ .msg .star:active { transform: scale(var(--press-scale)); }
204
+ /* Punto de NO LEÍDO + negrita en remitente/asunto. */
205
+ .msg.unread .from,
206
+ .msg.unread .subject { font-weight: 700; }
207
+ .msg.unread .dot {
208
+ grid-row: 1 / span 2;
209
+ align-self: center;
210
+ width: 8px;
211
+ height: 8px;
212
+ border-radius: 999px;
213
+ background: var(--primary);
214
+ }
215
+ .msg.active { background: color-mix(in srgb, var(--primary) 10%, transparent); }
216
+ @media (hover: hover) {
217
+ .msg:hover { background: var(--row-hover); }
218
+ .msg.active:hover { background: color-mix(in srgb, var(--primary) 14%, transparent); }
219
+ }
220
+ .msg:active { transform: scale(0.998); }
221
+
222
+ /* ── Panel Lectura (derecha) ─────────────────────────────────────────────────────────── */
223
+ .reader {
224
+ flex: 1 1 auto;
225
+ display: flex;
226
+ flex-direction: column;
227
+ min-width: 0;
228
+ min-height: 0;
229
+ }
230
+ .reader-actions {
231
+ flex: 0 0 auto;
232
+ display: flex;
233
+ align-items: center;
234
+ gap: 0.2rem;
235
+ padding: 0.4rem 0.6rem;
236
+ border-bottom: 1px solid var(--border-color);
237
+ background: var(--surface-2);
238
+ }
239
+ .reader-actions ion-button { --box-shadow: none; margin: 0; }
240
+ .reader-body {
241
+ flex: 1 1 auto;
242
+ min-height: 0;
243
+ overflow: auto;
244
+ padding: 1.25rem 1.5rem;
245
+ }
246
+ .reader-subject { margin: 0 0 0.85rem; font-size: 1.4rem; font-weight: 600; line-height: 1.25; }
247
+ .reader-meta {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0.6rem;
251
+ padding-bottom: 0.85rem;
252
+ margin-bottom: 1rem;
253
+ border-bottom: 1px solid var(--border-color-soft);
254
+ }
255
+ .reader-meta ion-avatar {
256
+ width: 40px;
257
+ height: 40px;
258
+ flex: 0 0 auto;
259
+ display: grid;
260
+ place-items: center;
261
+ background: color-mix(in srgb, var(--primary) 16%, transparent);
262
+ color: var(--primary);
263
+ font-size: 14px;
264
+ font-weight: 600;
265
+ }
266
+ .reader-meta .who { flex: 1 1 auto; min-width: 0; }
267
+ .reader-meta .who .name { font-weight: 600; }
268
+ .reader-meta .who .addr,
269
+ .reader-meta .who .to { font-size: 12.5px; color: var(--color-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
270
+ .reader-meta .when { flex: 0 0 auto; font-size: 12.5px; color: var(--color-muted); }
271
+ .reader-text { white-space: pre-wrap; line-height: 1.55; font-size: 14.5px; }
272
+
273
+ /* Adjuntos como chips. */
274
+ .attachments { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border-color-soft); }
275
+ .att-chip {
276
+ display: inline-flex;
277
+ align-items: center;
278
+ gap: 0.4rem;
279
+ padding: 0.4rem 0.7rem;
280
+ border: 1px solid var(--border-color);
281
+ border-radius: 10px;
282
+ background: var(--surface-2);
283
+ font-size: 12.5px;
284
+ }
285
+ .att-chip ion-icon { font-size: 16px; color: var(--color-muted); }
286
+ .att-chip .att-size { color: var(--color-muted); }
287
+
288
+ /* ── Estados vacíos (REUSO ok-empty-state) ──────────────────────────────────────────── */
289
+ .empty-wrap { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; min-height: 0; padding: 1rem; }
290
+
291
+ /* ── Botón atrás (solo visible en vista móvil) ──────────────────────────────────────── */
292
+ .back { display: none; }
293
+ .back ion-button { --box-shadow: none; margin: 0; }
294
+
295
+ /* ── Responsive (móvil): tabs siempre arriba, UN panel del cuerpo a la vez ──────────── */
296
+ @media (max-width: 760px) {
297
+ .body { position: relative; }
298
+ .list,
299
+ .reader {
300
+ position: absolute;
301
+ inset: 0;
302
+ flex: none;
303
+ width: 100%;
304
+ border-right: 0;
305
+ background: var(--background);
306
+ }
307
+ /* Solo el panel correspondiente a la vista móvil activa queda visible. */
308
+ :host([data-mview='list']) .reader,
309
+ :host([data-mview='message']) .list { display: none; }
310
+ .back { display: flex; align-items: center; }
311
+ }
312
+
313
+ @media (prefers-reduced-motion: reduce) {
314
+ .tab:active,
315
+ .msg:active,
316
+ .msg .star:active { transform: none; }
317
+ }
318
+ `;
319
+ }
320
+ /** Textos efectivos (defaults inglés + overrides). */
321
+ get t() {
322
+ return { ...DEFAULT_LABELS, ...this.labels };
323
+ }
324
+ emit(type, detail) {
325
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
326
+ }
327
+ /** Vista móvil derivada del estado activo: las tabs están siempre arriba, así que el cuerpo
328
+ * solo alterna entre la lista y la lectura (message si hay mensaje seleccionado, si no list). */
329
+ get mobileView() {
330
+ return this.activeMessage ? "message" : "list";
331
+ }
332
+ // Refleja la vista móvil en un atributo del host para que el CSS responsive muestre un panel.
333
+ updated() {
334
+ this.setAttribute("data-mview", this.mobileView);
335
+ }
336
+ /** Iniciales de un nombre para el avatar (p.ej. "Ana Pérez" → "AP"). */
337
+ initials(name) {
338
+ const parts = name.trim().split(/\s+/).filter(Boolean);
339
+ if (parts.length === 0) return "?";
340
+ const first = parts[0][0] ?? "";
341
+ const last = parts.length > 1 ? parts[parts.length - 1][0] ?? "" : "";
342
+ return (first + last).toUpperCase();
343
+ }
344
+ /** Fecha en formato corto con Intl (hora si es hoy, día/mes si es este año, si no fecha completa). */
345
+ fmtDate(iso) {
346
+ const d = new Date(iso);
347
+ if (Number.isNaN(d.getTime())) return "";
348
+ const now = /* @__PURE__ */ new Date();
349
+ const sameDay = d.toDateString() === now.toDateString();
350
+ if (sameDay) return new Intl.DateTimeFormat(void 0, { hour: "2-digit", minute: "2-digit" }).format(d);
351
+ if (d.getFullYear() === now.getFullYear()) {
352
+ return new Intl.DateTimeFormat(void 0, { day: "2-digit", month: "short" }).format(d);
353
+ }
354
+ return new Intl.DateTimeFormat(void 0, { day: "2-digit", month: "short", year: "numeric" }).format(d);
355
+ }
356
+ /** Tamaño legible de un adjunto (B/KB/MB). */
357
+ fmtSize(bytes) {
358
+ if (bytes == null) return "";
359
+ if (bytes < 1024) return `${bytes} B`;
360
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
361
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
362
+ }
363
+ /** Mensajes de la carpeta activa filtrados por el buscador (preview/asunto/remitente). */
364
+ get folderMessages() {
365
+ let result = this.messages.filter((m) => m.folderId === this.activeFolder);
366
+ const needle = this.q.trim().toLowerCase();
367
+ if (needle) {
368
+ result = result.filter(
369
+ (m) => [m.subject, m.preview ?? "", m.from.name, m.from.email].some((s) => s.toLowerCase().includes(needle))
370
+ );
371
+ }
372
+ return result;
373
+ }
374
+ onFolderSelect(id) {
375
+ this.activeFolder = id;
376
+ this.activeMessage = "";
377
+ this.emit("ok-folder-select", { id });
378
+ }
379
+ onMessageSelect(message) {
380
+ this.activeMessage = message.id;
381
+ this.emit("ok-message-select", { id: message.id, message });
382
+ }
383
+ onStar(ev, m) {
384
+ ev.stopPropagation();
385
+ this.emit("ok-star", { id: m.id, starred: !m.starred });
386
+ }
387
+ // ── Render: Cabecera superior (Redactar + buscador) ───────────────────────────────────────
388
+ renderTopbar() {
389
+ return html`
390
+ <div class="topbar">
391
+ <ion-button @click=${() => this.emit("ok-compose", {})}>
392
+ <ion-icon slot="start" name="create-outline"></ion-icon>
393
+ ${this.t.compose}
394
+ </ion-button>
395
+ ${this.searchable ? html`<ion-searchbar
396
+ .value=${this.q}
397
+ placeholder=${this.t.searchPlaceholder}
398
+ debounce="200"
399
+ @ionInput=${this.onSearch}
400
+ ></ion-searchbar>` : nothing}
401
+ </div>
402
+ `;
403
+ }
404
+ // ── Render: Barra de TABS de carpetas (scroll horizontal) ─────────────────────────────────
405
+ renderTabs() {
406
+ return html`
407
+ <nav class="tabs" role="tablist">
408
+ ${repeat(
409
+ this.folders,
410
+ (f) => f.id,
411
+ (f) => html`
412
+ <button
413
+ class=${`tab${f.id === this.activeFolder ? " active" : ""}`}
414
+ role="tab"
415
+ aria-selected=${f.id === this.activeFolder ? "true" : "false"}
416
+ @click=${() => this.onFolderSelect(f.id)}
417
+ >
418
+ ${f.icon ? html`<ion-icon name=${f.icon}></ion-icon>` : nothing}
419
+ <span class="tlabel">${f.label}</span>
420
+ ${f.count ? html`<ion-badge>${f.count}</ion-badge>` : nothing}
421
+ </button>
422
+ `
423
+ )}
424
+ </nav>
425
+ `;
426
+ }
427
+ // ── Render: Panel Lista de mensajes ──────────────────────────────────────────────────────────
428
+ renderList() {
429
+ const msgs = this.folderMessages;
430
+ return html`
431
+ <section class="list">
432
+ <div class="msg-list">
433
+ ${msgs.length === 0 ? html`<div class="empty-wrap"><ok-empty-state icon="mail-outline" heading=${this.t.noMessages}></ok-empty-state></div>` : repeat(
434
+ msgs,
435
+ (m) => m.id,
436
+ (m) => {
437
+ const unread = m.read === false;
438
+ return html`
439
+ <article
440
+ class=${`msg${unread ? " unread" : ""}${m.id === this.activeMessage ? " active" : ""}`}
441
+ @click=${() => this.onMessageSelect(m)}
442
+ >
443
+ ${unread ? html`<span class="dot" aria-label=${this.t.unread}></span>` : nothing}
444
+ <span class="avatar"><ion-avatar>${this.initials(m.from.name)}</ion-avatar></span>
445
+ <div class="top">
446
+ <span class="from">${m.from.name}</span>
447
+ <span class="date">${this.fmtDate(m.date)}</span>
448
+ </div>
449
+ <span class="subject">${m.subject}</span>
450
+ ${m.preview ? html`<span class="preview">${m.preview}</span>` : nothing}
451
+ <button
452
+ class=${`star${m.starred ? " on" : ""}`}
453
+ aria-label=${m.starred ? this.t.unstar : this.t.star}
454
+ @click=${(e) => this.onStar(e, m)}
455
+ >
456
+ <ion-icon name=${m.starred ? "star" : "star-outline"}></ion-icon>
457
+ </button>
458
+ </article>
459
+ `;
460
+ }
461
+ )}
462
+ </div>
463
+ </section>
464
+ `;
465
+ }
466
+ // ── Render: Panel Lectura ─────────────────────────────────────────────────────────────────────
467
+ renderReader() {
468
+ const m = this.messages.find((x) => x.id === this.activeMessage);
469
+ if (!m) {
470
+ return html`
471
+ <section class="reader">
472
+ <div class="empty-wrap">
473
+ <ok-empty-state icon="mail-open-outline" heading=${this.t.selectMessage}></ok-empty-state>
474
+ </div>
475
+ </section>
476
+ `;
477
+ }
478
+ return html`
479
+ <section class="reader">
480
+ <div class="reader-actions">
481
+ <span class="back">
482
+ <ion-button fill="clear" size="small" aria-label=${this.t.back} @click=${() => {
483
+ this.activeMessage = "";
484
+ }}>
485
+ <ion-icon slot="icon-only" name="chevron-back"></ion-icon>
486
+ </ion-button>
487
+ </span>
488
+ <ion-button fill="clear" size="small" aria-label=${this.t.reply} @click=${() => this.emit("ok-reply", { id: m.id })}>
489
+ <ion-icon slot="icon-only" name="arrow-undo-outline"></ion-icon>
490
+ </ion-button>
491
+ <ion-button fill="clear" size="small" aria-label=${this.t.forward} @click=${() => this.emit("ok-forward", { id: m.id })}>
492
+ <ion-icon slot="icon-only" name="arrow-redo-outline"></ion-icon>
493
+ </ion-button>
494
+ <ion-button fill="clear" size="small" aria-label=${this.t.archive} @click=${() => this.emit("ok-archive", { id: m.id })}>
495
+ <ion-icon slot="icon-only" name="archive-outline"></ion-icon>
496
+ </ion-button>
497
+ <ion-button fill="clear" size="small" color="danger" aria-label=${this.t.delete} @click=${() => this.emit("ok-delete", { id: m.id })}>
498
+ <ion-icon slot="icon-only" name="trash-outline"></ion-icon>
499
+ </ion-button>
500
+ </div>
501
+ <div class="reader-body">
502
+ <h1 class="reader-subject">${m.subject}</h1>
503
+ <header class="reader-meta">
504
+ <ion-avatar>${this.initials(m.from.name)}</ion-avatar>
505
+ <div class="who">
506
+ <div class="name">${m.from.name}</div>
507
+ <div class="addr">${m.from.email}</div>
508
+ ${m.to && m.to.length ? html`<div class="to">${this.t.to} ${m.to.join(", ")}</div>` : nothing}
509
+ </div>
510
+ <span class="when">${this.fmtDate(m.date)}</span>
511
+ </header>
512
+ <div class="reader-text">${m.body ?? m.preview ?? ""}</div>
513
+ ${m.attachments && m.attachments.length ? html`
514
+ <div class="attachments">
515
+ ${m.attachments.map(
516
+ (a) => html`
517
+ <span class="att-chip">
518
+ <ion-icon name="document-attach-outline"></ion-icon>
519
+ <span>${a.name}</span>
520
+ ${a.size != null ? html`<span class="att-size">${this.fmtSize(a.size)}</span>` : nothing}
521
+ </span>
522
+ `
523
+ )}
524
+ </div>
525
+ ` : nothing}
526
+ </div>
527
+ </section>
528
+ `;
529
+ }
530
+ render() {
531
+ return html`
532
+ ${this.renderTopbar()}
533
+ ${this.renderTabs()}
534
+ <div class="body">${this.renderList()}${this.renderReader()}</div>
535
+ `;
536
+ }
537
+ }
538
+ __decorateClass([
539
+ property({ attribute: false })
540
+ ], OkMail.prototype, "folders");
541
+ __decorateClass([
542
+ property({ attribute: false })
543
+ ], OkMail.prototype, "messages");
544
+ __decorateClass([
545
+ property({ attribute: "active-folder" })
546
+ ], OkMail.prototype, "activeFolder");
547
+ __decorateClass([
548
+ property({ attribute: "active-message" })
549
+ ], OkMail.prototype, "activeMessage");
550
+ __decorateClass([
551
+ property({ type: Boolean })
552
+ ], OkMail.prototype, "searchable");
553
+ __decorateClass([
554
+ property({ attribute: false })
555
+ ], OkMail.prototype, "labels");
556
+ __decorateClass([
557
+ state()
558
+ ], OkMail.prototype, "q");
559
+ define("ok-mail", OkMail);
560
+ export {
561
+ OkMail
562
+ };