@eifi1/ui-kit 0.22.0 → 0.24.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.
- package/README.md +42 -25
- package/dist/chart.d.ts +8 -7
- package/dist/components/amount-input.d.ts +34 -7
- package/dist/components/amount-input.js +14 -10
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +26 -0
- package/dist/components/autocomplete.js +164 -137
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +8 -7
- package/dist/components/calculator.d.ts +8 -7
- package/dist/components/column-mapper.d.ts +235 -0
- package/dist/components/column-mapper.js +411 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +113 -3
- package/dist/components/combobox.js +456 -313
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +8 -7
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -4
- package/dist/components/country-select.d.ts +50 -7
- package/dist/components/country-select.js +65 -9
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +8 -7
- package/dist/components/danger-confirm.js +64 -7
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/date-picker.d.ts +41 -0
- package/dist/components/date-picker.js +269 -8
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +48 -2
- package/dist/components/entity-combobox.js +159 -114
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +8 -7
- package/dist/components/field-parts.d.ts +116 -6
- package/dist/components/field-parts.js +84 -0
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +8 -7
- package/dist/components/file-dropzone.d.ts +8 -7
- package/dist/components/form-actions.d.ts +8 -7
- package/dist/components/form-actions.js +23 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +8 -7
- package/dist/components/icon-picker.d.ts +10 -1
- package/dist/components/icon-picker.js +12 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/language-select.d.ts +8 -7
- package/dist/components/money-field.d.ts +11 -7
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.js +2 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +31 -2
- package/dist/components/multi-entity-combobox.js +152 -103
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +8 -7
- package/dist/components/number-input.d.ts +8 -7
- package/dist/components/numpad-sheet.d.ts +8 -7
- package/dist/components/phone-input.d.ts +8 -7
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +8 -7
- package/dist/components/settings-fields.d.ts +8 -7
- package/dist/components/share-card.d.ts +8 -7
- package/dist/components/swatch-picker.d.ts +17 -1
- package/dist/components/swatch-picker.js +10 -4
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.d.ts +8 -7
- package/dist/components/tile-radio.d.ts +20 -0
- package/dist/components/tile-radio.js +25 -8
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +8 -7
- package/dist/components/toggle-group.js +2 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +5 -4
- package/dist/components/ui.js +60 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +66 -1
- package/dist/feedback/feedback-attachment.js +185 -65
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +66 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +67 -252
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +66 -287
- package/dist/feedback/feedback-thread.js +16 -3
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
- package/dist/feedback.d.ts +65 -3
- package/dist/hooks/use-file-drop.d.ts +8 -7
- package/dist/i18n/defaults.d.ts +8 -7
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +8 -7
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +8 -7
- package/dist/i18n/locales/de-CH.d.ts +8 -7
- package/dist/i18n/locales/en.d.ts +8 -7
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +8 -7
- package/dist/i18n/locales/es.js +37 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -7
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -7
- package/dist/i18n/locales/hu.js +38 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -7
- package/dist/i18n/locales/it.js +37 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -7
- package/dist/i18n/locales/zh.js +34 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +8 -7
- package/dist/i18n/review.js +20 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- package/dist/lib/strip-fade.d.ts +2 -2
- package/dist/lib/strip-fade.js.map +1 -1
- package/dist/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +142 -13
- package/dist/rhf/fields.js +185 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +8 -7
- package/dist/rhf.d.ts +9 -8
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +8 -7
- package/dist/shell/top-bar-brand.d.ts +8 -7
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +8 -7
- package/dist/table-text.d.ts +1 -1
- package/dist/wizard/stepper-nav.d.ts +38 -9
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +8 -7
- package/package.json +1 -1
- package/src/components/amount-input.tsx +50 -14
- package/src/components/autocomplete.tsx +188 -116
- package/src/components/column-mapper.tsx +768 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +655 -335
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +135 -22
- package/src/components/danger-confirm.tsx +274 -24
- package/src/components/date-picker.tsx +423 -10
- package/src/components/entity-combobox.tsx +243 -129
- package/src/components/field-parts.tsx +224 -5
- package/src/components/field-strip.tsx +149 -0
- package/src/components/form-actions.tsx +52 -5
- package/src/components/icon-picker.tsx +23 -4
- package/src/components/money-field.tsx +3 -0
- package/src/components/month-picker.tsx +2 -1
- package/src/components/multi-entity-combobox.tsx +221 -121
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/swatch-picker.tsx +29 -5
- package/src/components/tile-radio.tsx +74 -13
- package/src/components/toggle-group.tsx +2 -2
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +114 -33
- package/src/feedback/feedback-attachment.tsx +372 -72
- package/src/feedback/feedback-dialog.tsx +7 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +112 -9
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +42 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/en.ts +27 -5
- package/src/i18n/locales/es.ts +41 -0
- package/src/i18n/locales/fr.ts +42 -0
- package/src/i18n/locales/hu.ts +37 -0
- package/src/i18n/locales/it.ts +41 -0
- package/src/i18n/locales/zh.ts +32 -0
- package/src/i18n/review.ts +19 -0
- package/src/index.ts +18 -0
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/strip-fade.ts +2 -2
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +421 -6
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/wizard/stepper-nav.tsx +35 -1
- package/dist/components/field-anatomy.d.ts +0 -95
- package/dist/components/field-anatomy.js +0 -84
- package/dist/components/field-anatomy.js.map +0 -1
- package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
- package/src/components/field-anatomy.tsx +0 -190
package/src/i18n/locales/it.ts
CHANGED
|
@@ -26,6 +26,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
26
26
|
attachmentRemoveFile: (name) => `Rimuovi ${name}`,
|
|
27
27
|
attachmentLimit: (max) => `Massimo ${n(max)} ${max === 1 ? "allegato" : "allegati"}: ne rimuova uno per aggiungerne un altro.`,
|
|
28
28
|
attachmentUploading: "Caricamento…",
|
|
29
|
+
attachmentUploadFailed: (name) => `Impossibile caricare «${name}»`,
|
|
29
30
|
},
|
|
30
31
|
feedbackDialog: {
|
|
31
32
|
title: "Invia feedback",
|
|
@@ -332,6 +333,9 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
332
333
|
password: "Password",
|
|
333
334
|
phrase: (phrase) => `Digiti «${phrase}» per confermare`,
|
|
334
335
|
acknowledge: "Ho letto cosa comporta questa operazione e desidero continuare.",
|
|
336
|
+
needsPhrase: (phrase) => `Digiti «${phrase}» per confermare`,
|
|
337
|
+
needsAcknowledge: "Spunti la casella per confermare",
|
|
338
|
+
needsPassword: "Inserisca la password per confermare",
|
|
335
339
|
},
|
|
336
340
|
tabs: {
|
|
337
341
|
add: "Aggiungi scheda",
|
|
@@ -684,6 +688,43 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
684
688
|
// "premere per…": an instruction without a tu imperative, as the kit's Italian is formal.
|
|
685
689
|
direction: (current, next) => `Direzione: ${current} – premere per passare a ${next}`,
|
|
686
690
|
},
|
|
691
|
+
columnMapper: {
|
|
692
|
+
paste: "Incolla una tabella",
|
|
693
|
+
pasteHint:
|
|
694
|
+
"Copi le righe da un foglio di calcolo e le incolli qui, oppure trascini un file CSV o di testo.",
|
|
695
|
+
chooseFile: "Scegli un file",
|
|
696
|
+
readError: (name) => `Impossibile leggere «${name}»`,
|
|
697
|
+
headerRow: "La prima riga contiene i nomi delle colonne",
|
|
698
|
+
summary: (columns, rows) =>
|
|
699
|
+
`${n(columns)} ${plural(columns, "colonna", "colonne")}, ${n(rows)} ${plural(rows, "riga", "righe")}`,
|
|
700
|
+
// Fragments of the summary line. "Separato da…" would have to agree with a noun the
|
|
701
|
+
// line does not name, so the separator is stated as a value.
|
|
702
|
+
separatorSemicolon: "separatore: punto e virgola",
|
|
703
|
+
separatorComma: "separatore: virgola",
|
|
704
|
+
separatorTab: "separatore: tabulazione",
|
|
705
|
+
separatorSpace: "separatore: spazio",
|
|
706
|
+
// The examples stay as written: they show the convention, not the reader's locale.
|
|
707
|
+
decimalComma: "virgola decimale (1,5)",
|
|
708
|
+
decimalPoint: "punto decimale (1.5)",
|
|
709
|
+
unreadCount: (count) =>
|
|
710
|
+
count === 1 ? "Impossibile leggere 1 riga" : `Impossibile leggere ${n(count)} righe`,
|
|
711
|
+
unreadLine: (line) => `Impossibile leggere la riga ${line}`,
|
|
712
|
+
unreadMore: (count) => (count === 1 ? "…e un’altra" : `…e altre ${n(count)}`),
|
|
713
|
+
noRows: "Nessuna riga di questo testo si può leggere come riga della tabella.",
|
|
714
|
+
table: "Colonne e relativi ruoli",
|
|
715
|
+
columnN: (column) => `Colonna ${column}`,
|
|
716
|
+
roleOf: (column) => `Che cosa contiene la colonna «${column}»?`,
|
|
717
|
+
ignore: "Ignora",
|
|
718
|
+
requiredRole: (role) => `${role} (obbligatorio)`,
|
|
719
|
+
previewOf: (shown, total) =>
|
|
720
|
+
shown === 1
|
|
721
|
+
? `La prima di ${n(total)} righe`
|
|
722
|
+
: `Le prime ${n(shown)} di ${n(total)} righe`,
|
|
723
|
+
// `roles` arrives joined with "o" ("Dare o Avere").
|
|
724
|
+
oneOf: (roles) => `${roles} (a scelta)`,
|
|
725
|
+
// A participle phrase: no verb to agree with however many roles follow.
|
|
726
|
+
missing: (roles) => `Ancora da assegnare: ${roles}.`,
|
|
727
|
+
},
|
|
687
728
|
translationReview: {
|
|
688
729
|
statusMissing: "Mancante",
|
|
689
730
|
statusUnreviewed: "Non rivisto",
|
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -25,6 +25,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
25
25
|
attachmentRemoveFile: (name) => `移除 ${name}`,
|
|
26
26
|
attachmentLimit: (max) => `最多 ${n(max)} 个附件,请先移除一个再添加。`,
|
|
27
27
|
attachmentUploading: "正在上传…",
|
|
28
|
+
attachmentUploadFailed: (name) => `无法上传“${name}”`,
|
|
28
29
|
},
|
|
29
30
|
feedbackDialog: {
|
|
30
31
|
title: "发送反馈",
|
|
@@ -324,6 +325,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
324
325
|
password: "密码",
|
|
325
326
|
phrase: (phrase) => `输入“${phrase}”以确认`,
|
|
326
327
|
acknowledge: "我已了解此操作的作用,并希望继续。",
|
|
328
|
+
needsPhrase: (phrase) => `输入“${phrase}”以确认`,
|
|
329
|
+
needsAcknowledge: "勾选复选框以确认",
|
|
330
|
+
needsPassword: "输入密码以确认",
|
|
327
331
|
},
|
|
328
332
|
tabs: {
|
|
329
333
|
add: "添加标签页",
|
|
@@ -667,6 +671,34 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
667
671
|
inflow: "收入",
|
|
668
672
|
direction: (current, next) => `方向:${current},切换为${next}`,
|
|
669
673
|
},
|
|
674
|
+
columnMapper: {
|
|
675
|
+
paste: "粘贴表格",
|
|
676
|
+
pasteHint: "从电子表格中复制行并粘贴到此处,或拖放 CSV 或文本文件。",
|
|
677
|
+
chooseFile: "选择文件",
|
|
678
|
+
readError: (name) => `无法读取“${name}”`,
|
|
679
|
+
headerRow: "第一行为列名",
|
|
680
|
+
summary: (columns, rows) => `${n(columns)} 列,${n(rows)} 行`,
|
|
681
|
+
separatorSemicolon: "以分号分隔",
|
|
682
|
+
separatorComma: "以逗号分隔",
|
|
683
|
+
separatorTab: "以制表符分隔",
|
|
684
|
+
separatorSpace: "以空格分隔",
|
|
685
|
+
// The examples stay as written: they show the convention, not the reader's locale.
|
|
686
|
+
decimalComma: "小数点为逗号(1,5)",
|
|
687
|
+
decimalPoint: "小数点为句点(1.5)",
|
|
688
|
+
unreadCount: (count) => `${n(count)} 行无法读取`,
|
|
689
|
+
unreadLine: (line) => `无法读取第 ${line} 行`,
|
|
690
|
+
unreadMore: (count) => `……另有 ${n(count)} 行`,
|
|
691
|
+
noRows: "此文本中没有任何一行能读作表格的一行。",
|
|
692
|
+
table: "列及其用途",
|
|
693
|
+
columnN: (column) => `第 ${column} 列`,
|
|
694
|
+
roleOf: (column) => `“${column}”列的内容是什么?`,
|
|
695
|
+
ignore: "忽略",
|
|
696
|
+
requiredRole: (role) => `${role}(必填)`,
|
|
697
|
+
previewOf: (shown, total) => `显示前 ${n(shown)} 行,共 ${n(total)} 行`,
|
|
698
|
+
// `roles` arrives joined with "或" ("借方或贷方").
|
|
699
|
+
oneOf: (roles) => `${roles}(任选其一)`,
|
|
700
|
+
missing: (roles) => `仍需指定:${roles}。`,
|
|
701
|
+
},
|
|
670
702
|
translationReview: {
|
|
671
703
|
statusMissing: "缺失",
|
|
672
704
|
statusUnreviewed: "未审校",
|
package/src/i18n/review.ts
CHANGED
|
@@ -122,6 +122,7 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
122
122
|
"passwordStrength.optional": [["{{rule}}"]],
|
|
123
123
|
"passwordStrength.tooLong": [[72]],
|
|
124
124
|
"dangerConfirm.phrase": [["{{phrase}}"]],
|
|
125
|
+
"dangerConfirm.needsPhrase": [["{{phrase}}"]],
|
|
125
126
|
"iconPicker.resultCount": one,
|
|
126
127
|
"filePicker.rejectedType": [["{{name}}"]],
|
|
127
128
|
"filePicker.rejectedTypeOnly": [["{{accept}}", "{{name}}"]],
|
|
@@ -147,6 +148,7 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
147
148
|
"measuredGrid.problems": one,
|
|
148
149
|
"feedbackAttachment.attachmentRemoveFile": [["{{name}}"]],
|
|
149
150
|
"feedbackAttachment.attachmentLimit": one,
|
|
151
|
+
"feedbackAttachment.attachmentUploadFailed": [["{{name}}"]],
|
|
150
152
|
"feedbackComposer.sendHint": [["{{modifier}}"]],
|
|
151
153
|
"accountSettings.passkeys.renameItem": [["{{name}}"]],
|
|
152
154
|
"accountSettings.passkeys.renameField": [["{{name}}"]],
|
|
@@ -197,4 +199,21 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
197
199
|
"ibanInput.country": [["{{code}}"]],
|
|
198
200
|
"ibanInput.length": [[20, 21]],
|
|
199
201
|
"signChip.direction": [["{{current}}", "{{next}}"]],
|
|
202
|
+
"columnMapper.readError": [["{{name}}"]],
|
|
203
|
+
"columnMapper.summary": [
|
|
204
|
+
[1, 1],
|
|
205
|
+
[3, 12],
|
|
206
|
+
],
|
|
207
|
+
"columnMapper.unreadCount": one,
|
|
208
|
+
"columnMapper.unreadLine": [[3]],
|
|
209
|
+
"columnMapper.unreadMore": one,
|
|
210
|
+
"columnMapper.columnN": [[3]],
|
|
211
|
+
"columnMapper.roleOf": [["{{column}}"]],
|
|
212
|
+
"columnMapper.requiredRole": [["{{role}}"]],
|
|
213
|
+
"columnMapper.previewOf": [
|
|
214
|
+
[1, 12],
|
|
215
|
+
[10, 250],
|
|
216
|
+
],
|
|
217
|
+
"columnMapper.oneOf": [["{{roles}}"]],
|
|
218
|
+
"columnMapper.missing": [["{{roles}}"]],
|
|
200
219
|
};
|
package/src/index.ts
CHANGED
|
@@ -129,6 +129,8 @@ export type {
|
|
|
129
129
|
TypedConfirmFieldLabels,
|
|
130
130
|
CurrentPasswordInputProps,
|
|
131
131
|
CurrentPasswordInputLabels,
|
|
132
|
+
// 0.23.0: onConfirm's second argument — the guards' values (keksdose G4b).
|
|
133
|
+
DangerConfirmValues,
|
|
132
134
|
} from "./components/danger-confirm";
|
|
133
135
|
// 0.18: re-authentication before a sensitive action (Kurvenschmiede 4).
|
|
134
136
|
export { ReauthDialog, DEFAULT_REAUTH_DIALOG_LABELS } from "./components/reauth-dialog";
|
|
@@ -456,3 +458,19 @@ export {
|
|
|
456
458
|
isE164,
|
|
457
459
|
} from "./lib/phone";
|
|
458
460
|
export type { PhoneCountryCode, PhoneCountry, ParsedPhone } from "./lib/phone";
|
|
461
|
+
|
|
462
|
+
// ── 0.23.0: the apps' 0.22 adoption round (kastlan, keksdose, Kurvenschmiede) ──
|
|
463
|
+
// The label strip over custom content (keksdose G8).
|
|
464
|
+
export * from "./components/field-strip";
|
|
465
|
+
// Paste or drop a table, give each column a role (Kurvenschmiede's columns input,
|
|
466
|
+
// keksdose's import map step). The lexer itself stays in `@eifi1/ui-kit/table-text`.
|
|
467
|
+
export * from "./components/column-mapper";
|
|
468
|
+
export {
|
|
469
|
+
assignColumnRole,
|
|
470
|
+
guessMapping,
|
|
471
|
+
missingRoles,
|
|
472
|
+
readMappedTable,
|
|
473
|
+
readTextFile,
|
|
474
|
+
roleOfColumn,
|
|
475
|
+
} from "./lib/column-mapping";
|
|
476
|
+
export type { ColumnMapperResult, ColumnMapping, ColumnRole } from "./lib/column-mapping";
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which column of a table plays which part — the headless half of {@link ColumnMapper}.
|
|
3
|
+
*
|
|
4
|
+
* Two apps built this by hand, the same way. Kurvenschmiede's columns input
|
|
5
|
+
* (`features/control/columns-input.tsx`) reads a recorder's export and asks which
|
|
6
|
+
* column is the time, the setpoint and the actual; keksdose's bank-file import
|
|
7
|
+
* (`import-map-step.tsx`, `use-file-import.ts`) shows a bank's CSV and asks which
|
|
8
|
+
* column is the booking date, the amount, the payee. Both then have the same four
|
|
9
|
+
* rules, and keksdose wrote them down after getting each one wrong once:
|
|
10
|
+
*
|
|
11
|
+
* * **A role sits on one column at most, and a column carries one role.** Picking a
|
|
12
|
+
* role for a column MOVES it there from wherever it was ({@link assignColumnRole});
|
|
13
|
+
* the column it left falls back to "ignore" in the same step. There is no error
|
|
14
|
+
* state, because there is nothing wrong to report.
|
|
15
|
+
* * **What is still missing is named**, not left to a disabled button
|
|
16
|
+
* ({@link missingRoles}). A required role can be one of a group: a bank export that
|
|
17
|
+
* splits debit and credit has no amount column and needs none, so `required` may
|
|
18
|
+
* name a group, satisfied by any one of its members (keksdose's "money").
|
|
19
|
+
* * **A mapping is indexes into ONE table.** When the column count changes, the
|
|
20
|
+
* indexes mean nothing any more and are guessed afresh; when it does not (the
|
|
21
|
+
* header line toggled), they still point where they did — keksdose's `keepRoles`.
|
|
22
|
+
* * **The rows a role cannot read are reported by line**, not dropped in silence
|
|
23
|
+
* ({@link readMappedTable}): a footer under a recording, a total under a statement.
|
|
24
|
+
*
|
|
25
|
+
* Pure functions over plain data, so a two-step wizard can hold the mapping in its own
|
|
26
|
+
* state between steps, and a table the server sniffed (keksdose's) can be mapped with
|
|
27
|
+
* the same rules as one parsed here.
|
|
28
|
+
*/
|
|
29
|
+
import { tableNumber } from "./table-text";
|
|
30
|
+
import type { TableLine, TextTable } from "./table-text";
|
|
31
|
+
|
|
32
|
+
/** One part a column can play, defined by the caller. */
|
|
33
|
+
export interface ColumnRole<R extends string = string> {
|
|
34
|
+
/** The key the mapping is stored under. */
|
|
35
|
+
value: R;
|
|
36
|
+
/** What the role select shows — translated by the caller. */
|
|
37
|
+
label: string;
|
|
38
|
+
/**
|
|
39
|
+
* `true`: the table cannot be used until a column plays this role. A string names a
|
|
40
|
+
* GROUP: the table needs any one of the roles that share it — keksdose's amount,
|
|
41
|
+
* debit and credit, any of which carries the money.
|
|
42
|
+
*/
|
|
43
|
+
required?: boolean | string;
|
|
44
|
+
/**
|
|
45
|
+
* Every cell this role reads must be a number in the table's decimal convention
|
|
46
|
+
* ({@link tableNumber}). A row where one is not — a footer, a units line, a total
|
|
47
|
+
* with a grouping mark — is moved to `unread` with its line number instead of being
|
|
48
|
+
* handed over. Kurvenschmiede's time, setpoint and actual. The cells stay strings:
|
|
49
|
+
* converting is the caller's, with `tableNumber(cell, result.decimalComma)`.
|
|
50
|
+
*/
|
|
51
|
+
numeric?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Role → column index. A role that is absent or `null` sits on no column — so a
|
|
55
|
+
* mapping with nullable fields (keksdose's `CsvMapping`) is one as it stands. */
|
|
56
|
+
export type ColumnMapping<R extends string = string> = Partial<Record<R, number | null>>;
|
|
57
|
+
|
|
58
|
+
/** What the table is, read through a mapping. */
|
|
59
|
+
export interface ColumnMapperResult<R extends string = string> extends TextTable {
|
|
60
|
+
mapping: ColumnMapping<R>;
|
|
61
|
+
/** What the table still needs before it can be used: one entry per gap, each the
|
|
62
|
+
* roles any of which would fill it (one role, or a required group). */
|
|
63
|
+
missing: R[][];
|
|
64
|
+
/** Every required role placed, and at least one row read. */
|
|
65
|
+
complete: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const placedAt = <R extends string>(mapping: ColumnMapping<R>, role: R, width?: number): number | null => {
|
|
69
|
+
const column = mapping[role];
|
|
70
|
+
if (typeof column !== "number") return null;
|
|
71
|
+
return width === undefined || column < width ? column : null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** The role on this column, or `null` for an ignored one. */
|
|
75
|
+
export function roleOfColumn<R extends string>(
|
|
76
|
+
roles: readonly ColumnRole<R>[],
|
|
77
|
+
mapping: ColumnMapping<R>,
|
|
78
|
+
column: number,
|
|
79
|
+
): R | null {
|
|
80
|
+
return roles.find((role) => mapping[role.value] === column)?.value ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Put `role` on `column` — or, with `null`, take the column's role off it.
|
|
85
|
+
*
|
|
86
|
+
* Whatever this column played before stops, and the role moves here from any other
|
|
87
|
+
* column, in one new mapping (keksdose's `assignColumn`: "both halves are patches to
|
|
88
|
+
* the same object"). Other fields of the mapping are kept as they are.
|
|
89
|
+
*/
|
|
90
|
+
export function assignColumnRole<R extends string, M extends ColumnMapping<R>>(
|
|
91
|
+
roles: readonly ColumnRole<R>[],
|
|
92
|
+
mapping: M,
|
|
93
|
+
column: number,
|
|
94
|
+
role: R | null,
|
|
95
|
+
): M {
|
|
96
|
+
const next = { ...mapping } as Record<string, unknown>;
|
|
97
|
+
for (const { value } of roles) {
|
|
98
|
+
if (mapping[value] === column) next[value] = null;
|
|
99
|
+
}
|
|
100
|
+
if (role !== null) next[role] = column;
|
|
101
|
+
return next as M;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The gaps: each required role with no column, and each required group none of whose
|
|
106
|
+
* roles has one. In the order of `roles`. A column index past `width` is no column.
|
|
107
|
+
*/
|
|
108
|
+
export function missingRoles<R extends string>(
|
|
109
|
+
roles: readonly ColumnRole<R>[],
|
|
110
|
+
mapping: ColumnMapping<R>,
|
|
111
|
+
width?: number,
|
|
112
|
+
): R[][] {
|
|
113
|
+
const gaps: R[][] = [];
|
|
114
|
+
const groups = new Set<string>();
|
|
115
|
+
for (const role of roles) {
|
|
116
|
+
if (role.required === true) {
|
|
117
|
+
if (placedAt(mapping, role.value, width) === null) gaps.push([role.value]);
|
|
118
|
+
} else if (typeof role.required === "string" && !groups.has(role.required)) {
|
|
119
|
+
groups.add(role.required);
|
|
120
|
+
const members = roles.filter((other) => other.required === role.required);
|
|
121
|
+
if (members.every((member) => placedAt(mapping, member.value, width) === null)) {
|
|
122
|
+
gaps.push(members.map((member) => member.value));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return gaps;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const normalised = (text: string) => text.trim().toLowerCase();
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A first mapping for a table nobody has mapped yet.
|
|
133
|
+
*
|
|
134
|
+
* A column whose header IS a role's label or key gets that role. The required roles
|
|
135
|
+
* left over then take the free columns in order — Kurvenschmiede's "time, setpoint,
|
|
136
|
+
* actual are the first three columns", which is how a recorder writes them. Optional
|
|
137
|
+
* roles are not guessed by position: in a bank
|
|
138
|
+
* export of twelve columns, "the fourth role is the fourth column" is a wrong answer
|
|
139
|
+
* presented as a suggestion, and an "ignore" is the honest one.
|
|
140
|
+
*/
|
|
141
|
+
export function guessMapping<R extends string>(
|
|
142
|
+
roles: readonly ColumnRole<R>[],
|
|
143
|
+
header: readonly string[] | null,
|
|
144
|
+
width: number,
|
|
145
|
+
): ColumnMapping<R> {
|
|
146
|
+
const mapping: ColumnMapping<R> = {};
|
|
147
|
+
const taken = new Set<number>();
|
|
148
|
+
if (header) {
|
|
149
|
+
for (const role of roles) {
|
|
150
|
+
const names = [normalised(role.label), normalised(role.value)];
|
|
151
|
+
const column = header.findIndex((name, index) => !taken.has(index) && names.includes(normalised(name)));
|
|
152
|
+
if (column >= 0) {
|
|
153
|
+
mapping[role.value] = column;
|
|
154
|
+
taken.add(column);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const free = Array.from({ length: width }, (_, index) => index).filter((index) => !taken.has(index));
|
|
159
|
+
for (const role of roles) {
|
|
160
|
+
if (role.required !== true || typeof mapping[role.value] === "number") continue;
|
|
161
|
+
const column = free.shift();
|
|
162
|
+
if (column === undefined) break;
|
|
163
|
+
mapping[role.value] = column;
|
|
164
|
+
}
|
|
165
|
+
return mapping;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The table read through the mapping: the rows every `numeric` role can read, the
|
|
170
|
+
* others moved to `unread` (in line order, with the parse's own), what is missing,
|
|
171
|
+
* and whether the table can be used. Column indexes past the table's width count as
|
|
172
|
+
* unplaced.
|
|
173
|
+
*/
|
|
174
|
+
export function readMappedTable<R extends string>(
|
|
175
|
+
table: TextTable,
|
|
176
|
+
roles: readonly ColumnRole<R>[],
|
|
177
|
+
mapping: ColumnMapping<R>,
|
|
178
|
+
): ColumnMapperResult<R> {
|
|
179
|
+
const numeric = roles
|
|
180
|
+
.filter((role) => role.numeric)
|
|
181
|
+
.map((role) => placedAt(mapping, role.value, table.width))
|
|
182
|
+
.filter((column): column is number => column !== null);
|
|
183
|
+
const rows: string[][] = [];
|
|
184
|
+
const lines: TableLine[] = [];
|
|
185
|
+
const refused: TableLine[] = [];
|
|
186
|
+
table.rows.forEach((row, index) => {
|
|
187
|
+
if (numeric.every((column) => Number.isFinite(tableNumber(row[column] ?? "", table.decimalComma)))) {
|
|
188
|
+
rows.push(row);
|
|
189
|
+
lines.push(table.lines[index]);
|
|
190
|
+
} else {
|
|
191
|
+
refused.push(table.lines[index]);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const unread = refused.length ? [...table.unread, ...refused].sort((a, b) => a.line - b.line) : table.unread;
|
|
195
|
+
const missing = missingRoles(roles, mapping, table.width);
|
|
196
|
+
return {
|
|
197
|
+
...table,
|
|
198
|
+
rows,
|
|
199
|
+
lines,
|
|
200
|
+
unread,
|
|
201
|
+
mapping,
|
|
202
|
+
missing,
|
|
203
|
+
complete: missing.length === 0 && rows.length > 0,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A dropped or chosen file's text — UTF-8 when it is UTF-8, else Windows-1252.
|
|
209
|
+
*
|
|
210
|
+
* A German bank or bench export is very often written in Windows-1252, and reading it
|
|
211
|
+
* as UTF-8 turns every "ä" in a column name into "�" — no error, just a header nobody
|
|
212
|
+
* recognises. So the bytes are tried as strict UTF-8 first and read as Windows-1252
|
|
213
|
+
* when that fails (keksdose's server sniffs the encoding for the same reason; a file
|
|
214
|
+
* read in the browser had nothing doing it). Falls back to `FileReader` where a `File`
|
|
215
|
+
* has no `arrayBuffer` (Kurvenschmiede's `readText`).
|
|
216
|
+
*/
|
|
217
|
+
export async function readTextFile(file: Blob): Promise<string> {
|
|
218
|
+
const bytes = await bytesOf(file);
|
|
219
|
+
try {
|
|
220
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
221
|
+
} catch {
|
|
222
|
+
return new TextDecoder("windows-1252").decode(bytes);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function bytesOf(file: Blob): Promise<ArrayBuffer> {
|
|
227
|
+
if (typeof file.arrayBuffer === "function") return file.arrayBuffer();
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const reader = new FileReader();
|
|
230
|
+
reader.onload = () => resolve(reader.result as ArrayBuffer);
|
|
231
|
+
reader.onerror = () => reject(reader.error ?? new Error("read failed"));
|
|
232
|
+
reader.readAsArrayBuffer(file);
|
|
233
|
+
});
|
|
234
|
+
}
|
package/src/lib/strip-fade.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { useLayoutEffect, useState, type RefObject } from "react";
|
|
|
5
5
|
export const TAB_FADE_PX = 24;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Shared by Tabs' strip
|
|
9
|
-
* the package's surface.
|
|
8
|
+
* Shared by Tabs' strip, AppShell's scrolling phone sub-nav and (0.24) ColumnRoleTable's
|
|
9
|
+
* preview; internal, not part of the package's surface.
|
|
10
10
|
*
|
|
11
11
|
* Which ends of a sideways-scrolling strip have tabs cut off behind them, as the
|
|
12
12
|
* `mask-image` that fades those ends out — or `undefined` while everything fits.
|