@dnax/ui 0.0.66 → 0.0.69
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.
- package/components/QChart.vue +329 -19
- package/components/QSpreadsheet.vue +194 -37
- package/index.ts +11 -1
- package/lib/chart.axis.test.ts +1 -1
- package/lib/chart.bi.test.ts +839 -0
- package/lib/chart.modules.test.ts +232 -0
- package/lib/chart.test.ts +13 -4
- package/lib/chart.ts +1400 -80
- package/lib/chartFormat.ts +222 -0
- package/lib/chartStats.ts +178 -0
- package/lib/formula.test.ts +8 -0
- package/lib/formula.ts +4 -2
- package/lib/history.test.ts +78 -0
- package/lib/history.ts +72 -0
- package/lib/spreadsheet.test.ts +110 -0
- package/lib/spreadsheet.ts +204 -5
- package/package.json +1 -1
- package/styles/main.css +65 -0
|
@@ -186,6 +186,10 @@ import {
|
|
|
186
186
|
ISO_DATE,
|
|
187
187
|
isoAddDays,
|
|
188
188
|
shiftFormulaRefs,
|
|
189
|
+
functionQueryAt,
|
|
190
|
+
rankSuggestions,
|
|
191
|
+
functionCallAt,
|
|
192
|
+
signatureArgs,
|
|
189
193
|
csvEscape,
|
|
190
194
|
deriveCols,
|
|
191
195
|
parseCsv,
|
|
@@ -194,6 +198,7 @@ import { checkValidation, type ArkCheck } from "../lib/spreadsheetValidation"
|
|
|
194
198
|
import { normalizeCellOptions } from "../lib/spreadsheetOptions"
|
|
195
199
|
import { inLockRect, lockRects, prefillForRow } from "../lib/spreadsheetZones"
|
|
196
200
|
import { canAddRows, canRemoveRows, clampRows } from "../lib/spreadsheetRows"
|
|
201
|
+
import { createHistory } from "../lib/history"
|
|
197
202
|
import {
|
|
198
203
|
DEFAULT_ROW_KEY,
|
|
199
204
|
diffDocuments,
|
|
@@ -349,6 +354,9 @@ const I18N = {
|
|
|
349
354
|
sheet: "Sheet",
|
|
350
355
|
fxPlaceholder: "Value or formula (=SUM(A1:A3)…)",
|
|
351
356
|
fxNoSel: "Select a cell…",
|
|
357
|
+
fxSyntax: "Syntax error",
|
|
358
|
+
fxEmpty: "empty",
|
|
359
|
+
fxColumn: "column",
|
|
352
360
|
cut: "Cut",
|
|
353
361
|
copy: "Copy",
|
|
354
362
|
paste: "Paste",
|
|
@@ -427,6 +435,9 @@ const I18N = {
|
|
|
427
435
|
sheet: "Feuille",
|
|
428
436
|
fxPlaceholder: "Valeur ou formule (=SOMME(A1:A3)…)",
|
|
429
437
|
fxNoSel: "Sélectionnez une cellule…",
|
|
438
|
+
fxSyntax: "Erreur de syntaxe",
|
|
439
|
+
fxEmpty: "vide",
|
|
440
|
+
fxColumn: "colonne",
|
|
430
441
|
cut: "Couper",
|
|
431
442
|
copy: "Copier",
|
|
432
443
|
paste: "Coller",
|
|
@@ -1069,11 +1080,13 @@ const coerceValue = (col: QSpreadsheetColumn | undefined, text: unknown, old: an
|
|
|
1069
1080
|
const raw = String(text ?? "")
|
|
1070
1081
|
const t = raw.trim()
|
|
1071
1082
|
if (t === "") return null
|
|
1072
|
-
// Formule A1 : conservée brute dans la cellule (booléen/choix exclus)
|
|
1083
|
+
// Formule A1 : conservée brute dans la cellule (booléen/choix exclus) —
|
|
1084
|
+
// `t` (déjà trimé) évite qu'un `" =…"` ne soit traité comme une formule par
|
|
1085
|
+
// `isFormulaRaw` tout en échouant au parsing du moteur.
|
|
1073
1086
|
if (t.startsWith("=")) {
|
|
1074
1087
|
if (col?.type === "boolean" || col?.type === "select" || col?.type === "multiselect")
|
|
1075
1088
|
return old
|
|
1076
|
-
return
|
|
1089
|
+
return t
|
|
1077
1090
|
}
|
|
1078
1091
|
if (col?.type === "number") {
|
|
1079
1092
|
const n = Number(raw)
|
|
@@ -1136,12 +1149,14 @@ const clearCell = (row: number, column: string) => {
|
|
|
1136
1149
|
if (isCellLocked(row, column)) return
|
|
1137
1150
|
const old = state.value[row]?.[column]
|
|
1138
1151
|
if (isBlankValue(old) || (Array.isArray(old) && old.length === 0)) return
|
|
1152
|
+
pushHistory()
|
|
1139
1153
|
setCellValue(row, column, old, null)
|
|
1140
1154
|
}
|
|
1141
1155
|
|
|
1142
1156
|
const toggleBoolean = (row: number, column: string) => {
|
|
1143
1157
|
if (isCellLocked(row, column)) return
|
|
1144
1158
|
const old = !!state.value[row]?.[column]
|
|
1159
|
+
pushHistory()
|
|
1145
1160
|
setCellValue(row, column, old, !old)
|
|
1146
1161
|
}
|
|
1147
1162
|
|
|
@@ -1246,9 +1261,7 @@ interface Snapshot {
|
|
|
1246
1261
|
merges: MergeRange[]
|
|
1247
1262
|
rules: CondRule[]
|
|
1248
1263
|
}
|
|
1249
|
-
const
|
|
1250
|
-
const redoStack: Snapshot[] = []
|
|
1251
|
-
|
|
1264
|
+
const history = createHistory<Snapshot>(100)
|
|
1252
1265
|
const takeSnapshot = (): Snapshot => ({
|
|
1253
1266
|
rows: state.value.map((r) => ({ ...r })),
|
|
1254
1267
|
cols: cols.value.map((c) => ({ ...c })),
|
|
@@ -1261,13 +1274,9 @@ const takeSnapshot = (): Snapshot => ({
|
|
|
1261
1274
|
merges: merges.value.map((m) => ({ ...m })),
|
|
1262
1275
|
rules: condRules.value.map((r) => ({ ...r })),
|
|
1263
1276
|
})
|
|
1264
|
-
const pushHistory = () =>
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
redoStack.length = 0
|
|
1268
|
-
}
|
|
1269
|
-
const canUndo = computed(() => undoStack.length > 0 && !props.readonly && !props.disable)
|
|
1270
|
-
const canRedo = computed(() => redoStack.length > 0 && !props.readonly && !props.disable)
|
|
1277
|
+
const pushHistory = () => history.push(takeSnapshot())
|
|
1278
|
+
const canUndo = computed(() => history.canUndo.value && !props.readonly && !props.disable)
|
|
1279
|
+
const canRedo = computed(() => history.canRedo.value && !props.readonly && !props.disable)
|
|
1271
1280
|
|
|
1272
1281
|
const restoreSnapshot = (s: Snapshot) => {
|
|
1273
1282
|
cols.value = s.cols.map((c) => ({ ...c }))
|
|
@@ -1282,17 +1291,35 @@ const restoreSnapshot = (s: Snapshot) => {
|
|
|
1282
1291
|
condRules.value = s.rules.map((r) => ({ ...r }))
|
|
1283
1292
|
emit("update:columns", cols.value)
|
|
1284
1293
|
pushRows(state.value)
|
|
1294
|
+
// La sélection peut pointer sur une ligne/colonne qui n'existe plus (redo d'une
|
|
1295
|
+
// suppression) : on la ramène sur la première cellule plutôt que de laisser un
|
|
1296
|
+
// curseur fantôme hors grille.
|
|
1297
|
+
const current = sel.value
|
|
1298
|
+
if (current) {
|
|
1299
|
+
const maxRow = state.value.length - 1
|
|
1300
|
+
const bad =
|
|
1301
|
+
colIndex(current.column) === -1 ||
|
|
1302
|
+
(current.endColumn !== undefined && colIndex(current.endColumn) === -1) ||
|
|
1303
|
+
current.row > maxRow ||
|
|
1304
|
+
(current.endRow !== undefined && current.endRow > maxRow)
|
|
1305
|
+
if (bad) {
|
|
1306
|
+
if (state.value.length && cols.value.length) select(0, cols.value[0]!.name)
|
|
1307
|
+
else setSel(null)
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1285
1310
|
}
|
|
1286
1311
|
const undo = () => {
|
|
1287
1312
|
if (!canUndo.value) return
|
|
1288
|
-
|
|
1289
|
-
|
|
1313
|
+
const snapshot = history.undo(takeSnapshot())
|
|
1314
|
+
if (!snapshot) return
|
|
1315
|
+
restoreSnapshot(snapshot)
|
|
1290
1316
|
emit("structure-change", { rows: state.value, columns: cols.value, reason: "undo" })
|
|
1291
1317
|
}
|
|
1292
1318
|
const redo = () => {
|
|
1293
1319
|
if (!canRedo.value) return
|
|
1294
|
-
|
|
1295
|
-
|
|
1320
|
+
const snapshot = history.redo(takeSnapshot())
|
|
1321
|
+
if (!snapshot) return
|
|
1322
|
+
restoreSnapshot(snapshot)
|
|
1296
1323
|
emit("structure-change", { rows: state.value, columns: cols.value, reason: "redo" })
|
|
1297
1324
|
}
|
|
1298
1325
|
|
|
@@ -1924,6 +1951,41 @@ const fxCanEdit = computed(() => {
|
|
|
1924
1951
|
)
|
|
1925
1952
|
})
|
|
1926
1953
|
|
|
1954
|
+
/**
|
|
1955
|
+
* Aperçu **live** du résultat de la formule en cours de saisie dans la barre fx.
|
|
1956
|
+
* Évaluée dans le contexte de la ligne sélectionnée (références par nom de colonne
|
|
1957
|
+
* et A1 résolues sur la grille mémorisée). `null` si la saisie n'est pas une formule.
|
|
1958
|
+
*/
|
|
1959
|
+
const fxPreview = computed<{ text: string; error: boolean; hint: string } | null>(() => {
|
|
1960
|
+
// Pendant l'édition en cellule, la grille affiche déjà le résultat.
|
|
1961
|
+
if (editing.value || !sel.value || !fxCanEdit.value) return null
|
|
1962
|
+
const source = fxDraft.value
|
|
1963
|
+
if (!isFormulaRaw(source)) return null
|
|
1964
|
+
const row = sel.value.row
|
|
1965
|
+
const value = evaluateFormula(source, {
|
|
1966
|
+
row,
|
|
1967
|
+
resolveField: (name) => {
|
|
1968
|
+
const i = colIndex(name)
|
|
1969
|
+
return i === -1 ? undefined : evaluated.value[row]?.[i]
|
|
1970
|
+
},
|
|
1971
|
+
resolveCell: (r, c) => evaluated.value[r]?.[c],
|
|
1972
|
+
})
|
|
1973
|
+
if (isError(value)) {
|
|
1974
|
+
return {
|
|
1975
|
+
text: value.code,
|
|
1976
|
+
error: true,
|
|
1977
|
+
hint: value.code === "#ERROR!" ? t("fxSyntax") : value.code,
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
if (value === null || value === undefined || value === "") {
|
|
1981
|
+
return { text: t("fxEmpty"), error: false, hint: "" }
|
|
1982
|
+
}
|
|
1983
|
+
const text = typeof value === "number"
|
|
1984
|
+
? new Intl.NumberFormat(lang.value, { maximumFractionDigits: 6 }).format(value)
|
|
1985
|
+
: String(value)
|
|
1986
|
+
return { text: text.length > 60 ? text.slice(0, 59) + "…" : text, error: false, hint: "" }
|
|
1987
|
+
})
|
|
1988
|
+
|
|
1927
1989
|
const commitFx = async () => {
|
|
1928
1990
|
if (!sel.value) return
|
|
1929
1991
|
if (!fxCanEdit.value) {
|
|
@@ -1939,7 +2001,21 @@ const commitFx = async () => {
|
|
|
1939
2001
|
}
|
|
1940
2002
|
|
|
1941
2003
|
const onFxInput = (e: Event) => {
|
|
1942
|
-
|
|
2004
|
+
const el = e.target as HTMLInputElement
|
|
2005
|
+
fxDraft.value = el.value
|
|
2006
|
+
fxCaret.value = el.selectionStart
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
/** Position du curseur depuis un événement (`null` si indisponible). */
|
|
2010
|
+
const caretFrom = (e: Event): number | null => {
|
|
2011
|
+
const el = e.target as HTMLInputElement | HTMLTextAreaElement | null
|
|
2012
|
+
return el && typeof el.selectionStart === "number" ? el.selectionStart : null
|
|
2013
|
+
}
|
|
2014
|
+
const syncFxCaret = (e: Event) => {
|
|
2015
|
+
fxCaret.value = caretFrom(e)
|
|
2016
|
+
}
|
|
2017
|
+
const syncEditCaret = (e: Event) => {
|
|
2018
|
+
editCaret.value = caretFrom(e)
|
|
1943
2019
|
}
|
|
1944
2020
|
|
|
1945
2021
|
const onFxKeydown = (e: KeyboardEvent) => {
|
|
@@ -2084,16 +2160,38 @@ const FORMULA_FNS: { name: string; sig: string }[] = [
|
|
|
2084
2160
|
// La source alimente des méthodes de chaîne (`.trim()`, `.startsWith()`) :
|
|
2085
2161
|
// la valeur brute d'une cellule peut être un nombre / booléen → on coerce.
|
|
2086
2162
|
const fxSource = computed(() => String((editing.value ? draft.value : fxDraft.value) ?? ""))
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2163
|
+
/** Position du curseur (fx bar / éditeur de cellule) — pilote les suggestions. */
|
|
2164
|
+
const fxCaret = ref<number | null>(null)
|
|
2165
|
+
const editCaret = ref<number | null>(null)
|
|
2166
|
+
const fxQuery = computed(() =>
|
|
2167
|
+
functionQueryAt(
|
|
2168
|
+
fxSource.value,
|
|
2169
|
+
(editing.value ? editCaret.value : fxCaret.value) ?? undefined,
|
|
2170
|
+
),
|
|
2171
|
+
)
|
|
2093
2172
|
const fxSuggestions = computed(() => {
|
|
2094
2173
|
const q = fxQuery.value
|
|
2095
2174
|
if (!q) return []
|
|
2096
|
-
|
|
2175
|
+
const fns = rankSuggestions(FORMULA_FNS, q, 8).map((f) => ({
|
|
2176
|
+
kind: "fn" as const,
|
|
2177
|
+
name: f.name,
|
|
2178
|
+
detail: f.sig,
|
|
2179
|
+
insert: f.name + "(",
|
|
2180
|
+
}))
|
|
2181
|
+
// Colonnes de la feuille : les formules acceptent `=price * qty`
|
|
2182
|
+
const fields = rankSuggestions(
|
|
2183
|
+
cols.value.map((col) => ({
|
|
2184
|
+
name: col.name,
|
|
2185
|
+
detail:
|
|
2186
|
+
col.label !== undefined && String(col.label) !== col.name
|
|
2187
|
+
? String(col.label)
|
|
2188
|
+
: t("fxColumn"),
|
|
2189
|
+
insert: col.name,
|
|
2190
|
+
})),
|
|
2191
|
+
q,
|
|
2192
|
+
4,
|
|
2193
|
+
).map((f) => ({ kind: "field" as const, ...f }))
|
|
2194
|
+
return [...fns, ...fields]
|
|
2097
2195
|
})
|
|
2098
2196
|
const fxHide = ref(false)
|
|
2099
2197
|
const fxActive = ref(0)
|
|
@@ -2102,6 +2200,23 @@ watch(fxQuery, () => {
|
|
|
2102
2200
|
fxActive.value = 0
|
|
2103
2201
|
fxHide.value = false
|
|
2104
2202
|
})
|
|
2203
|
+
/** Signature de la fonction au curseur (argument courant surligné). */
|
|
2204
|
+
const fxHint = computed<{ name: string; args: string[]; active: number } | null>(() => {
|
|
2205
|
+
const source = fxSource.value
|
|
2206
|
+
if (!isFormulaRaw(source)) return null
|
|
2207
|
+
const caret = Math.min(
|
|
2208
|
+
(editing.value ? editCaret.value : fxCaret.value) ?? source.length,
|
|
2209
|
+
source.length,
|
|
2210
|
+
)
|
|
2211
|
+
const call = functionCallAt(source, caret, FORMULA_FNS.map((f) => f.name))
|
|
2212
|
+
if (!call) return null
|
|
2213
|
+
const fn = FORMULA_FNS.find((f) => f.name.toLowerCase() === call.name)
|
|
2214
|
+
if (!fn) return null
|
|
2215
|
+
const args = signatureArgs(fn.sig)
|
|
2216
|
+
if (!args.length) return null
|
|
2217
|
+
return { name: fn.name, args, active: Math.min(call.index, args.length - 1) }
|
|
2218
|
+
})
|
|
2219
|
+
|
|
2105
2220
|
const fxPopStyle = ref<Record<string, string>>({})
|
|
2106
2221
|
const refreshFxPop = () => {
|
|
2107
2222
|
if (!fxOpen.value || !fxInputEl.value) return
|
|
@@ -2123,9 +2238,18 @@ const stepFx = (d: number) => {
|
|
|
2123
2238
|
const acceptFx = () => {
|
|
2124
2239
|
const f = fxSuggestions.value[fxActive.value]
|
|
2125
2240
|
if (!f) return
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2241
|
+
const source = editing.value ? String(draft.value ?? "") : String(fxDraft.value ?? "")
|
|
2242
|
+
const caret = Math.min(
|
|
2243
|
+
(editing.value ? editCaret.value : fxCaret.value) ?? source.length,
|
|
2244
|
+
source.length,
|
|
2245
|
+
)
|
|
2246
|
+
const query = functionQueryAt(source, caret)
|
|
2247
|
+
if (!query) return
|
|
2248
|
+
const start = caret - query.length
|
|
2249
|
+
const inserted = f.insert
|
|
2250
|
+
const next = source.slice(0, start) + inserted + source.slice(caret)
|
|
2251
|
+
if (editing.value) draft.value = next
|
|
2252
|
+
else fxDraft.value = next
|
|
2129
2253
|
fxActive.value = 0
|
|
2130
2254
|
nextTick(() => {
|
|
2131
2255
|
const el = editing.value
|
|
@@ -2133,7 +2257,12 @@ const acceptFx = () => {
|
|
|
2133
2257
|
? editAreaEl.value
|
|
2134
2258
|
: editInputEl.value
|
|
2135
2259
|
: fxInputEl.value
|
|
2136
|
-
el
|
|
2260
|
+
if (!el) return
|
|
2261
|
+
el.focus({ preventScroll: true })
|
|
2262
|
+
const pos = start + inserted.length
|
|
2263
|
+
el.setSelectionRange(pos, pos)
|
|
2264
|
+
if (editing.value) editCaret.value = pos
|
|
2265
|
+
else fxCaret.value = pos
|
|
2137
2266
|
})
|
|
2138
2267
|
}
|
|
2139
2268
|
const acceptFxAt = (i: number) => {
|
|
@@ -2960,8 +3089,8 @@ const applyFill = () => {
|
|
|
2960
3089
|
const sr = b.r0 + (((rr - b.r0) % h) + h) % h
|
|
2961
3090
|
const sc = b.c0 + (((cc - b.c0) % w) + w) % w
|
|
2962
3091
|
let raw = state.value[sr]?.[colNameAt(sc)]
|
|
2963
|
-
if (typeof raw === "string" && raw.startsWith("=")) {
|
|
2964
|
-
raw = shiftFormulaRefs(raw, rr - sr, cc - sc)
|
|
3092
|
+
if (typeof raw === "string" && raw.trim().startsWith("=")) {
|
|
3093
|
+
raw = shiftFormulaRefs(raw.trim(), rr - sr, cc - sc)
|
|
2965
3094
|
}
|
|
2966
3095
|
val = raw
|
|
2967
3096
|
}
|
|
@@ -3239,8 +3368,7 @@ const loadSheetIntoEngine = (idx: number) => {
|
|
|
3239
3368
|
colWidths.value = { ...ext.widths }
|
|
3240
3369
|
setSel(null)
|
|
3241
3370
|
editing.value = null
|
|
3242
|
-
|
|
3243
|
-
redoStack.length = 0
|
|
3371
|
+
history.clear()
|
|
3244
3372
|
if (scrollEl.value) scrollEl.value.scrollTop = 0
|
|
3245
3373
|
}
|
|
3246
3374
|
/**
|
|
@@ -3652,7 +3780,10 @@ const findReplaceCurrent = () => {
|
|
|
3652
3780
|
const next = findCase.value
|
|
3653
3781
|
? raw.split(findQuery.value).join(findReplaceText.value)
|
|
3654
3782
|
: raw.replace(new RegExp(escapeRegExp(findQuery.value), "gi"), findReplaceText.value)
|
|
3655
|
-
if (next !== raw)
|
|
3783
|
+
if (next !== raw) {
|
|
3784
|
+
pushHistory()
|
|
3785
|
+
setCellValue(m.r, m.name, raw, next)
|
|
3786
|
+
}
|
|
3656
3787
|
findScan()
|
|
3657
3788
|
findNext(1)
|
|
3658
3789
|
}
|
|
@@ -4192,6 +4323,7 @@ const validateAndSet = async (r: number, name: string, old: any, next: any): Pro
|
|
|
4192
4323
|
return false
|
|
4193
4324
|
}
|
|
4194
4325
|
clearValError(r, name)
|
|
4326
|
+
pushHistory()
|
|
4195
4327
|
setCellValue(r, name, old, next)
|
|
4196
4328
|
return true
|
|
4197
4329
|
}
|
|
@@ -4572,9 +4704,23 @@ defineExpose({
|
|
|
4572
4704
|
:placeholder="fxCanEdit ? t('fxPlaceholder') : t('fxNoSel')"
|
|
4573
4705
|
spellcheck="false"
|
|
4574
4706
|
@input="onFxInput"
|
|
4707
|
+
@keyup="syncFxCaret"
|
|
4708
|
+
@click="syncFxCaret"
|
|
4575
4709
|
@keydown="onFxKeydown"
|
|
4576
4710
|
@blur="commitFx"
|
|
4577
4711
|
/>
|
|
4712
|
+
<span v-if="fxHint && !fxOpen && !editing" class="q-spreadsheet__fxhint">
|
|
4713
|
+
<strong>{{ fxHint.name }}</strong>(<template v-for="(arg, i) in fxHint.args" :key="i"><span
|
|
4714
|
+
class="q-spreadsheet__fxhint-arg"
|
|
4715
|
+
:class="{ 'q-spreadsheet__fxhint-arg--on': i === fxHint.active }"
|
|
4716
|
+
>{{ arg }}</span><span v-if="i < fxHint.args.length - 1">, </span></template>)
|
|
4717
|
+
</span>
|
|
4718
|
+
<span
|
|
4719
|
+
v-if="fxPreview"
|
|
4720
|
+
class="q-spreadsheet__fx-preview"
|
|
4721
|
+
:class="{ 'q-spreadsheet__fx-preview--error': fxPreview.error }"
|
|
4722
|
+
:title="fxPreview.hint"
|
|
4723
|
+
><span class="q-spreadsheet__fx-preview-eq" aria-hidden="true">=</span>{{ fxPreview.text }}</span>
|
|
4578
4724
|
</div>
|
|
4579
4725
|
|
|
4580
4726
|
<!-- ═══════ Barre Find & Replace ═══════ -->
|
|
@@ -4954,6 +5100,9 @@ defineExpose({
|
|
|
4954
5100
|
class="q-spreadsheet__editor-input q-spreadsheet__editor-area"
|
|
4955
5101
|
:rows="draftLines"
|
|
4956
5102
|
spellcheck="false"
|
|
5103
|
+
@input="syncEditCaret"
|
|
5104
|
+
@keyup="syncEditCaret"
|
|
5105
|
+
@click="syncEditCaret"
|
|
4957
5106
|
@keydown="onEditKeydown"
|
|
4958
5107
|
@blur="commitEdit"
|
|
4959
5108
|
/>
|
|
@@ -4968,6 +5117,12 @@ defineExpose({
|
|
|
4968
5117
|
@keydown="onEditKeydown"
|
|
4969
5118
|
@blur="commitEdit"
|
|
4970
5119
|
/>
|
|
5120
|
+
<div v-if="editingIsFormula && fxHint && !fxOpen" class="q-spreadsheet__fxhint q-spreadsheet__fxhint--editor">
|
|
5121
|
+
<strong>{{ fxHint.name }}</strong>(<template v-for="(arg, i) in fxHint.args" :key="i"><span
|
|
5122
|
+
class="q-spreadsheet__fxhint-arg"
|
|
5123
|
+
:class="{ 'q-spreadsheet__fxhint-arg--on': i === fxHint.active }"
|
|
5124
|
+
>{{ arg }}</span><span v-if="i < fxHint.args.length - 1">, </span></template>)
|
|
5125
|
+
</div>
|
|
4971
5126
|
<div v-if="editingIsChoice && selectOptions.length" class="q-spreadsheet__options">
|
|
4972
5127
|
<button
|
|
4973
5128
|
v-for="opt in selectOptions"
|
|
@@ -4992,7 +5147,7 @@ defineExpose({
|
|
|
4992
5147
|
<div v-if="editingIsFormula && fxOpen" class="q-spreadsheet__fnsug">
|
|
4993
5148
|
<button
|
|
4994
5149
|
v-for="(f, fi) in fxSuggestions"
|
|
4995
|
-
:key="f.name"
|
|
5150
|
+
:key="`${f.kind}:${f.name}`"
|
|
4996
5151
|
type="button"
|
|
4997
5152
|
class="q-spreadsheet__fnsug-item"
|
|
4998
5153
|
:class="{ 'q-spreadsheet__fnsug-item--on': fxActive === fi }"
|
|
@@ -5000,7 +5155,8 @@ defineExpose({
|
|
|
5000
5155
|
@click="acceptFxAt(fi)"
|
|
5001
5156
|
>
|
|
5002
5157
|
<code>{{ f.name }}</code>
|
|
5003
|
-
<span>({{ f.
|
|
5158
|
+
<span v-if="f.kind === 'fn'">({{ f.detail }})</span>
|
|
5159
|
+
<span v-else class="q-spreadsheet__fnsug-kind">{{ f.detail }}</span>
|
|
5004
5160
|
</button>
|
|
5005
5161
|
</div>
|
|
5006
5162
|
</div>
|
|
@@ -5297,7 +5453,7 @@ defineExpose({
|
|
|
5297
5453
|
>
|
|
5298
5454
|
<button
|
|
5299
5455
|
v-for="(f, fi) in fxSuggestions"
|
|
5300
|
-
:key="f.name"
|
|
5456
|
+
:key="`${f.kind}:${f.name}`"
|
|
5301
5457
|
type="button"
|
|
5302
5458
|
class="q-spreadsheet__fnsug-item"
|
|
5303
5459
|
:class="{ 'q-spreadsheet__fnsug-item--on': fxActive === fi }"
|
|
@@ -5305,7 +5461,8 @@ defineExpose({
|
|
|
5305
5461
|
@click="acceptFxAt(fi)"
|
|
5306
5462
|
>
|
|
5307
5463
|
<code>{{ f.name }}</code>
|
|
5308
|
-
<span>({{ f.
|
|
5464
|
+
<span v-if="f.kind === 'fn'">({{ f.detail }})</span>
|
|
5465
|
+
<span v-else class="q-spreadsheet__fnsug-kind">{{ f.detail }}</span>
|
|
5309
5466
|
</button>
|
|
5310
5467
|
</div>
|
|
5311
5468
|
</Teleport>
|
package/index.ts
CHANGED
|
@@ -203,19 +203,29 @@ export { DEFAULT_LAYOUT_VIEW, parseView, qLayoutKey } from "./lib/layout"
|
|
|
203
203
|
export type { LayoutView, LayoutZone, LayoutZonePlacement, QLayoutContext } from "./lib/layout"
|
|
204
204
|
export { vRipple } from "./lib/ripple"
|
|
205
205
|
export type { RippleOptions, RippleValue } from "./lib/ripple"
|
|
206
|
-
export { chartToECharts, channelValue, pickFromEvent, tableModel, textValue } from "./lib/chart"
|
|
206
|
+
export { chartToECharts, channelValue, pickFromEvent, tableCsv, tableModel, textValue } from "./lib/chart"
|
|
207
207
|
export type {
|
|
208
208
|
QChartAxis,
|
|
209
209
|
QChartChannel,
|
|
210
210
|
QChartColumn,
|
|
211
211
|
QChartConfig,
|
|
212
|
+
QChartFormat,
|
|
213
|
+
QChartFormatKind,
|
|
214
|
+
QChartFormatOptions,
|
|
215
|
+
QChartGaugeBand,
|
|
212
216
|
QChartLegend,
|
|
213
217
|
QChartMark,
|
|
214
218
|
QChartMarkType,
|
|
215
219
|
QChartOrientation,
|
|
216
220
|
QChartTable,
|
|
221
|
+
QChartTableTotals,
|
|
222
|
+
QChartTableView,
|
|
223
|
+
QChartThreshold,
|
|
224
|
+
QChartTooltip,
|
|
217
225
|
QChartPick,
|
|
218
226
|
} from "./lib/chart"
|
|
227
|
+
export { DEFAULT_CURRENCY, DEFAULT_LOCALE, formatValue } from "./lib/chartFormat"
|
|
228
|
+
export { boxStats, histogram, linearFit, waterfallSteps } from "./lib/chartStats"
|
|
219
229
|
export { vTouchPan } from "./lib/touchPan"
|
|
220
230
|
export type { PanDetails, TouchPanHandler, PanDirection } from "./lib/touchPan"
|
|
221
231
|
export { vTouchHold } from "./lib/touchHold"
|
package/lib/chart.axis.test.ts
CHANGED
|
@@ -28,7 +28,7 @@ function renderSvg(config: Record<string, any>, width: number, height: number):
|
|
|
28
28
|
width,
|
|
29
29
|
height,
|
|
30
30
|
})
|
|
31
|
-
chart.setOption(chartToECharts({ theme: THEME, ...config }))
|
|
31
|
+
chart.setOption(chartToECharts({ marks: [], theme: THEME, ...config }))
|
|
32
32
|
const svg = chart.renderToSVGString()
|
|
33
33
|
chart.dispose()
|
|
34
34
|
return svg
|