@vantageos/mosaic 0.3.1 → 0.5.0

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 (157) hide show
  1. package/CHANGELOG.md +88 -2
  2. package/LICENSE +110 -0
  3. package/README.md +193 -21
  4. package/dist/PipelineBoard-FucyGArR.d.cts +759 -0
  5. package/dist/PipelineBoard-FucyGArR.d.ts +759 -0
  6. package/dist/PipelineBoard.markdown-DIcvrS-2.d.cts +16 -0
  7. package/dist/PipelineBoard.markdown-DIcvrS-2.d.ts +16 -0
  8. package/dist/{StatusBadge.schema-1tOjB4Ir.d.cts → StatusBadge.schema-BEjSH2_4.d.cts} +2 -2
  9. package/dist/{StatusBadge.schema-1tOjB4Ir.d.ts → StatusBadge.schema-BEjSH2_4.d.ts} +2 -2
  10. package/dist/{Tabs.schema-BzcEyeq8.d.cts → Tabs.schema-Dqr6vwV3.d.cts} +2 -2
  11. package/dist/{Tabs.schema-BzcEyeq8.d.ts → Tabs.schema-Dqr6vwV3.d.ts} +2 -2
  12. package/dist/Timeline-Coo_3tA_.d.cts +114 -0
  13. package/dist/Timeline-Coo_3tA_.d.ts +114 -0
  14. package/dist/Timeline.markdown-C6UPDv1H.d.cts +4 -0
  15. package/dist/Timeline.markdown-C6UPDv1H.d.ts +4 -0
  16. package/dist/TransactionPreview-BzWDn8kE.d.cts +391 -0
  17. package/dist/TransactionPreview-BzWDn8kE.d.ts +391 -0
  18. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.cts +7 -0
  19. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.ts +7 -0
  20. package/dist/artifacts.cjs +190 -2
  21. package/dist/artifacts.cjs.map +1 -1
  22. package/dist/artifacts.js +190 -2
  23. package/dist/artifacts.js.map +1 -1
  24. package/dist/confirmation.cjs +728 -2
  25. package/dist/confirmation.cjs.map +1 -1
  26. package/dist/confirmation.d.cts +2 -1
  27. package/dist/confirmation.d.ts +2 -1
  28. package/dist/confirmation.js +726 -4
  29. package/dist/confirmation.js.map +1 -1
  30. package/dist/display.cjs +1555 -203
  31. package/dist/display.cjs.map +1 -1
  32. package/dist/display.d.cts +6 -229
  33. package/dist/display.d.ts +6 -229
  34. package/dist/display.js +1539 -206
  35. package/dist/display.js.map +1 -1
  36. package/dist/forms.cjs +139 -46
  37. package/dist/forms.cjs.map +1 -1
  38. package/dist/forms.d.cts +2 -2
  39. package/dist/forms.d.ts +2 -2
  40. package/dist/forms.js +139 -46
  41. package/dist/forms.js.map +1 -1
  42. package/dist/host.cjs +822 -0
  43. package/dist/host.cjs.map +1 -0
  44. package/dist/host.d.cts +287 -0
  45. package/dist/host.d.ts +287 -0
  46. package/dist/host.js +794 -0
  47. package/dist/host.js.map +1 -0
  48. package/dist/{index-CwXVgtGb.d.cts → index-BadRcCbm.d.cts} +28 -11
  49. package/dist/{index-CwXVgtGb.d.ts → index-BadRcCbm.d.ts} +28 -11
  50. package/dist/index.cjs +2202 -247
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +42 -8
  53. package/dist/index.d.ts +42 -8
  54. package/dist/index.js +2167 -251
  55. package/dist/index.js.map +1 -1
  56. package/dist/input.d.cts +2 -2
  57. package/dist/input.d.ts +2 -2
  58. package/dist/media.cjs +190 -2
  59. package/dist/media.cjs.map +1 -1
  60. package/dist/media.d.cts +1 -1
  61. package/dist/media.d.ts +1 -1
  62. package/dist/media.js +190 -2
  63. package/dist/media.js.map +1 -1
  64. package/dist/preact/artifacts.js +190 -2
  65. package/dist/preact/artifacts.js.map +1 -1
  66. package/dist/preact/confirmation.d.ts +321 -1
  67. package/dist/preact/confirmation.js +629 -4
  68. package/dist/preact/confirmation.js.map +1 -1
  69. package/dist/preact/display.d.ts +570 -31
  70. package/dist/preact/display.js +1248 -126
  71. package/dist/preact/display.js.map +1 -1
  72. package/dist/preact/forms.d.ts +32 -15
  73. package/dist/preact/forms.js +139 -46
  74. package/dist/preact/forms.js.map +1 -1
  75. package/dist/preact/host.d.ts +307 -0
  76. package/dist/preact/host.js +875 -0
  77. package/dist/preact/host.js.map +1 -0
  78. package/dist/preact/media.d.ts +37 -1
  79. package/dist/preact/media.js +423 -0
  80. package/dist/preact/media.js.map +1 -1
  81. package/dist/preact/progress.d.ts +92 -1
  82. package/dist/preact/progress.js +469 -3
  83. package/dist/preact/progress.js.map +1 -1
  84. package/dist/progress.cjs +525 -2
  85. package/dist/progress.cjs.map +1 -1
  86. package/dist/progress.d.cts +2 -1
  87. package/dist/progress.d.ts +2 -1
  88. package/dist/progress.js +522 -3
  89. package/dist/progress.js.map +1 -1
  90. package/dist/react/artifacts.cjs +190 -2
  91. package/dist/react/artifacts.cjs.map +1 -1
  92. package/dist/react/artifacts.js +190 -2
  93. package/dist/react/artifacts.js.map +1 -1
  94. package/dist/react/confirmation.cjs +630 -2
  95. package/dist/react/confirmation.cjs.map +1 -1
  96. package/dist/react/confirmation.d.cts +3 -3
  97. package/dist/react/confirmation.d.ts +3 -3
  98. package/dist/react/confirmation.js +629 -4
  99. package/dist/react/confirmation.js.map +1 -1
  100. package/dist/react/display.cjs +1137 -123
  101. package/dist/react/display.cjs.map +1 -1
  102. package/dist/react/display.d.cts +42 -14
  103. package/dist/react/display.d.ts +42 -14
  104. package/dist/react/display.js +1122 -126
  105. package/dist/react/display.js.map +1 -1
  106. package/dist/react/forms.cjs +139 -46
  107. package/dist/react/forms.cjs.map +1 -1
  108. package/dist/react/forms.d.cts +1 -1
  109. package/dist/react/forms.d.ts +1 -1
  110. package/dist/react/forms.js +139 -46
  111. package/dist/react/forms.js.map +1 -1
  112. package/dist/react/host.cjs +910 -0
  113. package/dist/react/host.cjs.map +1 -0
  114. package/dist/react/host.d.cts +24 -0
  115. package/dist/react/host.d.ts +24 -0
  116. package/dist/react/host.js +875 -0
  117. package/dist/react/host.js.map +1 -0
  118. package/dist/react/input.d.cts +1 -1
  119. package/dist/react/input.d.ts +1 -1
  120. package/dist/react/media.cjs +426 -0
  121. package/dist/react/media.cjs.map +1 -1
  122. package/dist/react/media.d.cts +19 -1
  123. package/dist/react/media.d.ts +19 -1
  124. package/dist/react/media.js +423 -0
  125. package/dist/react/media.js.map +1 -1
  126. package/dist/react/progress.cjs +471 -2
  127. package/dist/react/progress.cjs.map +1 -1
  128. package/dist/react/progress.d.cts +1 -1
  129. package/dist/react/progress.d.ts +1 -1
  130. package/dist/react/progress.js +469 -3
  131. package/dist/react/progress.js.map +1 -1
  132. package/dist/server/resource.cjs +151 -0
  133. package/dist/server/resource.cjs.map +1 -0
  134. package/dist/server/resource.d.cts +90 -0
  135. package/dist/server/resource.d.ts +90 -0
  136. package/dist/server/resource.js +147 -0
  137. package/dist/server/resource.js.map +1 -0
  138. package/dist/server.cjs +1100 -25
  139. package/dist/server.cjs.map +1 -1
  140. package/dist/server.d.cts +84 -5
  141. package/dist/server.d.ts +84 -5
  142. package/dist/server.js +1087 -27
  143. package/dist/server.js.map +1 -1
  144. package/dist/tokens.cjs +73 -22
  145. package/dist/tokens.cjs.map +1 -1
  146. package/dist/tokens.d.cts +23 -3
  147. package/dist/tokens.d.ts +23 -3
  148. package/dist/tokens.js +73 -22
  149. package/dist/tokens.js.map +1 -1
  150. package/package.json +250 -231
  151. package/registry.yaml +51 -2
  152. package/dist/Alert.schema-LPJE5gFP.d.cts +0 -71
  153. package/dist/Alert.schema-LPJE5gFP.d.ts +0 -71
  154. package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +0 -22
  155. package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +0 -22
  156. package/dist/{ConfirmDialog-B3CQQGmJ.d.cts → ConfirmDialog-BCtOcmHe.d.cts} +1 -1
  157. package/dist/{ConfirmDialog-B3CQQGmJ.d.ts → ConfirmDialog-BCtOcmHe.d.ts} +1 -1
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { useVirtualizer } from '@tanstack/react-virtual';
4
- import { useRef, useCallback, useState, useEffect } from 'react';
4
+ import { useRef, useCallback, useState, useEffect, useMemo } from 'react';
5
5
 
6
6
  // src/components/display/EmptyState.logic.ts
