@dnax/ui 0.0.46 → 0.0.49

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 (47) hide show
  1. package/components/QBottomSheet.vue +172 -6
  2. package/components/QBtn.vue +3 -4
  3. package/components/QContainer.vue +2 -2
  4. package/components/QImagePicker.vue +28 -1
  5. package/components/QInputChat.vue +859 -0
  6. package/components/QInputCurrency.vue +319 -0
  7. package/components/QItemLabel.vue +99 -0
  8. package/components/QMap.vue +410 -0
  9. package/components/QNumericKeyboard.vue +304 -0
  10. package/components/QPage.vue +26 -6
  11. package/components/QPageContainer.vue +32 -0
  12. package/components/QPopupProxy.vue +378 -0
  13. package/components/QQrcode.vue +132 -0
  14. package/components/QSpreadsheet.vue +395 -69
  15. package/components/QTime.vue +845 -0
  16. package/components/QTimeline.vue +111 -0
  17. package/components/QTimelineEntry.vue +271 -0
  18. package/index.ts +11 -1
  19. package/lib/bottomSheet.test.ts +121 -0
  20. package/lib/bottomSheet.ts +115 -0
  21. package/lib/color.ts +13 -0
  22. package/lib/currency.test.ts +193 -0
  23. package/lib/currency.ts +301 -0
  24. package/lib/fixedLayout.ts +12 -11
  25. package/lib/icons.ts +3 -20
  26. package/lib/map.test.ts +365 -0
  27. package/lib/map.ts +438 -0
  28. package/lib/mapEngine.ts +158 -0
  29. package/lib/mapLeaflet.ts +142 -0
  30. package/lib/mapMaplibre.ts +106 -0
  31. package/lib/mapMaptiler.ts +93 -0
  32. package/lib/numericKeyboard.test.ts +179 -0
  33. package/lib/numericKeyboard.ts +166 -0
  34. package/lib/pagePadding.test.ts +37 -0
  35. package/lib/pagePadding.ts +32 -0
  36. package/lib/qrcode.test.ts +103 -0
  37. package/lib/qrcode.ts +125 -0
  38. package/lib/spreadsheetChanges.test.ts +230 -0
  39. package/lib/spreadsheetChanges.ts +331 -0
  40. package/package.json +5 -17
  41. package/shims.d.ts +19 -0
  42. package/styles/main.css +702 -893
  43. package/components/QTiptap.vue +0 -996
  44. package/components/internal/QTiptapTableView.vue +0 -123
  45. package/components/internal/QTiptapTaskItemView.vue +0 -49
  46. package/lib/tiptap-table.ts +0 -19
  47. package/lib/tiptap-task-list.ts +0 -16