7
7
  var ACTION_VARIANT_CLASSES = {
@@ -43,6 +43,8 @@ var EN = {
43
43
  "TokenDisplayOnceModal.copied": "Copied",
44
44
  "StatusBadge.aria.status": "Status",
45
45
  "StatusBadge.error.invalidProps": "StatusBadge: invalid props",
46
+ "Badge.aria.label": "Badge",
47
+ "Badge.error.invalidProps": "Badge: invalid props",
46
48
  "EmptyState.error.invalidProps": "EmptyState: invalid props",
47
49
  "Toast.aria.close": "Close notification",
48
50
  "Toast.error.invalidProps": "Toast: invalid props",
@@ -112,7 +114,99 @@ var EN = {
112
114
  "Select.search.placeholder": "Search\u2026",
113
115
  "Select.empty": "No options available",
114
116
  "Select.error.required": "Select an option",
115
- "Select.error.invalidProps": "Select: invalid props"
117
+ "Select.error.invalidProps": "Select: invalid props",
118
+ "ModelContext.selection.one": "1 row selected",
119
+ "ModelContext.selection.many": "{n} rows selected",
120
+ "ModelContext.selection.none": "No rows selected",
121
+ "TableView.sort.by": "Sort by",
122
+ "TableView.select.row": "Select row",
123
+ "TableView.select.all": "Select all rows",
124
+ "TableView.loading": "Loading table",
125
+ "StatCard.title": "Statistic",
126
+ "StatCard.trend.up": "Up",
127
+ "StatCard.trend.down": "Down",
128
+ "StatCard.trend.flat": "Flat",
129
+ "StatCard.empty.message": "No statistic to display",
130
+ "StatCard.loading": "Loading statistic",
131
+ "StatCard.error.invalidProps": "StatCard: invalid props",
132
+ "BalanceCard.title": "Balance",
133
+ "BalanceCard.network": "Network",
134
+ "BalanceCard.testnet": "Testnet",
135
+ "BalanceCard.address": "Address",
136
+ "BalanceCard.contract": "Token contract",
137
+ "BalanceCard.value": "Value",
138
+ "BalanceCard.copy": "Copy address",
139
+ "BalanceCard.copied": "Address copied",
140
+ "BalanceCard.empty.message": "No balance to display",
141
+ "BalanceCard.loading": "Loading balance",
142
+ "BalanceCard.error.invalidProps": "BalanceCard: invalid props",
143
+ "Timeline.title": "Timeline",
144
+ "Timeline.status.done": "Done",
145
+ "Timeline.status.current": "In progress",
146
+ "Timeline.status.pending": "Pending",
147
+ "Timeline.status.blocked": "Blocked",
148
+ "Timeline.completed": "{n} completed",
149
+ "Timeline.more": "{n} more",
150
+ "Timeline.empty.message": "No steps to display",
151
+ "Timeline.loading": "Loading timeline",
152
+ "Timeline.error.invalidProps": "Timeline: invalid props",
153
+ "MessageFeed.title": "Messages",
154
+ "MessageFeed.unread": "Unread",
155
+ "MessageFeed.attachment": "Attachment",
156
+ "MessageFeed.showing": "Showing the latest {n} of {total}",
157
+ "MessageFeed.empty.message": "No messages",
158
+ "MessageFeed.loading": "Loading messages",
159
+ "MessageFeed.error.invalidProps": "MessageFeed: invalid props",
160
+ "PipelineBoard.title": "Pipeline",
161
+ "PipelineBoard.deals.one": "1 deal",
162
+ "PipelineBoard.deals.many": "{n} deals",
163
+ "PipelineBoard.total": "Total",
164
+ "PipelineBoard.stage.empty": "No deals",
165
+ "PipelineBoard.filter.label": "Show stages",
166
+ "PipelineBoard.summary.open": "Open full board",
167
+ "PipelineBoard.more": "{n} more stages",
168
+ "PipelineBoard.empty.message": "No pipeline to display",
169
+ "PipelineBoard.loading": "Loading pipeline",
170
+ "PipelineBoard.error.invalidProps": "PipelineBoard: invalid props",
171
+ "PipelineBoard.col.stage": "Stage",
172
+ "PipelineBoard.col.deals": "Deals",
173
+ "TransactionPreview.title": "Transaction preview",
174
+ "TransactionPreview.notice": "NOT SIGNED - preparation only",
175
+ "TransactionPreview.kind.transfer": "Transfer",
176
+ "TransactionPreview.kind.swap": "Swap",
177
+ "TransactionPreview.testnet": "Testnet",
178
+ "TransactionPreview.field.network": "Network",
179
+ "TransactionPreview.field.from": "From",
180
+ "TransactionPreview.field.to": "To",
181
+ "TransactionPreview.field.amount": "Amount",
182
+ "TransactionPreview.field.fee": "Network fee",
183
+ "TransactionPreview.field.sell": "Sell",
184
+ "TransactionPreview.field.buy": "Buy",
185
+ "TransactionPreview.field.minReceived": "Minimum received",
186
+ "TransactionPreview.field.route": "Route",
187
+ "TransactionPreview.field.priceImpact": "Price impact",
188
+ "TransactionPreview.field.expires": "Quote expires",
189
+ "TransactionPreview.priceImpact.high": "High price impact",
190
+ "TransactionPreview.simulation.success": "Simulation succeeded",
191
+ "TransactionPreview.simulation.failed": "Simulation failed",
192
+ "TransactionPreview.simulation.unavailable": "Simulation unavailable",
193
+ "TransactionPreview.warnings": "Warnings",
194
+ "TransactionPreview.handoff": "Open in wallet",
195
+ "TransactionPreview.empty.message": "No transaction to preview",
196
+ "TransactionPreview.loading": "Loading preview",
197
+ "TransactionPreview.error.invalidProps": "TransactionPreview: invalid props",
198
+ "ProgressBar.title": "Progress",
199
+ "ConfirmDialog.title": "Confirmation",
200
+ "ConfirmDialog.options": "Answers",
201
+ "TableView.title": "Data table",
202
+ "TableView.markdown.more": "{n} more rows",
203
+ "MarkdownRenderer.title": "Markdown content",
204
+ "TokenDisplayOnceModal.title": "Token (shown once)",
205
+ "StatusBadge.title": "Status",
206
+ "TokenDisplayOnceModal.notice.once": "The value is shown once, in the app view only.",
207
+ "TokenDisplayOnceModal.field.scope": "Scope",
208
+ "TokenDisplayOnceModal.field.expires": "Expires",
209
+ "TokenDisplayOnceModal.field.fingerprint": "Fingerprint"
116
210
  };
117
211
  var FR = {
118
212
  // ─── Non-forms components ──────────────────────────────────────────────────
@@ -135,6 +229,8 @@ var FR = {
135
229
  "TokenDisplayOnceModal.copied": "Copi\xE9",
136
230
  "StatusBadge.aria.status": "Statut",
137
231
  "StatusBadge.error.invalidProps": "StatusBadge : propri\xE9t\xE9s invalides",
232
+ "Badge.aria.label": "Badge",
233
+ "Badge.error.invalidProps": "Badge : propri\xE9t\xE9s invalides",
138
234
  "EmptyState.error.invalidProps": "EmptyState : propri\xE9t\xE9s invalides",
139
235
  "Toast.aria.close": "Fermer la notification",
140
236
  "Toast.error.invalidProps": "Toast : propri\xE9t\xE9s invalides",
@@ -204,7 +300,99 @@ var FR = {
204
300
  "Select.search.placeholder": "Rechercher\u2026",
205
301
  "Select.empty": "Aucune option disponible",
206
302
  "Select.error.required": "S\xE9lectionnez une option",
207
- "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides"
303
+ "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides",
304
+ "ModelContext.selection.one": "1 ligne s\xE9lectionn\xE9e",
305
+ "ModelContext.selection.many": "{n} lignes s\xE9lectionn\xE9es",
306
+ "ModelContext.selection.none": "Aucune ligne s\xE9lectionn\xE9e",
307
+ "TableView.sort.by": "Trier par",
308
+ "TableView.select.row": "S\xE9lectionner la ligne",
309
+ "TableView.select.all": "Tout s\xE9lectionner",
310
+ "TableView.loading": "Chargement du tableau",
311
+ "StatCard.title": "Statistique",
312
+ "StatCard.trend.up": "En hausse",
313
+ "StatCard.trend.down": "En baisse",
314
+ "StatCard.trend.flat": "Stable",
315
+ "StatCard.empty.message": "Aucune statistique \xE0 afficher",
316
+ "StatCard.loading": "Chargement de la statistique",
317
+ "StatCard.error.invalidProps": "StatCard : propri\xE9t\xE9s invalides",
318
+ "BalanceCard.title": "Solde",
319
+ "BalanceCard.network": "R\xE9seau",
320
+ "BalanceCard.testnet": "R\xE9seau de test",
321
+ "BalanceCard.address": "Adresse",
322
+ "BalanceCard.contract": "Contrat du jeton",
323
+ "BalanceCard.value": "Valeur",
324
+ "BalanceCard.copy": "Copier l'adresse",
325
+ "BalanceCard.copied": "Adresse copi\xE9e",
326
+ "BalanceCard.empty.message": "Aucun solde \xE0 afficher",
327
+ "BalanceCard.loading": "Chargement du solde",
328
+ "BalanceCard.error.invalidProps": "BalanceCard : propri\xE9t\xE9s invalides",
329
+ "Timeline.title": "Chronologie",
330
+ "Timeline.status.done": "Termin\xE9",
331
+ "Timeline.status.current": "En cours",
332
+ "Timeline.status.pending": "\xC0 venir",
333
+ "Timeline.status.blocked": "Bloqu\xE9",
334
+ "Timeline.completed": "{n} termin\xE9(s)",
335
+ "Timeline.more": "{n} de plus",
336
+ "Timeline.empty.message": "Aucune \xE9tape \xE0 afficher",
337
+ "Timeline.loading": "Chargement de la chronologie",
338
+ "Timeline.error.invalidProps": "Timeline : propri\xE9t\xE9s invalides",
339
+ "MessageFeed.title": "Messages",
340
+ "MessageFeed.unread": "Non lu",
341
+ "MessageFeed.attachment": "Pi\xE8ce jointe",
342
+ "MessageFeed.showing": "Affichage des {n} derniers sur {total}",
343
+ "MessageFeed.empty.message": "Aucun message",
344
+ "MessageFeed.loading": "Chargement des messages",
345
+ "MessageFeed.error.invalidProps": "MessageFeed : propri\xE9t\xE9s invalides",
346
+ "PipelineBoard.title": "Pipeline",
347
+ "PipelineBoard.deals.one": "1 affaire",
348
+ "PipelineBoard.deals.many": "{n} affaires",
349
+ "PipelineBoard.total": "Total",
350
+ "PipelineBoard.stage.empty": "Aucune affaire",
351
+ "PipelineBoard.filter.label": "Afficher les \xE9tapes",
352
+ "PipelineBoard.summary.open": "Ouvrir le tableau complet",
353
+ "PipelineBoard.more": "{n} \xE9tapes de plus",
354
+ "PipelineBoard.empty.message": "Aucun pipeline \xE0 afficher",
355
+ "PipelineBoard.loading": "Chargement du pipeline",
356
+ "PipelineBoard.error.invalidProps": "PipelineBoard : propri\xE9t\xE9s invalides",
357
+ "PipelineBoard.col.stage": "\xC9tape",
358
+ "PipelineBoard.col.deals": "Affaires",
359
+ "TransactionPreview.title": "Aper\xE7u de transaction",
360
+ "TransactionPreview.notice": "NON SIGN\xC9 - pr\xE9paration uniquement",
361
+ "TransactionPreview.kind.transfer": "Transfert",
362
+ "TransactionPreview.kind.swap": "\xC9change",
363
+ "TransactionPreview.testnet": "R\xE9seau de test",
364
+ "TransactionPreview.field.network": "R\xE9seau",
365
+ "TransactionPreview.field.from": "De",
366
+ "TransactionPreview.field.to": "\xC0",
367
+ "TransactionPreview.field.amount": "Montant",
368
+ "TransactionPreview.field.fee": "Frais de r\xE9seau",
369
+ "TransactionPreview.field.sell": "Vendre",
370
+ "TransactionPreview.field.buy": "Acheter",
371
+ "TransactionPreview.field.minReceived": "Minimum re\xE7u",
372
+ "TransactionPreview.field.route": "Itin\xE9raire",
373
+ "TransactionPreview.field.priceImpact": "Impact sur le prix",
374
+ "TransactionPreview.field.expires": "Expiration du devis",
375
+ "TransactionPreview.priceImpact.high": "Impact sur le prix \xE9lev\xE9",
376
+ "TransactionPreview.simulation.success": "Simulation r\xE9ussie",
377
+ "TransactionPreview.simulation.failed": "Simulation \xE9chou\xE9e",
378
+ "TransactionPreview.simulation.unavailable": "Simulation indisponible",
379
+ "TransactionPreview.warnings": "Avertissements",
380
+ "TransactionPreview.handoff": "Ouvrir dans le portefeuille",
381
+ "TransactionPreview.empty.message": "Aucune transaction \xE0 pr\xE9visualiser",
382
+ "TransactionPreview.loading": "Chargement de l'aper\xE7u",
383
+ "TransactionPreview.error.invalidProps": "TransactionPreview : propri\xE9t\xE9s invalides",
384
+ "ProgressBar.title": "Progression",
385
+ "ConfirmDialog.title": "Confirmation",
386
+ "ConfirmDialog.options": "R\xE9ponses",
387
+ "TableView.title": "Tableau de donn\xE9es",
388
+ "TableView.markdown.more": "{n} lignes de plus",
389
+ "MarkdownRenderer.title": "Contenu Markdown",
390
+ "TokenDisplayOnceModal.title": "Jeton (affich\xE9 une fois)",
391
+ "StatusBadge.title": "Statut",
392
+ "TokenDisplayOnceModal.notice.once": "La valeur n'est affich\xE9e qu'une fois, dans la vue de l'application uniquement.",
393
+ "TokenDisplayOnceModal.field.scope": "Port\xE9e",
394
+ "TokenDisplayOnceModal.field.expires": "Expire le",
395
+ "TokenDisplayOnceModal.field.fingerprint": "Empreinte"
208
396
  };
209
397
  var TABLE = {
210
398
  en: EN,
@@ -520,92 +708,226 @@ function StatusBadge(raw) {
520
708
  return /* @__PURE__ */ jsx("span", { role: "alert", children: t("StatusBadge.error.invalidProps", locale) });
521
709
  }
522
710
  }
711
+ var BadgePropsSchema = z.object({
712
+ label: z.string(),
713
+ variant: z.enum(["default", "primary", "success", "warning", "danger", "info", "neutral"]).default("default"),
714
+ dot: z.boolean().optional(),
715
+ count: z.number().int().nonnegative().optional(),
716
+ locale: z.enum(["en", "fr"]).default("en")
717
+ });
718
+ function validateBadgeProps(raw) {
719
+ return BadgePropsSchema.parse(raw);
720
+ }
721
+ var variantClasses2 = {
722
+ default: "bg-slate-100 text-slate-700 ring-1 ring-slate-200",
723
+ primary: "bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300",
724
+ success: "bg-green-100 text-green-800 ring-1 ring-green-300",
725
+ warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
726
+ danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
727
+ info: "bg-blue-100 text-blue-800 ring-1 ring-blue-300",
728
+ neutral: "bg-slate-100 text-slate-800 ring-1 ring-slate-300"
729
+ };
730
+ var dotClasses = {
731
+ default: "bg-slate-400",
732
+ primary: "bg-indigo-500",
733
+ success: "bg-green-500",
734
+ warning: "bg-amber-500",
735
+ danger: "bg-red-500",
736
+ info: "bg-blue-500",
737
+ neutral: "bg-slate-400"
738
+ };
739
+ function BadgeInner({ label, variant, dot, count, locale }) {
740
+ const ariaLabel = t("Badge.aria.label", locale);
741
+ const displayCount = count !== void 0 ? ` (${count})` : "";
742
+ return /* @__PURE__ */ jsxs(
743
+ "span",
744
+ {
745
+ "aria-label": `${ariaLabel}: ${label}${displayCount}`,
746
+ lang: locale,
747
+ className: `inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses2[variant]}`,
748
+ children: [
749
+ dot && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: `h-1.5 w-1.5 rounded-full ${dotClasses[variant]}` }),
750
+ label,
751
+ count !== void 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "font-semibold tabular-nums", children: count })
752
+ ]
753
+ }
754
+ );
755
+ }
756
+ function Badge(raw) {
757
+ try {
758
+ const props = validateBadgeProps(raw);
759
+ return /* @__PURE__ */ jsx(BadgeInner, { ...props });
760
+ } catch {
761
+ const locale = raw.locale === "fr" ? "fr" : "en";
762
+ return /* @__PURE__ */ jsx("span", { role: "alert", children: t("Badge.error.invalidProps", locale) });
763
+ }
764
+ }
765
+
766
+ // src/components/display/TableView.logic.ts
767
+ function isMissing(v) {
768
+ return v === null || v === void 0 || v === "";
769
+ }
770
+ var DECIMAL = /^(-)?(\d+)(?:\.(\d+))?$/;
771
+ function parseDecimal(s) {
772
+ const m = DECIMAL.exec(s.trim());
773
+ if (!m) return void 0;
774
+ const int = (m[2] ?? "0").replace(/^0+(?=\d)/, "");
775
+ const frac = (m[3] ?? "").replace(/0+$/, "");
776
+ const zero = int === "0" && frac === "";
777
+ return { neg: m[1] === "-" && !zero, int, frac };
778
+ }
779
+ function compareDecimalStrings(a, b) {
780
+ const x = parseDecimal(a);
781
+ const y = parseDecimal(b);
782
+ if (!x || !y) return 0;
783
+ if (x.neg !== y.neg) return x.neg ? -1 : 1;
784
+ const sign = x.neg ? -1 : 1;
785
+ if (x.int.length !== y.int.length) return sign * (x.int.length < y.int.length ? -1 : 1);
786
+ if (x.int !== y.int) return sign * (x.int < y.int ? -1 : 1);
787
+ const len = Math.max(x.frac.length, y.frac.length);
788
+ const fx = x.frac.padEnd(len, "0");
789
+ const fy = y.frac.padEnd(len, "0");
790
+ if (fx === fy) return 0;
791
+ return sign * (fx < fy ? -1 : 1);
792
+ }
793
+ var collators = /* @__PURE__ */ new Map();
794
+ function collator(locale) {
795
+ let c = collators.get(locale);
796
+ if (!c) {
797
+ c = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
798
+ collators.set(locale, c);
799
+ }
800
+ return c;
801
+ }
802
+ function compareCells(a, b, kind, locale) {
803
+ if (kind === "amount") return compareDecimalStrings(String(a), String(b));
804
+ if (kind === "number") {
805
+ const x = Number(a);
806
+ const y = Number(b);
807
+ if (Number.isNaN(x) || Number.isNaN(y)) return collator(locale).compare(String(a), String(b));
808
+ return x === y ? 0 : x < y ? -1 : 1;
809
+ }
810
+ return collator(locale).compare(String(a), String(b));
811
+ }
812
+ function sortItems(items, getValue, kind, direction, locale) {
813
+ const sign = direction === "asc" ? 1 : -1;
814
+ return items.map((item, index) => ({ item, index, value: getValue(item) })).sort((p, q) => {
815
+ const pm = isMissing(p.value);
816
+ const qm = isMissing(q.value);
817
+ if (pm || qm) return pm && qm ? p.index - q.index : pm ? 1 : -1;
818
+ const c = compareCells(p.value, q.value, kind, locale);
819
+ return c !== 0 ? sign * c : p.index - q.index;
820
+ }).map((e) => e.item);
821
+ }
822
+ function nextSort(current, key) {
823
+ if (!current || current.key !== key) return { key, direction: "asc" };
824
+ return current.direction === "asc" ? { key, direction: "desc" } : void 0;
825
+ }
826
+ function toggleSelection(current, key, mode) {
827
+ if (mode === "none") return new Set(current);
828
+ if (mode === "single") return current.has(key) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([key]);
829
+ const next = new Set(current);
830
+ if (!next.delete(key)) next.add(key);
831
+ return next;
832
+ }
833
+ function toggleAll(current, allKeys) {
834
+ return allKeys.length > 0 && allKeys.every((k) => current.has(k)) ? /* @__PURE__ */ new Set() : new Set(allKeys);
835
+ }
836
+ var ColumnKindSchema = z.enum(["text", "number", "amount", "icon"]);
837
+ var AlignSchema = z.enum(["start", "center", "end"]);
523
838
  var ColumnDefSchema = z.object({
524
839
  key: z.string().min(1),
525
- header: z.string().min(1)
840
+ header: z.string().min(1),
841
+ /** Cell kind: drives comparison and rendering. `amount` = decimal STRING, never a float. */
842
+ type: ColumnKindSchema.default("text"),
843
+ /** Defaults to `end` for `number` / `amount` columns, `start` otherwise. */
844
+ align: AlignSchema.optional(),
845
+ /** CSS width: a number is pixels. */
846
+ width: z.union([z.string().min(1), z.number().positive()]).optional(),
847
+ sortable: z.boolean().default(false),
848
+ /**
849
+ * Values of this column are masked in the A-1 markdown fallback and kept out of the HTML
850
+ * props of a tool result (delivered via the result `_meta`), never written to `content[]`.
851
+ */
852
+ sensitive: z.boolean().default(false)
526
853
  // render is a runtime function — excluded from Zod, typed separately
527
854
  });
855
+ var SortSchema = z.object({ key: z.string().min(1), direction: z.enum(["asc", "desc"]) });
528
856
  var TableViewPropsSchema = z.object({
529
857
  columns: z.array(ColumnDefSchema).min(1, "TableView requires at least one column"),
858
+ /**
859
+ * Serialisable rows, for views built through `createMosaicResource` (the React props carry
860
+ * `rows` separately).
861
+ */
862
+ rows: z.array(z.record(z.unknown())).default([]),
530
863
  virtualizeThreshold: z.number().int().positive().default(100),
531
864
  ariaLabel: z.string().min(1),
532
- locale: z.enum(["en", "fr"]).default("en")
865
+ locale: z.enum(["en", "fr"]).default("en"),
866
+ selectionMode: z.enum(["none", "single", "multiple"]).default("none"),
867
+ /** Initial sort. */
868
+ sort: SortSchema.optional(),
869
+ stickyHeader: z.boolean().default(false),
870
+ /** Scroll region height when `stickyHeader` is on (pixels). */
871
+ maxHeight: z.number().int().positive().default(400),
872
+ density: z.enum(["comfortable", "compact"]).default("comfortable"),
873
+ loading: z.boolean().default(false)
533
874
  });
534
875
  function validateTableViewProps(raw) {
535
876
  return TableViewPropsSchema.parse(raw);
536
877
  }