@@ -0,0 +1,331 @@
1
+ // spreadsheetChanges — suivi des modifications d'un tableur : delta entre un **état de
2
+ // référence** (« ce qui a été chargé / enregistré ») et l'état courant.
3
+ //
4
+ // Le delta est organisé par **niveau**, comme le modèle de données :
5
+ // • `rows` — lignes **ajoutées / modifiées / supprimées** (identité = clé stable `_key`) ;
6
+ // • `sheets` — feuilles **ajoutées / modifiées / supprimées** (identité = `key`) ;
7
+ // • `extras` — autres parties du document (mise en forme, largeurs, filtres, fusions…).
8
+ //
9
+ // Décision d'architecture : on **compare deux photographies** au lieu de journaliser les
10
+ // opérations. Conséquences recherchées :
11
+ // • le résultat est juste quels que soient les chemins de mutation traversés (édition, collage,
12
+ // recopie, tri, réordonnancement, import CSV, undo/redo…) — aucun chemin à ne pas oublier ;
13
+ // • c'est l'état **net** qui est rapporté, pas l'historique : modifier puis annuler ⇒ rien ;
14
+ // • « modifié » a un sens fort et vérifiable : le document (`toJSON()`) diffère de la référence.
15
+ //
16
+ // Pur (aucun DOM, aucun Vue) → testable, comme `lib/spreadsheet.ts`.
17
+
18
+ import { isBlankValue } from "./spreadsheet"
19
+
20
+ /** Propriété d'identité des lignes (injectée par `QSpreadsheet`) */
21
+ export const DEFAULT_ROW_KEY = "_key"
22
+
23
+ /**
24
+ * Parties d'une feuille comparées en plus des lignes et des colonnes — une modification de mise
25
+ * en forme, de largeur, de filtre… rend le document « modifié » sans toucher aux lignes.
26
+ */
27
+ export const DOC_EXTRA_PARTS = [
28
+ "formats",
29
+ "widths",
30
+ "rowHeights",
31
+ "filters",
32
+ "rules",
33
+ "merges",
34
+ "hiddenRows",
35
+ "hiddenCols",
36
+ ] as const
37
+
38
+ export type QSpreadsheetExtraPart = (typeof DOC_EXTRA_PARTS)[number]
39
+
40
+ /** Feuille telle qu'elle circule dans un document (`QSpreadsheetSheet` sérialisée) */
41
+ export interface QSpreadsheetSheetPayload {
42
+ /** Clé stable de la feuille */
43
+ key?: string
44
+ /** Nom affiché sur l'onglet */
45
+ name?: string
46
+ /** Lignes de la feuille */
47
+ rows?: Record<string, any>[]
48
+ /** Schéma de la feuille (noms ou descripteurs complets) */
49
+ columns?: (string | { name: string })[]
50
+ [extra: string]: any
51
+ }
52
+
53
+ /** Document minimal manipulé par le diff (`QSpreadsheetDocument`) */
54
+ export interface QSpreadsheetDocumentPayload {
55
+ version?: number
56
+ active?: string
57
+ sheets?: QSpreadsheetSheetPayload[]
58
+ }
59
+
60
+ const isRecord = (v: unknown): v is Record<string, any> =>
61
+ typeof v === "object" && v !== null && !Array.isArray(v)
62
+
63
+ /**
64
+ * Égalité profonde (objets simples, tableaux, primitives). L'**ordre des clés** est ignoré — les
65
+ * records de mise en forme / largeurs sont reconstruits dans un ordre variable selon le chemin.
66
+ */
67
+ export function deepEqual(a: unknown, b: unknown): boolean {
68
+ if (Object.is(a, b)) return true
69
+ if (Array.isArray(a) || Array.isArray(b)) {
70
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false
71
+ return a.every((v, i) => deepEqual(v, b[i]))
72
+ }
73
+ if (isRecord(a) && isRecord(b)) {
74
+ const ka = Object.keys(a)
75
+ if (ka.length !== Object.keys(b).length) return false
76
+ return ka.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]))
77
+ }
78
+ return false
79
+ }
80
+
81
+ /**
82
+ * Même valeur de cellule ?
83
+ *
84
+ * `null`, `undefined` et `""` sont **équivalents** (« vide ») : ajouter une colonne vide, ou vider
85
+ * une cellule, ne marque donc aucune ligne comme modifiée. Les tableaux (`multiselect`) sont
86
+ * comparés élément par élément, les objets en profondeur.
87
+ */
88
+ export function sameCellValue(a: unknown, b: unknown): boolean {
89
+ if (isBlankValue(a) || isBlankValue(b)) return isBlankValue(a) && isBlankValue(b)
90
+ if (Object.is(a, b)) return true
91
+ if (Array.isArray(a) || Array.isArray(b)) {
92
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false
93
+ return a.every((v, i) => sameCellValue(v, b[i]))
94
+ }
95
+ if (isRecord(a) && isRecord(b)) return deepEqual(a, b)
96
+ return false
97
+ }
98
+
99
+ /** Une ligne du delta : sa feuille, sa clé stable et ses valeurs */
100
+ export interface QSpreadsheetRowRef {
101
+ /** Clé de la feuille d'appartenance (`sheet-1` quand le classeur n'a qu'une feuille) */
102
+ sheet: string
103
+ /** Clé stable de la ligne (`_key`), normalisée en chaîne */
104
+ key: string
105
+ /** La ligne (valeurs stockées, `_key` compris) */
106
+ row: Record<string, any>
107
+ }
108
+
109
+ /** Une ligne modifiée : valeurs avant / après + noms des colonnes touchées */
110
+ export interface QSpreadsheetRowUpdate extends QSpreadsheetRowRef {
111
+ /** La ligne telle qu'elle était dans la référence */
112
+ before: Record<string, any>
113
+ /** Noms des colonnes dont la valeur a changé */
114
+ columns: string[]
115
+ }
116
+
117
+ export interface QSpreadsheetRowsDelta {
118
+ added: QSpreadsheetRowRef[]
119
+ updated: QSpreadsheetRowUpdate[]
120
+ deleted: QSpreadsheetRowRef[]
121
+ }
122
+
123
+ /** Une feuille modifiée (nom et/ou schéma) */
124
+ export interface QSpreadsheetSheetUpdate {
125
+ key: string
126
+ /** La feuille dans la référence */
127
+ before: QSpreadsheetSheetPayload
128
+ /** La feuille maintenant */
129
+ sheet: QSpreadsheetSheetPayload
130
+ /** Parties de la feuille qui ont changé : `"name"`, `"columns"` */
131
+ changed: string[]
132
+ }
133
+
134
+ export interface QSpreadsheetSheetsDelta {
135
+ added: QSpreadsheetSheetPayload[]
136
+ updated: QSpreadsheetSheetUpdate[]
137
+ deleted: QSpreadsheetSheetPayload[]
138
+ }
139
+
140
+ /** Delta complet entre une référence et l'état courant */
141
+ export interface QSpreadsheetChanges {
142
+ /** Vrai dès qu'une ligne, une feuille ou une autre partie du document diffère */
143
+ dirty: boolean
144
+ /**
145
+ * Nombre d'éléments touchés : lignes + feuilles, plus **1** si d'autres parties du document
146
+ * ont changé (mise en forme, largeur, filtre…) — pratique pour un badge.
147
+ */
148
+ count: number
149
+ /** Niveau **lignes** (feuilles présentes avant **et** après) */
150
+ rows: QSpreadsheetRowsDelta
151
+ /** Niveau **feuilles** */
152
+ sheets: QSpreadsheetSheetsDelta
153
+ /** Parties du document touchées hors lignes / feuilles (`"formats"`, `"widths"`…) */
154
+ extras: QSpreadsheetExtraPart[]
155
+ }
156
+
157
+ const emptyDelta = (): QSpreadsheetChanges => ({
158
+ dirty: false,
159
+ count: 0,
160
+ rows: { added: [], updated: [], deleted: [] },
161
+ sheets: { added: [], updated: [], deleted: [] },
162
+ extras: [],
163
+ })
164
+
165
+ /** Identité d'une ligne, ou `undefined` si elle n'est pas identifiable (pas de clé) */
166
+ const identity = (row: Record<string, any> | undefined, key: string): string | undefined => {
167
+ const v = row?.[key]
168
+ return v === undefined || v === null || v === "" ? undefined : String(v)
169
+ }
170
+
171
+ const indexByKey = (
172
+ rows: Record<string, any>[] | undefined,
173
+ key: string,
174
+ ): Map<string, Record<string, any>> => {
175
+ const map = new Map<string, Record<string, any>>()
176
+ for (const row of rows ?? []) {
177
+ const id = identity(row, key)
178
+ if (id !== undefined) map.set(id, row)
179
+ }
180
+ return map
181
+ }
182
+
183
+ const namesOf = (
184
+ columns: (string | { name: string })[] | undefined,
185
+ before: Record<string, any>,
186
+ after: Record<string, any>,
187
+ key: string,
188
+ ): string[] => {
189
+ if (columns?.length) return columns.map((c) => (typeof c === "string" ? c : c.name))
190
+ // Pas de schéma déclaré : on compare les clés présentes, `_key` exclue
191
+ const names = new Set(Object.keys(before))
192
+ for (const k of Object.keys(after)) names.add(k)
193
+ names.delete(key)
194
+ return [...names]
195
+ }
196
+
197
+ export interface DiffRowsOptions {
198
+ /** Colonnes comparées (le schéma de la feuille) — sinon les clés des lignes elles-mêmes */
199
+ columns?: (string | { name: string })[]
200
+ /** Propriété d'identité (défaut : `_key`) */
201
+ key?: string
202
+ /** Clé de la feuille reportée sur chaque entrée du delta */
203
+ sheet?: string
204
+ }
205
+
206
+ /**
207
+ * Delta des **lignes** entre deux états, par identité (`_key`).
208
+ *
209
+ * Seules les colonnes du schéma courant sont comparées : supprimer une colonne ne déclenche donc
210
+ * pas une modification sur chacune des lignes (le changement de schéma est rapporté au niveau
211
+ * feuille). Les lignes sans clé ne sont pas suivies.
212
+ */
213
+ export function diffRows(
214
+ before: Record<string, any>[] | undefined,
215
+ after: Record<string, any>[] | undefined,
216
+ options: DiffRowsOptions = {},
217
+ ): QSpreadsheetRowsDelta {
218
+ const key = options.key ?? DEFAULT_ROW_KEY
219
+ const sheet = options.sheet ?? ""
220
+ const reference = indexByKey(before, key)
221
+
222
+ const added: QSpreadsheetRowRef[] = []
223
+ const updated: QSpreadsheetRowUpdate[] = []
224
+ const seen = new Set<string>()
225
+
226
+ for (const row of after ?? []) {
227
+ const id = identity(row, key)
228
+ if (id === undefined) continue
229
+ seen.add(id)
230
+ const prev = reference.get(id)
231
+ if (!prev) {
232
+ added.push({ sheet, key: id, row })
233
+ continue
234
+ }
235
+ const names = namesOf(options.columns, prev, row, key)
236
+ const columns = names.filter((name) => !sameCellValue(prev[name], row[name]))
237
+ if (columns.length) updated.push({ sheet, key: id, before: prev, row, columns })
238
+ }
239
+
240
+ const deleted: QSpreadsheetRowRef[] = []
241
+ for (const [id, row] of reference) if (!seen.has(id)) deleted.push({ sheet, key: id, row })
242
+
243
+ return { added, updated, deleted }
244
+ }
245
+
246
+ /** Delta des **feuilles** entre deux états, par `key` (nom et schéma) */
247
+ export function diffSheets(
248
+ before: QSpreadsheetSheetPayload[] | undefined,
249
+ after: QSpreadsheetSheetPayload[] | undefined,
250
+ ): QSpreadsheetSheetsDelta {
251
+ const reference = new Map<string, QSpreadsheetSheetPayload>()
252
+ for (const sheet of before ?? []) reference.set(String(sheet.key ?? ""), sheet)
253
+
254
+ const added: QSpreadsheetSheetPayload[] = []
255
+ const updated: QSpreadsheetSheetUpdate[] = []
256
+ const seen = new Set<string>()
257
+
258
+ for (const sheet of after ?? []) {
259
+ const key = String(sheet.key ?? "")
260
+ seen.add(key)
261
+ const prev = reference.get(key)
262
+ if (!prev) {
263
+ added.push(sheet)
264
+ continue
265
+ }
266
+ const changed: string[] = []
267
+ if (String(sheet.name ?? "") !== String(prev.name ?? "")) changed.push("name")
268
+ if (!deepEqual(sheet.columns ?? [], prev.columns ?? [])) changed.push("columns")
269
+ if (changed.length) updated.push({ key, before: prev, sheet, changed })
270
+ }
271
+
272
+ const deleted = (before ?? []).filter((s) => !seen.has(String(s.key ?? "")))
273
+ return { added, updated, deleted }
274
+ }
275
+
276
+ /**
277
+ * Delta complet entre une **référence** et l'**état courant** (deux documents de la forme de
278
+ * `toJSON()`). Une feuille ajoutée emporte ses lignes dans `sheets.added` (elles ne sont pas
279
+ * répétées dans `rows`) — et inversement pour une feuille supprimée ; `rows` ne décrit donc que
280
+ * l'évolution des feuilles présentes des deux côtés.
281
+ *
282
+ * `active` (onglet courant) et `version` sont **ignorés** : changer d'onglet n'est pas une
283
+ * modification de données.
284
+ */
285
+ export function diffDocuments(
286
+ reference: QSpreadsheetDocumentPayload | null | undefined,
287
+ current: QSpreadsheetDocumentPayload | null | undefined,
288
+ ): QSpreadsheetChanges {
289
+ if (!reference || !current) return emptyDelta()
290
+
291
+ const sheets = diffSheets(reference.sheets, current.sheets)
292
+ const beforeSheets = new Map<string, QSpreadsheetSheetPayload>()
293
+ for (const sheet of reference.sheets ?? []) beforeSheets.set(String(sheet.key ?? ""), sheet)
294
+
295
+ const rows: QSpreadsheetRowsDelta = { added: [], updated: [], deleted: [] }
296
+ const extras = new Set<QSpreadsheetExtraPart>()
297
+
298
+ for (const sheet of current.sheets ?? []) {
299
+ const key = String(sheet.key ?? "")
300
+ const prev = beforeSheets.get(key)
301
+ if (!prev) continue // feuille ajoutée : ses lignes sont portées par `sheets.added`
302
+
303
+ const delta = diffRows(prev.rows, sheet.rows, { columns: sheet.columns, sheet: key })
304
+ rows.added.push(...delta.added)
305
+ rows.updated.push(...delta.updated)
306
+ rows.deleted.push(...delta.deleted)
307
+
308
+ for (const part of DOC_EXTRA_PARTS) {
309
+ if (!deepEqual(sheet[part], prev[part])) extras.add(part)
310
+ }
311
+ }
312
+
313
+ const count =
314
+ rows.added.length +
315
+ rows.updated.length +
316
+ rows.deleted.length +
317
+ sheets.added.length +
318
+ sheets.updated.length +
319
+ sheets.deleted.length +
320
+ // Une modification « autre » (mise en forme, largeur, filtre…) compte pour **un** élément :
321
+ // l'indicateur doit rester parlant même sans ligne ni feuille touchée.
322
+ (extras.size ? 1 : 0)
323
+
324
+ return {
325
+ dirty: count > 0,
326
+ count,
327
+ rows,
328
+ sheets,
329
+ extras: DOC_EXTRA_PARTS.filter((part) => extras.has(part)),
330
+ }
331
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnax/ui",
3
- "version": "0.0.46",
3
+ "version": "0.0.49",
4
4
  "module": "index.ts",
5
5
  "description": "UI design System",
6
6
  "type": "module",
@@ -23,23 +23,8 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@iconify/vue": "^5.0.1",
26
+ "@maptiler/sdk": "^4.1.0",
26
27
  "@nuxt/kit": "4.5.2",
27
- "@tiptap/extension-bubble-menu": "^3.31.3",
28
- "@tiptap/extension-drag-handle": "^3.31.3",
29
- "@tiptap/extension-drag-handle-vue-3": "^3.31.3",
30
- "@tiptap/extension-floating-menu": "^3.31.3",
31
- "@tiptap/extension-image": "^3.31.3",
32
- "@tiptap/extension-mention": "^3.31.3",
33
- "@tiptap/extension-table": "^3.31.3",
34
- "@tiptap/extension-table-of-contents": "^3.31.3",
35
- "@tiptap/extension-task-item": "^3.31.3",
36
- "@tiptap/extension-task-list": "^3.31.3",
37
- "@tiptap/extension-text-align": "^3.31.3",
38
- "@tiptap/extension-text-style": "^3.31.3",
39
- "@tiptap/pm": "^3.31.3",
40
- "@tiptap/starter-kit": "^3.31.3",
41
- "@tiptap/suggestion": "^3.31.3",
42
- "@tiptap/vue-3": "^3.31.3",
43
28
  "@vueuse/core": "^14.4.0",
44
29
  "arktype": "^2.2.3",
45
30
  "class-variance-authority": "^0.7.1",
@@ -48,7 +33,10 @@
48
33
  "embla-carousel": "^8.5.0",
49
34
  "embla-carousel-vue": "^8.5.0",
50
35
  "fuse.js": "^7.1.0",
36
+ "leaflet": "^1.9.4",
37
+ "maplibre-gl": "^5.24.0",
51
38
  "moment": "^2.30.1",
39
+ "qrcode": "^1.5.4",
52
40
  "shiki": "^3.0.0",
53
41
  "swiper": "^14.2.0",
54
42
  "tailwind-merge": "^3.6.0",
package/shims.d.ts CHANGED
@@ -4,3 +4,22 @@ declare module "*.css?raw" {
4
4
  const src: string
5
5
  export default src
6
6
  }
7
+
8
+ // Leaflet ne publie pas de types (ils vivent dans `@types/leaflet`, un paquet séparé) :
9
+ // le moteur `lib/mapLeaflet.ts` le manipule donc en `any`, comme le SDK MapTiler.
10
+ declare module "leaflet" {
11
+ const leaflet: any
12
+ export default leaflet
13
+ }
14
+
15
+ // Même situation pour `qrcode` (=`@types/qrcode` séparé) : seul `create()` est utilisé
16
+ // (matrice de modules) — le rendu est fait par dnax.ui (`lib/qrcode.ts`).
17
+ declare module "qrcode" {
18
+ const qrcode: {
19
+ create: (
20
+ text: string,
21
+ options?: { errorCorrectionLevel?: "L" | "M" | "Q" | "H"; version?: number },
22
+ ) => { modules: { size: number; data: ArrayLike<number> } }
23
+ }
24
+ export default qrcode
25
+ }