537
878
  var OVERSCAN = 5;
538
- function TableView(props) {
539
- let validatedThreshold = 100;
879
+ var SKELETON_ROWS = ["head", "r1", "r2", "r3", "r4", "r5"];
880
+ function resolveLocale(l) {
881
+ return l === "fr" ? "fr" : "en";
882
+ }
883
+ function validate(props) {
540
884
  try {
541
885
  const validated = validateTableViewProps({
542
- columns: props.columns.map(({ key, header }) => ({ key, header })),
886
+ columns: props.columns.map(({ key, header, type, align, width, sortable }) => ({
887
+ key,
888
+ header,
889
+ type,
890
+ align,
891
+ width,
892
+ sortable
893
+ })),
543
894
  virtualizeThreshold: props.virtualizeThreshold,
544
895
  ariaLabel: props.ariaLabel,
545
- locale: props.locale
896
+ locale: props.locale,
897
+ selectionMode: props.selectionMode,
898
+ density: props.density
546
899
  });
547
- validatedThreshold = validated.virtualizeThreshold;
900
+ return validated.virtualizeThreshold;
548
901
  } catch {
549
- const locale = props.locale === "fr" ? "fr" : "en";
550
- return /* @__PURE__ */ jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
902
+ return void 0;
551
903
  }
552
- const isVirtual = props.rows.length > validatedThreshold;
553
- const resolvedLocale = props.locale === "fr" ? "fr" : "en";
554
- if (isVirtual) {
555
- return /* @__PURE__ */ jsx(
556
- VirtualTable,
557
- {
558
- rows: props.rows,
559
- columns: props.columns,
560
- ariaLabel: props.ariaLabel,
561
- locale: resolvedLocale,
562
- onRowClick: props.onRowClick
563
- }
564
- );
904
+ }
905
+ function TableView(props) {
906
+ const threshold = validate(props);
907
+ if (threshold === void 0) {
908
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
565
909
  }
566
910
  return /* @__PURE__ */ jsx(
567
- StaticTable,
911
+ TableCore,
568
912
  {
913
+ ...props,
569
914
  rows: props.rows,
570
- columns: props.columns,
571
- ariaLabel: props.ariaLabel,
572
- locale: resolvedLocale,
573
- onRowClick: props.onRowClick
915
+ virtualizeThreshold: threshold,
916
+ locale: resolveLocale(props.locale)
574
917
  }
575
918
  );
576
919
  }
577
920
  function StreamingTableView(props) {
578
- let validatedThreshold = 100;
579
- try {
580
- const validated = validateTableViewProps({
581
- columns: props.columns.map(({ key, header }) => ({ key, header })),
582
- virtualizeThreshold: props.virtualizeThreshold,
583
- ariaLabel: props.ariaLabel,
584
- locale: props.locale
585
- });
586
- validatedThreshold = validated.virtualizeThreshold;
587
- } catch {
588
- const locale = props.locale === "fr" ? "fr" : "en";
589
- return /* @__PURE__ */ jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
921
+ const threshold = validate(props);
922
+ if (threshold === void 0) {
923
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
590
924
  }
591
- return /* @__PURE__ */ jsx(
592
- StreamingTableViewInner,
593
- {
594
- ...props,
595
- virtualizeThreshold: validatedThreshold,
596
- onRowClick: props.onRowClick
597
- }
598
- );
925
+ return /* @__PURE__ */ jsx(StreamingTableViewInner, { ...props, virtualizeThreshold: threshold });
599
926
  }
600
927
  function StreamingTableViewInner({
601
- columns,
602
928
  rows$,
603
- virtualizeThreshold = 100,
604
- ariaLabel,
605
- locale,
606
- onRowClick
929
+ ...rest
607
930
  }) {
608
- const resolvedLocale = locale === "fr" ? "fr" : "en";
609
931
  const [rows, setRows] = useState([]);
610
932
  useEffect(() => {
611
933
  setRows([]);
@@ -616,87 +938,282 @@ function StreamingTableViewInner({
616
938
  sub.unsubscribe();
617
939
  };
618
940
  }, [rows$]);
619
- const isVirtual = rows.length > virtualizeThreshold;
620
- if (isVirtual) {
941
+ return /* @__PURE__ */ jsx(TableCore, { ...rest, rows, locale: resolveLocale(rest.locale) });
942
+ }
943
+ function kindOf(col) {
944
+ return col.type ?? "text";
945
+ }
946
+ function alignOf(col) {
947
+ return col.align ?? (col.type === "amount" || col.type === "number" ? "end" : "start");
948
+ }
949
+ function cellText(row, key) {
950
+ return String(row[key] ?? "");
951
+ }
952
+ function safeIconSrc(v) {
953
+ if (typeof v !== "string") return void 0;
954
+ return /^(https?:\/\/|data:image\/)/i.test(v) ? v : void 0;
955
+ }
956
+ function renderCell(col, row) {
957
+ if (col.render) {
958
+ return col.render(row);
959
+ }
960
+ const value = row[col.key];
961
+ if (col.type === "icon") {
962
+ const src = safeIconSrc(value);
963
+ return src ? /* @__PURE__ */ jsx("img", { src, alt: "", width: 20, height: 20, style: { verticalAlign: "middle" } }) : null;
964
+ }
965
+ if (col.type === "amount" || col.type === "number") {
966
+ return /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: String(value ?? "") });
967
+ }
968
+ return String(value ?? "");
969
+ }
970
+ function TableCore(props) {
971
+ const {
972
+ columns,
973
+ rows,
974
+ ariaLabel,
975
+ locale,
976
+ selectionMode = "none",
977
+ stickyHeader = false,
978
+ density = "comfortable"
979
+ } = props;
980
+ const [innerSort, setInnerSort] = useState(props.defaultSort);
981
+ const sort = props.sort === void 0 ? innerSort : props.sort ?? void 0;
982
+ const onSort = (key) => {
983
+ const next = nextSort(sort, key);
984
+ if (props.sort === void 0) setInnerSort(next);
985
+ props.onSortChange?.(next);
986
+ };
987
+ const items = useMemo(
988
+ () => rows.map((row, i) => {
989
+ const id = row.id;
990
+ return { row, key: id != null ? String(id) : `row-${i}` };
991
+ }),
992
+ [rows]
993
+ );
994
+ const sorted = useMemo(() => {
995
+ const col = sort ? columns.find((c) => c.key === sort.key) : void 0;
996
+ if (!sort || !col) return items;
997
+ return sortItems(
998
+ items,
999
+ (it) => it.row[col.key],
1000
+ kindOf(col),
1001
+ sort.direction,
1002
+ locale
1003
+ );
1004
+ }, [items, sort, columns, locale]);
1005
+ const [innerSelected, setInnerSelected] = useState(
1006
+ () => new Set(props.defaultSelectedKeys ?? [])
1007
+ );
1008
+ const selected = props.selectedKeys ? new Set(props.selectedKeys) : innerSelected;
1009
+ const commit = (next) => {
1010
+ if (!props.selectedKeys) setInnerSelected(next);
1011
+ const chosen = sorted.filter((it) => next.has(it.key));
1012
+ props.onSelectionChange?.(
1013
+ chosen.map((it) => it.row),
1014
+ chosen.map((it) => it.key)
1015
+ );
1016
+ };
1017
+ const selection = selectionMode === "none" ? void 0 : {
1018
+ mode: selectionMode,
1019
+ selected,
1020
+ toggle: (key) => commit(toggleSelection(selected, key, selectionMode)),
1021
+ toggleAll: () => commit(
1022
+ toggleAll(
1023
+ selected,
1024
+ sorted.map((it) => it.key)
1025
+ )
1026
+ )
1027
+ };
1028
+ if (props.loading) {
1029
+ return /* @__PURE__ */ jsx("div", { "aria-busy": "true", "aria-label": t("TableView.loading", locale), lang: locale, children: SKELETON_ROWS.map((_, i) => /* @__PURE__ */ jsx(
1030
+ Skeleton,
1031
+ {
1032
+ variant: "rect",
1033
+ height: i === 0 ? 24 : 36,
1034
+ locale
1035
+ },
1036
+ SKELETON_ROWS[i]
1037
+ )) });
1038
+ }
1039
+ const content = {
1040
+ items: sorted,
1041
+ columns,
1042
+ ariaLabel,
1043
+ locale,
1044
+ onRowClick: props.onRowClick,
1045
+ sort,
1046
+ onSort,
1047
+ selection,
1048
+ sticky: stickyHeader,
1049
+ density
1050
+ };
1051
+ const isVirtual = rows.length > props.virtualizeThreshold;
1052
+ if (isVirtual) return /* @__PURE__ */ jsx(VirtualTable, { ...content });
1053
+ if (stickyHeader) {
621
1054
  return /* @__PURE__ */ jsx(
622
- VirtualTable,
1055
+ "div",
623
1056
  {
624
- rows,
625
- columns,
626
- ariaLabel,
627
- locale: resolvedLocale,
628
- onRowClick
1057
+ tabIndex: 0,
1058
+ role: "region",
1059
+ "aria-label": ariaLabel,
1060
+ style: { maxHeight: props.maxHeight ?? 400, overflow: "auto" },
1061
+ children: /* @__PURE__ */ jsx(StaticTable, { ...content })
629
1062
  }
630
1063
  );
631
1064
  }
1065
+ return /* @__PURE__ */ jsx(StaticTable, { ...content });
1066
+ }
1067
+ var CELL_PADDING = { comfortable: void 0, compact: "2px 6px" };
1068
+ function HeaderRow({
1069
+ columns,
1070
+ locale,
1071
+ sort,
1072
+ onSort,
1073
+ selection,
1074
+ sticky,
1075
+ density,
1076
+ items
1077
+ }) {
1078
+ const padding = CELL_PADDING[density];
1079
+ const stickyStyle = sticky ? { position: "sticky", top: 0, background: "var(--mosaic-color-background, Canvas)" } : {};
1080
+ const allSelected = items.length > 0 && items.every((it) => selection?.selected.has(it.key));
1081
+ const someSelected = items.some((it) => selection?.selected.has(it.key));
1082
+ return /* @__PURE__ */ jsxs("tr", { children: [
1083
+ selection ? /* @__PURE__ */ jsx("th", { scope: "col", style: { ...stickyStyle, padding, width: 32 }, children: selection.mode === "multiple" ? /* @__PURE__ */ jsx(
1084
+ "input",
1085
+ {
1086
+ type: "checkbox",
1087
+ "aria-label": t("TableView.select.all", locale),
1088
+ checked: allSelected,
1089
+ ref: (el) => {
1090
+ if (el) el.indeterminate = someSelected && !allSelected;
1091
+ },
1092
+ onChange: selection.toggleAll
1093
+ }
1094
+ ) : null }) : null,
1095
+ columns.map((col) => {
1096
+ const dir = sort?.key === col.key ? sort.direction : void 0;
1097
+ return /* @__PURE__ */ jsx(
1098
+ "th",
1099
+ {
1100
+ scope: "col",
1101
+ "aria-sort": col.sortable ? dir === "asc" ? "ascending" : dir === "desc" ? "descending" : "none" : void 0,
1102
+ style: {
1103
+ ...stickyStyle,
1104
+ padding,
1105
+ textAlign: alignOf(col),
1106
+ ...col.width !== void 0 ? { width: col.width } : {}
1107
+ },
1108
+ children: col.sortable ? /* @__PURE__ */ jsxs(
1109
+ "button",
1110
+ {
1111
+ type: "button",
1112
+ "aria-label": `${t("TableView.sort.by", locale)} ${col.header}`,
1113
+ onClick: () => onSort(col.key),
1114
+ style: { all: "inherit", cursor: "pointer", padding: 0 },
1115
+ children: [
1116
+ col.header,
1117
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: dir === "asc" ? " \u25B2" : dir === "desc" ? " \u25BC" : "" })
1118
+ ]
1119
+ }
1120
+ ) : col.header
1121
+ },
1122
+ col.key
1123
+ );
1124
+ })
1125
+ ] });
1126
+ }
1127
+ function SelectCell({
1128
+ item,
1129
+ columns,
1130
+ selection,
1131
+ locale,
1132
+ padding
1133
+ }) {
1134
+ const first = columns[0];
1135
+ const label = `${t("TableView.select.row", locale)}: ${first ? cellText(item.row, first.key) : item.key}`;
632
1136
  return /* @__PURE__ */ jsx(
633
- StaticTable,
1137
+ "td",
634
1138
  {
635
- rows,
636
- columns,
637
- ariaLabel,
638
- locale: resolvedLocale,
639
- onRowClick
1139
+ style: { padding, width: 32 },
1140
+ onClick: (e) => e.stopPropagation(),
1141
+ onKeyDown: (e) => e.stopPropagation(),
1142
+ children: /* @__PURE__ */ jsx(
1143
+ "input",
1144
+ {
1145
+ type: selection.mode === "multiple" ? "checkbox" : "radio",
1146
+ name: selection.mode === "single" ? "mosaic-table-selection" : void 0,
1147
+ "aria-label": label,
1148
+ checked: selection.selected.has(item.key),
1149
+ onChange: () => selection.toggle(item.key),
1150
+ onKeyDown: (e) => e.stopPropagation()
1151
+ }
1152
+ )
640
1153
  }
641
1154
  );
642
1155
  }
643
- function renderCell(col, row) {
644
- if (col.render) {
645
- return col.render(row);
646
- }
647
- return String(row[col.key] ?? "");
1156
+ function rowActivation(onRowClick, row, index) {
1157
+ if (!onRowClick) return {};
1158
+ return {
1159
+ role: "button",
1160
+ tabIndex: 0,
1161
+ onClick: () => onRowClick(row, index),
1162
+ onKeyDown: (e) => {
1163
+ if (e.key === "Enter" || e.key === " ") {
1164
+ e.preventDefault();
1165
+ onRowClick(row, index);
1166
+ }
1167
+ }
1168
+ };
648
1169
  }
649
- function StaticTable({
650
- rows,
651
- columns,
652
- ariaLabel,
653
- locale,
654
- onRowClick
655
- }) {
1170
+ function StaticTable(props) {
1171
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
656
1172
  const emptyMsg = t("TableView.empty.message", locale);
1173
+ const padding = CELL_PADDING[density];
1174
+ const colSpan = columns.length + (selection ? 1 : 0);
657
1175
  return /* @__PURE__ */ jsxs(
658
1176
  "table",
659
1177
  {
660
1178
  "aria-label": ariaLabel,
661
- "aria-rowcount": rows.length,
1179
+ "aria-rowcount": items.length,
1180
+ "data-density": density,
662
1181
  style: { width: "100%", borderCollapse: "collapse" },
663
1182
  children: [
664
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
665
- /* @__PURE__ */ jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, "aria-label": emptyMsg, children: emptyMsg }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(
1183
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx(HeaderRow, { ...props }) }),
1184
+ /* @__PURE__ */ jsx("tbody", { children: items.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan, "aria-label": emptyMsg, children: emptyMsg }) }) : items.map((item, rowIndex) => /* @__PURE__ */ jsxs(
666
1185
  "tr",
667
1186
  {
668
1187
  "aria-rowindex": rowIndex + 2,
669
- ...onRowClick ? {
670
- role: "button",
671
- tabIndex: 0,
672
- style: { cursor: "pointer" },
673
- onClick: () => onRowClick(row, rowIndex),
674
- onKeyDown: (e) => {
675
- if (e.key === "Enter" || e.key === " ") {
676
- e.preventDefault();
677
- onRowClick(row, rowIndex);
1188
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
1189
+ ...onRowClick ? { style: { cursor: "pointer" } } : {},
1190
+ ...rowActivation(onRowClick, item.row, rowIndex),
1191
+ children: [
1192
+ selection ? /* @__PURE__ */ jsx(
1193
+ SelectCell,
1194
+ {
1195
+ item,
1196
+ columns,
1197
+ selection,
1198
+ locale,
1199
+ padding
678
1200
  }
679
- }
680
- } : {},
681
- children: columns.map((col) => /* @__PURE__ */ jsx("td", { children: renderCell(col, row) }, col.key))
1201
+ ) : null,
1202
+ columns.map((col) => /* @__PURE__ */ jsx("td", { style: { padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1203
+ ]
682
1204
  },
683
- row.id != null ? String(row.id) : `row-${rowIndex}`
1205
+ item.key
684
1206
  )) })
685
1207
  ]
686
1208
  }
687
1209
  );
688
1210
  }
689
- function VirtualTable({
690
- rows,
691
- columns,
692
- ariaLabel,
693
- // locale reserved for future virtual-mode empty/pagination strings
694
- locale: _locale,
695
- onRowClick
696
- }) {
1211
+ function VirtualTable(props) {
1212
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
697
1213
  const scrollRef = useRef(null);
1214
+ const padding = CELL_PADDING[density];
698
1215
  const rowVirtualizer = useVirtualizer({
699
- count: rows.length,
1216
+ count: items.length,
700
1217
  /* v8 ignore next 1 */
701
1218
  getScrollElement: () => scrollRef.current,
702
1219
  estimateSize: useCallback(() => 40, []),
@@ -709,18 +1226,19 @@ function VirtualTable({
709
1226
  {
710
1227
  "data-virtual": "true",
711
1228
  ref: scrollRef,
712
- tabIndex: 0,
713
1229
  role: "region",
1230
+ tabIndex: 0,
714
1231
  "aria-label": ariaLabel,
715
1232
  style: { height: 400, overflow: "auto", position: "relative" },
716
1233
  children: /* @__PURE__ */ jsxs(
717
1234
  "table",
718
1235
  {
719
1236
  "aria-label": ariaLabel,
720
- "aria-rowcount": rows.length,
1237
+ "aria-rowcount": items.length,
1238
+ "data-density": density,
721
1239
  style: { width: "100%", borderCollapse: "collapse" },
722
1240
  children: [
723
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
1241
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx(HeaderRow, { ...props, sticky: true }) }),
724
1242
  /* @__PURE__ */ jsx(
725
1243
  "tbody",
726
1244
  {
@@ -730,11 +1248,12 @@ function VirtualTable({
730
1248
  display: "block"
731
1249
  },
732
1250
  children: virtualItems.map((virtualRow) => {
733
- const row = rows[virtualRow.index] ?? {};
734
- return /* @__PURE__ */ jsx(
1251
+ const item = items[virtualRow.index] ?? { row: {}, key: "" };
1252
+ return /* @__PURE__ */ jsxs(
735
1253
  "tr",
736
1254
  {
737
1255
  "aria-rowindex": virtualRow.index + 2,
1256
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
738
1257
  style: {
739
1258
  position: "absolute",
740
1259
  top: 0,
@@ -744,20 +1263,22 @@ function VirtualTable({
744
1263
  display: "flex",
745
1264
  ...onRowClick ? { cursor: "pointer" } : {}
746
1265
  },
747
- ...onRowClick ? {
748
- role: "button",
749
- tabIndex: 0,
750
- onClick: () => onRowClick(row, virtualRow.index),
751
- onKeyDown: (e) => {
752
- if (e.key === "Enter" || e.key === " ") {
753
- e.preventDefault();
754
- onRowClick(row, virtualRow.index);
1266
+ ...rowActivation(onRowClick, item.row, virtualRow.index),
1267
+ children: [
1268
+ selection ? /* @__PURE__ */ jsx(
1269
+ SelectCell,
1270
+ {
1271
+ item,
1272
+ columns,
1273
+ selection,
1274
+ locale,
1275
+ padding
755
1276
  }
756
- }
757
- } : {},
758
- children: columns.map((col) => /* @__PURE__ */ jsx("td", { style: { flex: 1 }, children: renderCell(col, row) }, col.key))
1277
+ ) : null,
1278
+ columns.map((col) => /* @__PURE__ */ jsx("td", { style: { flex: 1, padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1279
+ ]
759
1280
  },
760
- row.id != null ? String(row.id) : `vrow-${virtualRow.index}`
1281
+ item.key || `vrow-${virtualRow.index}`
761
1282
  );
762
1283
  })
763
1284
  }
@@ -768,7 +1289,482 @@ function VirtualTable({
768
1289
  }
769
1290
  );
770
1291
  }
1292
+ var StatCardPropsSchema = z.object({
1293
+ label: z.string().default(""),
1294
+ value: z.string().default(""),
1295
+ unit: z.string().optional(),
1296
+ trend: z.enum(["up", "down", "flat"]).optional(),
1297
+ /** Pre-formatted change, e.g. "+4.2%". */
1298
+ change: z.string().optional(),
1299
+ tone: z.enum(["neutral", "success", "warning", "danger"]).default("neutral"),
1300
+ description: z.string().optional(),
1301
+ loading: z.boolean().default(false),
1302
+ locale: z.enum(["en", "fr"]).default("en")
1303
+ });
1304
+ function validateStatCardProps(raw) {
1305
+ return StatCardPropsSchema.parse(raw);
1306
+ }
1307
+ var TONE_CLASSES = {
1308
+ neutral: "text-slate-900",
1309
+ success: "text-green-700",
1310
+ warning: "text-amber-700",
1311
+ danger: "text-red-700"
1312
+ };
1313
+ var TREND_ARROW = { up: "\u25B2", down: "\u25BC", flat: "\u25AC" };
1314
+ function StatCard(raw) {
1315
+ const parsed = StatCardPropsSchema.safeParse(raw);
1316
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1317
+ if (!parsed.success) {
1318
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("StatCard.error.invalidProps", locale) });
1319
+ }
1320
+ const p = parsed.data;
1321
+ if (p.loading) {
1322
+ return /* @__PURE__ */ jsx("div", { "aria-busy": "true", "aria-label": t("StatCard.loading", p.locale), children: /* @__PURE__ */ jsx(Skeleton, { variant: "rect", height: 72, locale: p.locale }) });
1323
+ }
1324
+ if (p.label === "" && p.value === "") {
1325
+ return /* @__PURE__ */ jsx(EmptyState, { title: t("StatCard.empty.message", p.locale), locale: p.locale });
1326
+ }
1327
+ return /* @__PURE__ */ jsxs(
1328
+ "section",
1329
+ {
1330
+ "aria-label": `${t("StatCard.title", p.locale)}: ${p.label}`,
1331
+ lang: p.locale,
1332
+ className: "flex flex-col gap-1 rounded-lg border border-slate-200 bg-white p-4",
1333
+ children: [
1334
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: p.label }),
1335
+ /* @__PURE__ */ jsxs("span", { className: `text-2xl font-semibold tabular-nums ${TONE_CLASSES[p.tone]}`, children: [
1336
+ p.value,
1337
+ p.unit ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-base font-normal text-slate-500", children: p.unit }) : null
1338
+ ] }),
1339
+ p.trend || p.change ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-sm text-slate-600", children: [
1340
+ p.trend ? /* @__PURE__ */ jsxs(Fragment, { children: [
1341
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: TREND_ARROW[p.trend] }),
1342
+ /* @__PURE__ */ jsx("span", { children: t(`StatCard.trend.${p.trend}`, p.locale) })
1343
+ ] }) : null,
1344
+ p.change ? /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: p.change }) : null
1345
+ ] }) : null,
1346
+ p.description ? /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: p.description }) : null
1347
+ ]
1348
+ }
1349
+ );
1350
+ }
1351
+
1352
+ // src/components/shared/decimal.ts
1353
+ var DECIMAL2 = /^(-)?(\d+)(?:\.(\d+))?$/;
1354
+ function sumDecimalStrings(values) {
1355
+ let scale = 0;
1356
+ const parsed = [];
1357
+ for (const v of values) {
1358
+ const m = DECIMAL2.exec(v);
1359
+ if (!m) return void 0;
1360
+ const frac2 = m[3] ?? "";
1361
+ scale = Math.max(scale, frac2.length);
1362
+ parsed.push({ neg: m[1] === "-", int: m[2] ?? "0", frac: frac2 });
1363
+ }
1364
+ let total = 0n;
1365
+ for (const p of parsed) {
1366
+ const n = BigInt(p.int + p.frac.padEnd(scale, "0"));
1367
+ total += p.neg ? -n : n;
1368
+ }
1369
+ const neg = total < 0n;
1370
+ const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
1371
+ const int = digits.slice(0, digits.length - scale);
1372
+ const frac = digits.slice(digits.length - scale).replace(/0+$/, "");
1373
+ return `${neg ? "-" : ""}${int}${frac ? `.${frac}` : ""}`;
1374
+ }
1375
+ function formatDecimalString(value, locale, currency) {
1376
+ if (!DECIMAL2.test(value)) return value;
1377
+ try {
1378
+ const nf = new Intl.NumberFormat(
1379
+ locale,
1380
+ currency ? { style: "currency", currency } : { maximumFractionDigits: 20 }
1381
+ );
1382
+ return nf.format(value);
1383
+ } catch {
1384
+ return value;
1385
+ }
1386
+ }
1387
+ function truncateAddress(address, head = 6, tail = 4) {
1388
+ return address.length <= head + tail + 1 ? address : `${address.slice(0, head)}\u2026${address.slice(address.length - tail)}`;
1389
+ }
1390
+ var BalanceCardPropsSchema = z.object({
1391
+ token: z.string().default(""),
1392
+ balance: z.string().default(""),
1393
+ address: z.string().default(""),
1394
+ tokenAddress: z.string().optional(),
1395
+ decimals: z.number().int().nonnegative().optional(),
1396
+ network: z.string().optional(),
1397
+ testnet: z.boolean().default(false),
1398
+ /** Decimal string in `currency`. */
1399
+ usdValue: z.string().optional(),
1400
+ currency: z.string().default("USD"),
1401
+ loading: z.boolean().default(false),
1402
+ locale: z.enum(["en", "fr"]).default("en")
1403
+ });
1404
+ function validateBalanceCardProps(raw) {
1405
+ return BalanceCardPropsSchema.parse(raw);
1406
+ }
1407
+ function BalanceCard(raw) {
1408
+ const parsed = BalanceCardPropsSchema.safeParse(raw);
1409
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1410
+ const [copied, setCopied] = useState(false);
1411
+ if (!parsed.success) {
1412
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("BalanceCard.error.invalidProps", locale) });
1413
+ }
1414
+ const p = parsed.data;
1415
+ if (p.loading) {
1416
+ return /* @__PURE__ */ jsx("div", { "aria-busy": "true", "aria-label": t("BalanceCard.loading", p.locale), children: /* @__PURE__ */ jsx(Skeleton, { variant: "rect", height: 120, locale: p.locale }) });
1417
+ }
1418
+ if (p.token === "" && p.balance === "") {
1419
+ return /* @__PURE__ */ jsx(EmptyState, { title: t("BalanceCard.empty.message", p.locale), locale: p.locale });
1420
+ }
1421
+ const canCopy = typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
1422
+ const copy = async () => {
1423
+ try {
1424
+ await navigator.clipboard.writeText(p.address);
1425
+ setCopied(true);
1426
+ } catch {
1427
+ }
1428
+ };
1429
+ return /* @__PURE__ */ jsxs(
1430
+ "section",
1431
+ {
1432
+ "aria-label": `${t("BalanceCard.title", p.locale)}: ${p.token}`,
1433
+ lang: p.locale,
1434
+ className: "flex flex-col gap-2",
1435
+ children: [
1436
+ /* @__PURE__ */ jsx(
1437
+ StatCard,
1438
+ {
1439
+ label: t("BalanceCard.title", p.locale),
1440
+ value: p.balance,
1441
+ unit: p.token,
1442
+ locale: p.locale
1443
+ }
1444
+ ),
1445
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1446
+ p.network ? /* @__PURE__ */ jsx(Badge, { label: p.network, variant: "info", locale: p.locale }) : null,
1447
+ p.testnet ? /* @__PURE__ */ jsx(Badge, { label: t("BalanceCard.testnet", p.locale), variant: "warning", locale: p.locale }) : null
1448
+ ] }),
1449
+ p.usdValue ? /* @__PURE__ */ jsxs("span", { className: "text-sm text-slate-600", children: [
1450
+ t("BalanceCard.value", p.locale),
1451
+ ":",
1452
+ " ",
1453
+ formatDecimalString(p.usdValue, p.locale, p.currency)
1454
+ ] }) : null,
1455
+ p.address ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-slate-600", children: [
1456
+ /* @__PURE__ */ jsx("span", { className: "font-mono", title: p.address, children: truncateAddress(p.address) }),
1457
+ canCopy ? /* @__PURE__ */ jsx(
1458
+ "button",
1459
+ {
1460
+ type: "button",
1461
+ onClick: copy,
1462
+ className: "rounded border border-slate-300 px-2 py-0.5 text-xs hover:bg-slate-50",
1463
+ children: t("BalanceCard.copy", p.locale)
1464
+ }
1465
+ ) : null,
1466
+ copied ? /* @__PURE__ */ jsx("output", { children: t("BalanceCard.copied", p.locale) }) : null
1467
+ ] }) : null
1468
+ ]
1469
+ }
1470
+ );
1471
+ }
1472
+
1473
+ // src/components/shared/datetime.ts
1474
+ function formatTimestamp(iso, locale, timeZone) {
1475
+ const d = new Date(iso);
1476
+ if (Number.isNaN(d.getTime())) return iso;
1477
+ try {
1478
+ return new Intl.DateTimeFormat(locale, {
1479
+ dateStyle: "medium",
1480
+ timeStyle: "short",
1481
+ ...timeZone ? { timeZone } : {}
1482
+ }).format(d);
1483
+ } catch {
1484
+ return iso;
1485
+ }
1486
+ }
1487
+
1488
+ // src/components/shared/url.ts
1489
+ function normalizeHttpUrl(value) {
1490
+ if (typeof value !== "string") return void 0;
1491
+ try {
1492
+ const u = new URL(value);
1493
+ return u.protocol === "https:" || u.protocol === "http:" ? u.href : void 0;
1494
+ } catch {
1495
+ return void 0;
1496
+ }
1497
+ }
1498
+ var FeedMessageSchema = z.object({
1499
+ id: z.string().optional(),
1500
+ sender: z.string().min(1),
1501
+ channel: z.string().optional(),
1502
+ /** Plain text. Rendered as text, never as HTML. */
1503
+ content: z.string(),
1504
+ /** ISO 8601. */
1505
+ timestamp: z.string(),
1506
+ attachmentUrl: z.string().optional(),
1507
+ attachmentLabel: z.string().optional(),
1508
+ unread: z.boolean().optional()
1509
+ });
1510
+ var MessageFeedPropsSchema = z.object({
1511
+ messages: z.array(FeedMessageSchema).default([]),
1512
+ maxItems: z.number().int().positive().optional(),
1513
+ /** IANA timezone for timestamps (e.g. `hostContext.timeZone`). */
1514
+ timeZone: z.string().optional(),
1515
+ /** Above this many shown messages the feed is virtualised through VirtualList. */
1516
+ virtualizeThreshold: z.number().int().positive().default(50),
1517
+ loading: z.boolean().default(false),
1518
+ locale: z.enum(["en", "fr"]).default("en")
1519
+ });
1520
+ function validateMessageFeedProps(raw) {
1521
+ return MessageFeedPropsSchema.parse(raw);
1522
+ }
1523
+ function latestMessages(messages, maxItems) {
1524
+ const shown = maxItems === void 0 ? [...messages] : messages.slice(-maxItems);
1525
+ return { shown, total: messages.length };
1526
+ }
1527
+ function Attachment({
1528
+ m,
1529
+ locale,
1530
+ onOpenLink
1531
+ }) {
1532
+ if (!m.attachmentUrl) return null;
1533
+ const label = m.attachmentLabel ?? t("MessageFeed.attachment", locale);
1534
+ const url = normalizeHttpUrl(m.attachmentUrl);
1535
+ if (!url) return /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: label });
1536
+ return onOpenLink ? /* @__PURE__ */ jsx(
1537
+ "button",
1538
+ {
1539
+ type: "button",
1540
+ onClick: () => onOpenLink(url),
1541
+ className: "text-left text-sm text-blue-700 underline",
1542
+ children: label
1543
+ }
1544
+ ) : /* @__PURE__ */ jsx(
1545
+ "a",
1546
+ {
1547
+ href: url,
1548
+ rel: "noopener noreferrer",
1549
+ target: "_blank",
1550
+ className: "text-sm text-blue-700 underline",
1551
+ children: label
1552
+ }
1553
+ );
1554
+ }
1555
+ function Message({
1556
+ m,
1557
+ locale,
1558
+ timeZone,
1559
+ onOpenLink
1560
+ }) {
1561
+ return /* @__PURE__ */ jsxs("article", { className: "flex flex-col gap-1 border-b border-slate-100 py-2", children: [
1562
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
1563
+ /* @__PURE__ */ jsx("strong", { className: "text-slate-900", children: m.sender }),
1564
+ m.channel ? /* @__PURE__ */ jsx(Badge, { label: m.channel, variant: "neutral", locale }) : null,
1565
+ /* @__PURE__ */ jsx("time", { dateTime: m.timestamp, className: "text-xs text-slate-500", children: formatTimestamp(m.timestamp, locale, timeZone) }),
1566
+ m.unread ? /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-blue-700", children: t("MessageFeed.unread", locale) }) : null
1567
+ ] }),
1568
+ /* @__PURE__ */ jsx("p", { className: "m-0 whitespace-pre-wrap text-sm text-slate-800", children: m.content }),
1569
+ /* @__PURE__ */ jsx(Attachment, { m, locale, onOpenLink })
1570
+ ] });
1571
+ }
1572
+ function MessageFeed(raw = {}) {
1573
+ const parsed = MessageFeedPropsSchema.safeParse(raw);
1574
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1575
+ if (!parsed.success) {
1576
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("MessageFeed.error.invalidProps", locale) });
1577
+ }
1578
+ const p = parsed.data;
1579
+ if (p.loading) {
1580
+ return /* @__PURE__ */ jsx("div", { "aria-busy": "true", "aria-label": t("MessageFeed.loading", p.locale), children: /* @__PURE__ */ jsx(Skeleton, { variant: "text", count: 5, locale: p.locale }) });
1581
+ }
1582
+ if (p.messages.length === 0) {
1583
+ return /* @__PURE__ */ jsx(EmptyState, { title: t("MessageFeed.empty.message", p.locale), locale: p.locale });
1584
+ }
1585
+ const { shown, total } = latestMessages(p.messages, p.maxItems);
1586
+ const virtual = shown.length > p.virtualizeThreshold;
1587
+ return /* @__PURE__ */ jsxs("section", { "aria-label": t("MessageFeed.title", p.locale), lang: p.locale, children: [
1588
+ shown.length < total ? /* @__PURE__ */ jsx("p", { className: "m-0 pb-1 text-xs text-slate-500", children: t("MessageFeed.showing", p.locale).replace("{n}", String(shown.length)).replace("{total}", String(total)) }) : null,
1589
+ virtual ? /* @__PURE__ */ jsx(
1590
+ VirtualList,
1591
+ {
1592
+ items: shown,
1593
+ itemHeight: 96,
1594
+ locale: p.locale,
1595
+ renderItem: (m) => /* @__PURE__ */ jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink })
1596
+ }
1597
+ ) : /* @__PURE__ */ jsx("ol", { className: "m-0 list-none p-0", children: shown.map((m, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink }) }, m.id ?? `msg-${i}`)) })
1598
+ ] });
1599
+ }
1600
+
1601
+ // src/components/forms/RadioGroup.logic.ts
1602
+ function getOptionLabelClasses(appearance, isSelected) {
1603
+ const base = "mosaic-forms-radiogroup-option-label inline-flex items-center px-3 py-1 text-sm";
1604
+ const shape = "rounded-full border" ;
1605
+ const tone = isSelected ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-700 border-slate-300 hover:bg-slate-50";
1606
+ return [base, shape, tone].filter(Boolean).join(" ");
1607
+ }
1608
+
1609
+ // src/components/display/PipelineBoard.logic.ts
1610
+ function stageSummary(deals) {
1611
+ const amounts = deals.flatMap((d) => d.amount === void 0 ? [] : [d.amount]);
1612
+ return { count: deals.length, total: sumDecimalStrings(amounts) };
1613
+ }
1614
+ var SUMMARY_STAGE_LIMIT = 5;
1615
+ var PipelineDealSchema = z.object({
1616
+ id: z.string().min(1),
1617
+ title: z.string().min(1),
1618
+ /** Decimal STRING (never a float), in the board currency. */
1619
+ amount: z.string().optional(),
1620
+ contact: z.string().optional(),
1621
+ url: z.string().optional()
1622
+ });
1623
+ var PipelineStageSchema = z.object({
1624
+ id: z.string().min(1),
1625
+ name: z.string().min(1),
1626
+ deals: z.array(PipelineDealSchema).default([])
1627
+ });
1628
+ var PipelineBoardPropsSchema = z.object({
1629
+ stages: z.array(PipelineStageSchema).default([]),
1630
+ /** ISO 4217 code used to format amounts. */
1631
+ currency: z.string().default("EUR"),
1632
+ layout: z.enum(["board", "summary"]).default("board"),
1633
+ loading: z.boolean().default(false),
1634
+ locale: z.enum(["en", "fr"]).default("en")
1635
+ });
1636
+ function validatePipelineBoardProps(raw) {
1637
+ return PipelineBoardPropsSchema.parse(raw);
1638
+ }
1639
+ function dealsLabel(n, locale) {
1640
+ return n === 1 ? t("PipelineBoard.deals.one", locale) : t("PipelineBoard.deals.many", locale).replace("{n}", String(n));
1641
+ }
1642
+ function Totals({
1643
+ stage,
1644
+ currency,
1645
+ locale
1646
+ }) {
1647
+ const { count, total } = stageSummary(stage.deals);
1648
+ return /* @__PURE__ */ jsxs("span", { className: "flex flex-col text-sm text-slate-600", children: [
1649
+ /* @__PURE__ */ jsx("span", { children: dealsLabel(count, locale) }),
1650
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total === void 0 ? "\u2014" : formatDecimalString(total, locale, currency) })
1651
+ ] });
1652
+ }
1653
+ function PipelineBoard(raw = {}) {
1654
+ const parsed = PipelineBoardPropsSchema.safeParse(raw);
1655
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1656
+ const [hidden, setHidden] = useState(/* @__PURE__ */ new Set());
1657
+ if (!parsed.success) {
1658
+ return /* @__PURE__ */ jsx("div", { role: "alert", children: t("PipelineBoard.error.invalidProps", locale) });
1659
+ }
1660
+ const p = parsed.data;
1661
+ if (p.loading) {
1662
+ return /* @__PURE__ */ jsx("div", { "aria-busy": "true", "aria-label": t("PipelineBoard.loading", p.locale), children: /* @__PURE__ */ jsx(Skeleton, { variant: "rect", height: 160, locale: p.locale }) });
1663
+ }
1664
+ if (p.stages.length === 0) {
1665
+ return /* @__PURE__ */ jsx(EmptyState, { title: t("PipelineBoard.empty.message", p.locale), locale: p.locale });
1666
+ }
1667
+ if (p.layout === "summary") {
1668
+ const shown = p.stages.slice(0, SUMMARY_STAGE_LIMIT);
1669
+ const rest = p.stages.length - shown.length;
1670
+ return /* @__PURE__ */ jsxs(
1671
+ "section",
1672
+ {
1673
+ "aria-label": t("PipelineBoard.title", p.locale),
1674
+ lang: p.locale,
1675
+ className: "flex flex-col gap-2",
1676
+ children: [
1677
+ /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: shown.map((s) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2", children: [
1678
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-900", children: s.name }),
1679
+ /* @__PURE__ */ jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1680
+ ] }, s.id)) }),
1681
+ rest > 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.more", p.locale).replace("{n}", String(rest)) }) : null,
1682
+ raw.onRequestFullscreen ? /* @__PURE__ */ jsx(
1683
+ "button",
1684
+ {
1685
+ type: "button",
1686
+ onClick: raw.onRequestFullscreen,
1687
+ className: "self-start rounded border border-slate-300 px-3 py-1 text-sm hover:bg-slate-50",
1688
+ children: t("PipelineBoard.summary.open", p.locale)
1689
+ }
1690
+ ) : null
1691
+ ]
1692
+ }
1693
+ );
1694
+ }
1695
+ const visible = p.stages.filter((s) => !hidden.has(s.id));
1696
+ const toggle = (id) => {
1697
+ const next = new Set(hidden);
1698
+ if (next.has(id)) next.delete(id);
1699
+ else if (visible.length > 1) next.add(id);
1700
+ setHidden(next);
1701
+ };
1702
+ return /* @__PURE__ */ jsxs(
1703
+ "section",
1704
+ {
1705
+ "aria-label": t("PipelineBoard.title", p.locale),
1706
+ lang: p.locale,
1707
+ className: "flex flex-col gap-3",
1708
+ children: [
1709
+ /* @__PURE__ */ jsxs("fieldset", { className: "m-0 flex min-w-0 flex-wrap gap-2 border-0 p-0", children: [
1710
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: t("PipelineBoard.filter.label", p.locale) }),
1711
+ p.stages.map((s) => {
1712
+ const on = !hidden.has(s.id);
1713
+ return /* @__PURE__ */ jsx(
1714
+ "button",
1715
+ {
1716
+ type: "button",
1717
+ "aria-pressed": on,
1718
+ onClick: () => toggle(s.id),
1719
+ className: getOptionLabelClasses("chips", on),
1720
+ children: s.name
1721
+ },
1722
+ s.id
1723
+ );
1724
+ })
1725
+ ] }),
1726
+ /* @__PURE__ */ jsx(
1727
+ "div",
1728
+ {
1729
+ className: "flex flex-row gap-3 overflow-x-auto",
1730
+ style: {
1731
+ scrollPaddingLeft: "var(--mosaic-safe-area-left, 0px)",
1732
+ scrollPaddingRight: "var(--mosaic-safe-area-right, 0px)"
1733
+ },
1734
+ children: visible.map((s) => /* @__PURE__ */ jsxs(
1735
+ "section",
1736
+ {
1737
+ "aria-label": s.name,
1738
+ className: "flex w-64 shrink-0 flex-col gap-2 rounded-lg border border-slate-200 bg-slate-50 p-3",
1739
+ children: [
1740
+ /* @__PURE__ */ jsxs("header", { className: "flex items-start justify-between gap-2", children: [
1741
+ /* @__PURE__ */ jsx("strong", { className: "text-slate-900", children: s.name }),
1742
+ /* @__PURE__ */ jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1743
+ ] }),
1744
+ s.deals.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.stage.empty", p.locale) }) : /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: s.deals.map((d) => /* @__PURE__ */ jsxs("li", { className: "rounded border border-slate-200 bg-white p-2", children: [
1745
+ raw.onOpenLink && normalizeHttpUrl(d.url) ? /* @__PURE__ */ jsx(
1746
+ "button",
1747
+ {
1748
+ type: "button",
1749
+ onClick: () => raw.onOpenLink?.(normalizeHttpUrl(d.url)),
1750
+ className: "text-left font-medium text-blue-700 underline",
1751
+ children: d.title
1752
+ }
1753
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-900", children: d.title }),
1754
+ d.amount !== void 0 ? /* @__PURE__ */ jsx("span", { className: "block text-sm tabular-nums text-slate-700", children: formatDecimalString(d.amount, p.locale, p.currency) }) : null,
1755
+ d.contact ? /* @__PURE__ */ jsx(Badge, { label: d.contact, variant: "neutral", locale: p.locale }) : null
1756
+ ] }, d.id)) })
1757
+ ]
1758
+ },
1759
+ s.id
1760
+ ))
1761
+ }
1762
+ )
1763
+ ]
1764
+ }
1765
+ );
1766
+ }
771
1767
 
772
- export { EmptyState, EmptyStatePropsSchema, Skeleton, SkeletonPropsSchema, StatusBadge, StatusBadgePropsSchema, StreamingTableView, TableView, TableViewPropsSchema, VirtualList, VirtualListPropsSchema, resolveSkeletonDimensions, validateEmptyStateProps, validateSkeletonProps, validateProps as validateStatusBadgeProps, validateTableViewProps, validateVirtualListProps };
1768
+ export { Badge, BadgePropsSchema, BalanceCard, BalanceCardPropsSchema, EmptyState, EmptyStatePropsSchema, MessageFeed, MessageFeedPropsSchema, PipelineBoard, PipelineBoardPropsSchema, Skeleton, SkeletonPropsSchema, StatCard, StatCardPropsSchema, StatusBadge, StatusBadgePropsSchema, StreamingTableView, TableView, TableViewPropsSchema, VirtualList, VirtualListPropsSchema, compareDecimalStrings, nextSort, resolveSkeletonDimensions, sortItems, validateBadgeProps, validateBalanceCardProps, validateEmptyStateProps, validateMessageFeedProps, validatePipelineBoardProps, validateSkeletonProps, validateStatCardProps, validateProps as validateStatusBadgeProps, validateTableViewProps, validateVirtualListProps };
773
1769
  //# sourceMappingURL=display.js.map
774
1770
  //# sourceMappingURL=display.js.map