@fieldia/core 0.23.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 +135 -3
- package/package.json +1 -1
- package/schema/page.schema.json +159 -4
- package/src/index.d.mts +6 -2
- package/src/index.d.ts +6 -2
- package/src/index.js +18 -1
- package/src/index.js.map +1 -1
- package/src/index.mjs +8 -2
- package/src/index.mjs.map +1 -1
- package/src/lib/format/autofill-group.d.mts +12 -0
- package/src/lib/format/autofill-group.d.ts +12 -0
- package/src/lib/format/autofill-group.js +23 -0
- package/src/lib/format/autofill-group.js.map +1 -0
- package/src/lib/format/autofill-group.mjs +19 -0
- package/src/lib/format/autofill-group.mjs.map +1 -0
- package/src/lib/format/autofill.d.mts +14 -0
- package/src/lib/format/autofill.d.ts +14 -0
- package/src/lib/format/autofill.js +54 -0
- package/src/lib/format/autofill.js.map +1 -0
- package/src/lib/format/autofill.mjs +48 -0
- package/src/lib/format/autofill.mjs.map +1 -0
- package/src/lib/format/bulk-edit.d.mts +18 -0
- package/src/lib/format/bulk-edit.d.ts +18 -0
- package/src/lib/format/bulk-edit.js +26 -0
- package/src/lib/format/bulk-edit.js.map +1 -0
- package/src/lib/format/bulk-edit.mjs +21 -0
- package/src/lib/format/bulk-edit.mjs.map +1 -0
- package/src/lib/format/field.d.mts +55 -34
- package/src/lib/format/field.d.ts +55 -34
- package/src/lib/format/field.js +16 -2
- package/src/lib/format/field.js.map +1 -1
- package/src/lib/format/field.mjs +15 -1
- package/src/lib/format/field.mjs.map +1 -1
- package/src/lib/format/layout.d.mts +365 -66
- package/src/lib/format/layout.d.ts +365 -66
- package/src/lib/format/layout.js +22 -4
- package/src/lib/format/layout.js.map +1 -1
- package/src/lib/format/layout.mjs +20 -3
- package/src/lib/format/layout.mjs.map +1 -1
- package/src/lib/format/page.d.mts +169 -29
- package/src/lib/format/page.d.ts +169 -29
- package/src/lib/format/page.js +11 -0
- package/src/lib/format/page.js.map +1 -1
- package/src/lib/format/page.mjs +12 -1
- package/src/lib/format/page.mjs.map +1 -1
- package/src/lib/format/phone.d.mts +27 -0
- package/src/lib/format/phone.d.ts +27 -0
- package/src/lib/format/phone.js +43 -0
- package/src/lib/format/phone.js.map +1 -0
- package/src/lib/format/phone.mjs +39 -0
- package/src/lib/format/phone.mjs.map +1 -0
- package/src/lib/format/references.d.mts +10 -0
- package/src/lib/format/references.d.ts +10 -0
- package/src/lib/format/references.js +96 -1
- package/src/lib/format/references.js.map +1 -1
- package/src/lib/format/references.mjs +96 -1
- package/src/lib/format/references.mjs.map +1 -1
- package/src/lib/format/slots-options.d.mts +26 -0
- package/src/lib/format/slots-options.d.ts +26 -0
- package/src/lib/format/slots-options.js +35 -0
- package/src/lib/format/slots-options.js.map +1 -0
- package/src/lib/format/slots-options.mjs +31 -0
- package/src/lib/format/slots-options.mjs.map +1 -0
- package/src/lib/format/upgrade.d.mts +4 -1
- package/src/lib/format/upgrade.d.ts +4 -1
- package/src/lib/format/upgrade.js +4 -1
- package/src/lib/format/upgrade.js.map +1 -1
- package/src/lib/format/upgrade.mjs +4 -1
- package/src/lib/format/upgrade.mjs.map +1 -1
- package/src/lib/format/version.d.mts +2 -2
- package/src/lib/format/version.d.ts +2 -2
- package/src/lib/format/version.js +2 -2
- package/src/lib/format/version.js.map +1 -1
- package/src/lib/format/version.mjs +2 -2
- package/src/lib/format/version.mjs.map +1 -1
- package/src/lib/format/zoned.d.mts +8 -0
- package/src/lib/format/zoned.d.ts +8 -0
- package/src/lib/format/zoned.js +36 -0
- package/src/lib/format/zoned.js.map +1 -0
- package/src/lib/format/zoned.mjs +33 -0
- package/src/lib/format/zoned.mjs.map +1 -0
- package/src/lib/record/check.d.mts +6 -1
- package/src/lib/record/check.d.ts +6 -1
- package/src/lib/record/check.js +12 -4
- package/src/lib/record/check.js.map +1 -1
- package/src/lib/record/check.mjs +12 -4
- package/src/lib/record/check.mjs.map +1 -1
- package/src/lib/record/data-source.d.mts +188 -3
- package/src/lib/record/data-source.d.ts +188 -3
- package/src/lib/record/data-source.js.map +1 -1
- package/src/lib/record/data-source.mjs.map +1 -1
- package/src/lib/record/form.d.mts +72 -25
- package/src/lib/record/form.d.ts +72 -25
- package/src/lib/record/form.js +404 -78
- package/src/lib/record/form.js.map +1 -1
- package/src/lib/record/form.mjs +404 -78
- package/src/lib/record/form.mjs.map +1 -1
- package/src/lib/record/input-rules.d.mts +51 -9
- package/src/lib/record/input-rules.d.ts +51 -9
- package/src/lib/record/input-rules.js +119 -20
- package/src/lib/record/input-rules.js.map +1 -1
- package/src/lib/record/input-rules.mjs +118 -19
- package/src/lib/record/input-rules.mjs.map +1 -1
- package/src/lib/record/inputs.js +4 -2
- package/src/lib/record/inputs.js.map +1 -1
- package/src/lib/record/inputs.mjs +4 -2
- package/src/lib/record/inputs.mjs.map +1 -1
- package/src/lib/record/limits.d.mts +1 -1
- package/src/lib/record/limits.d.ts +1 -1
- package/src/lib/record/limits.js +4 -2
- package/src/lib/record/limits.js.map +1 -1
- package/src/lib/record/limits.mjs +4 -2
- package/src/lib/record/limits.mjs.map +1 -1
- package/src/lib/record/locales/ar.js +3 -2
- package/src/lib/record/locales/ar.js.map +1 -1
- package/src/lib/record/locales/ar.mjs +3 -2
- package/src/lib/record/locales/ar.mjs.map +1 -1
- package/src/lib/record/locales/de.js +3 -2
- package/src/lib/record/locales/de.js.map +1 -1
- package/src/lib/record/locales/de.mjs +3 -2
- package/src/lib/record/locales/de.mjs.map +1 -1
- package/src/lib/record/locales/fr.js +3 -2
- package/src/lib/record/locales/fr.js.map +1 -1
- package/src/lib/record/locales/fr.mjs +3 -2
- package/src/lib/record/locales/fr.mjs.map +1 -1
- package/src/lib/record/memory-data-source.d.mts +13 -2
- package/src/lib/record/memory-data-source.d.ts +13 -2
- package/src/lib/record/memory-data-source.js +92 -35
- package/src/lib/record/memory-data-source.js.map +1 -1
- package/src/lib/record/memory-data-source.mjs +93 -36
- package/src/lib/record/memory-data-source.mjs.map +1 -1
- package/src/lib/record/memory-schedules.d.mts +45 -0
- package/src/lib/record/memory-schedules.d.ts +45 -0
- package/src/lib/record/memory-schedules.js +81 -0
- package/src/lib/record/memory-schedules.js.map +1 -0
- package/src/lib/record/memory-schedules.mjs +78 -0
- package/src/lib/record/memory-schedules.mjs.map +1 -0
- package/src/lib/record/messages.d.mts +3 -1
- package/src/lib/record/messages.d.ts +3 -1
- package/src/lib/record/messages.js +3 -2
- package/src/lib/record/messages.js.map +1 -1
- package/src/lib/record/messages.mjs +3 -2
- package/src/lib/record/messages.mjs.map +1 -1
- package/src/lib/record/periods.d.mts +47 -0
- package/src/lib/record/periods.d.ts +47 -0
- package/src/lib/record/periods.js +86 -0
- package/src/lib/record/periods.js.map +1 -0
- package/src/lib/record/periods.mjs +79 -0
- package/src/lib/record/periods.mjs.map +1 -0
- package/src/lib/record/rules.js +5 -6
- package/src/lib/record/rules.js.map +1 -1
- package/src/lib/record/rules.mjs +5 -6
- package/src/lib/record/rules.mjs.map +1 -1
- package/src/lib/record/run.d.mts +44 -8
- package/src/lib/record/run.d.ts +44 -8
- package/src/lib/record/run.js +155 -26
- package/src/lib/record/run.js.map +1 -1
- package/src/lib/record/run.mjs +156 -27
- package/src/lib/record/run.mjs.map +1 -1
- package/src/lib/record/search.d.mts +13 -3
- package/src/lib/record/search.d.ts +13 -3
- package/src/lib/record/search.js +11 -4
- package/src/lib/record/search.js.map +1 -1
- package/src/lib/record/search.mjs +11 -4
- package/src/lib/record/search.mjs.map +1 -1
- package/src/lib/record/zones.d.mts +57 -0
- package/src/lib/record/zones.d.ts +57 -0
- package/src/lib/record/zones.js +147 -0
- package/src/lib/record/zones.js.map +1 -0
- package/src/lib/record/zones.mjs +132 -0
- package/src/lib/record/zones.mjs.map +1 -0
- package/src/runtime.d.mts +12 -5
- package/src/runtime.d.ts +12 -5
- package/src/runtime.js +32 -2
- package/src/runtime.js.map +1 -1
- package/src/runtime.mjs +7 -1
- package/src/runtime.mjs.map +1 -1
|
@@ -73,6 +73,11 @@ export interface NumberReadOptions {
|
|
|
73
73
|
*/
|
|
74
74
|
ungrouped?: boolean;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
|
|
78
|
+
* types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
|
|
79
|
+
*/
|
|
80
|
+
export declare const foldTyped: (text: string) => string;
|
|
76
81
|
/**
|
|
77
82
|
* Typed or pasted text as a number, in the page's language (`locale`).
|
|
78
83
|
*
|
|
@@ -121,6 +126,11 @@ export type NumberRefusal = {
|
|
|
121
126
|
} | {
|
|
122
127
|
problem: 'digits';
|
|
123
128
|
n: number;
|
|
129
|
+
}
|
|
130
|
+
/** More digits before the decimal mark than a field of `digits` [precision, scale] keeps there: `n`, precision less scale. */
|
|
131
|
+
| {
|
|
132
|
+
problem: 'wholeDigits';
|
|
133
|
+
n: number;
|
|
124
134
|
};
|
|
125
135
|
/** The most significant digits a number keeps exactly. */
|
|
126
136
|
export declare const MOST_DIGITS = 15;
|
|
@@ -162,15 +172,18 @@ export type PastedNumber = {
|
|
|
162
172
|
/**
|
|
163
173
|
* Pasted (or dropped) text as the plain number a box takes, in the page's
|
|
164
174
|
* way: direction marks, the box's own currency (its symbol, narrow symbol,
|
|
165
|
-
* code or Arabic symbol
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
175
|
+
* code or Arabic symbol, in any case, as Intl writes it or as people write it
|
|
176
|
+
* — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
|
|
177
|
+
* its box says kg), grouping, % and spaces taken out, and an accounting
|
|
178
|
+
* negative read — (1,200.00) and 1,200- are -1200. Another currency is
|
|
179
|
+
* named; a tab or a line break inside is several values; a single line break
|
|
180
|
+
* at the end, as a copied cell brings, is let go.
|
|
169
181
|
*/
|
|
170
182
|
export declare function cleanPastedNumber(text: string, locale: string, options?: {
|
|
171
183
|
currency?: string | null;
|
|
172
184
|
percent?: boolean;
|
|
173
185
|
ungrouped?: boolean;
|
|
186
|
+
units?: readonly string[];
|
|
174
187
|
}): PastedNumber;
|
|
175
188
|
/** Why typed hours do not read. */
|
|
176
189
|
export type DurationProblem = 'unfinished' | 'minutes' | 'duration' | 'numbersOnly' | 'minusFirst' | 'severalValues';
|
|
@@ -198,13 +211,36 @@ export type DurationRead = {
|
|
|
198
211
|
export declare function readDuration(text: string, locale: string): DurationRead;
|
|
199
212
|
/** The order a page types a date's day, month and year in. */
|
|
200
213
|
export type DateOrder = 'dmy' | 'mdy' | 'ymd';
|
|
201
|
-
|
|
214
|
+
/** Why a date does not read; 'order', written in another order than the page's ("15/10/2026" where dates go year first). */
|
|
215
|
+
export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time' | 'order';
|
|
202
216
|
export type DateRead =
|
|
203
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in
|
|
219
|
+
* silence. `at`, in milliseconds, the instant a date and time written with its own zone or its offset from UTC is:
|
|
220
|
+
* its time on the clock of the zone it is read in may not tell it apart (the hour the clocks repeat).
|
|
221
|
+
*/
|
|
204
222
|
{
|
|
205
223
|
ok: true;
|
|
206
224
|
value: string | null;
|
|
207
225
|
timeLeftOut?: string;
|
|
226
|
+
at?: number;
|
|
227
|
+
}
|
|
228
|
+
/** A date and time the clocks skip where it is read (going forward for summer time): they go from `from` to `to`. */
|
|
229
|
+
| {
|
|
230
|
+
ok: false;
|
|
231
|
+
problem: 'skippedTime';
|
|
232
|
+
date: string;
|
|
233
|
+
time: string;
|
|
234
|
+
from: string;
|
|
235
|
+
to: string;
|
|
236
|
+
}
|
|
237
|
+
/** A date and time the clocks pass twice where it is read (going back): the offsets from UTC it could have, earliest first. */
|
|
238
|
+
| {
|
|
239
|
+
ok: false;
|
|
240
|
+
problem: 'repeatedTime';
|
|
241
|
+
date: string;
|
|
242
|
+
time: string;
|
|
243
|
+
offsets: [string, string];
|
|
208
244
|
} | {
|
|
209
245
|
ok: false;
|
|
210
246
|
problem: DateProblem;
|
|
@@ -242,14 +278,20 @@ export declare function couldBeMonth(word: string, locale?: string): boolean;
|
|
|
242
278
|
* numbers beside a month in words are its day and its year of four digits,
|
|
243
279
|
* in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
|
|
244
280
|
* 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
|
|
245
|
-
* its zone (2026-10-15T14:30:00Z)
|
|
246
|
-
*
|
|
247
|
-
*
|
|
281
|
+
* its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
|
|
282
|
+
* ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
|
|
283
|
+
* time zone's name; the computer's own when left out) — with `time`, its
|
|
284
|
+
* seconds :00, never dropped — and gives its moment (`at`). Unfinished or
|
|
285
|
+
* impossible dates say why (31/02 → 'noSuchDay' with the days that month
|
|
286
|
+
* has, as an instant's 2026-02-31 does). A date and time is one moment where
|
|
287
|
+
* it is read: one the clocks skip ('skippedTime') or pass twice
|
|
288
|
+
* ('repeatedTime') as summer time comes or goes is said, never moved.
|
|
248
289
|
*/
|
|
249
290
|
export declare function readDate(text: string, options?: {
|
|
250
291
|
order?: DateOrder;
|
|
251
292
|
time?: boolean;
|
|
252
293
|
locale?: string;
|
|
294
|
+
zone?: string;
|
|
253
295
|
}): DateRead;
|
|
254
296
|
export type TimeRead = {
|
|
255
297
|
ok: true;
|
|
@@ -73,6 +73,11 @@ export interface NumberReadOptions {
|
|
|
73
73
|
*/
|
|
74
74
|
ungrouped?: boolean;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
|
|
78
|
+
* types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
|
|
79
|
+
*/
|
|
80
|
+
export declare const foldTyped: (text: string) => string;
|
|
76
81
|
/**
|
|
77
82
|
* Typed or pasted text as a number, in the page's language (`locale`).
|
|
78
83
|
*
|
|
@@ -121,6 +126,11 @@ export type NumberRefusal = {
|
|
|
121
126
|
} | {
|
|
122
127
|
problem: 'digits';
|
|
123
128
|
n: number;
|
|
129
|
+
}
|
|
130
|
+
/** More digits before the decimal mark than a field of `digits` [precision, scale] keeps there: `n`, precision less scale. */
|
|
131
|
+
| {
|
|
132
|
+
problem: 'wholeDigits';
|
|
133
|
+
n: number;
|
|
124
134
|
};
|
|
125
135
|
/** The most significant digits a number keeps exactly. */
|
|
126
136
|
export declare const MOST_DIGITS = 15;
|
|
@@ -162,15 +172,18 @@ export type PastedNumber = {
|
|
|
162
172
|
/**
|
|
163
173
|
* Pasted (or dropped) text as the plain number a box takes, in the page's
|
|
164
174
|
* way: direction marks, the box's own currency (its symbol, narrow symbol,
|
|
165
|
-
* code or Arabic symbol
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
175
|
+
* code or Arabic symbol, in any case, as Intl writes it or as people write it
|
|
176
|
+
* — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
|
|
177
|
+
* its box says kg), grouping, % and spaces taken out, and an accounting
|
|
178
|
+
* negative read — (1,200.00) and 1,200- are -1200. Another currency is
|
|
179
|
+
* named; a tab or a line break inside is several values; a single line break
|
|
180
|
+
* at the end, as a copied cell brings, is let go.
|
|
169
181
|
*/
|
|
170
182
|
export declare function cleanPastedNumber(text: string, locale: string, options?: {
|
|
171
183
|
currency?: string | null;
|
|
172
184
|
percent?: boolean;
|
|
173
185
|
ungrouped?: boolean;
|
|
186
|
+
units?: readonly string[];
|
|
174
187
|
}): PastedNumber;
|
|
175
188
|
/** Why typed hours do not read. */
|
|
176
189
|
export type DurationProblem = 'unfinished' | 'minutes' | 'duration' | 'numbersOnly' | 'minusFirst' | 'severalValues';
|
|
@@ -198,13 +211,36 @@ export type DurationRead = {
|
|
|
198
211
|
export declare function readDuration(text: string, locale: string): DurationRead;
|
|
199
212
|
/** The order a page types a date's day, month and year in. */
|
|
200
213
|
export type DateOrder = 'dmy' | 'mdy' | 'ymd';
|
|
201
|
-
|
|
214
|
+
/** Why a date does not read; 'order', written in another order than the page's ("15/10/2026" where dates go year first). */
|
|
215
|
+
export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time' | 'order';
|
|
202
216
|
export type DateRead =
|
|
203
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in
|
|
219
|
+
* silence. `at`, in milliseconds, the instant a date and time written with its own zone or its offset from UTC is:
|
|
220
|
+
* its time on the clock of the zone it is read in may not tell it apart (the hour the clocks repeat).
|
|
221
|
+
*/
|
|
204
222
|
{
|
|
205
223
|
ok: true;
|
|
206
224
|
value: string | null;
|
|
207
225
|
timeLeftOut?: string;
|
|
226
|
+
at?: number;
|
|
227
|
+
}
|
|
228
|
+
/** A date and time the clocks skip where it is read (going forward for summer time): they go from `from` to `to`. */
|
|
229
|
+
| {
|
|
230
|
+
ok: false;
|
|
231
|
+
problem: 'skippedTime';
|
|
232
|
+
date: string;
|
|
233
|
+
time: string;
|
|
234
|
+
from: string;
|
|
235
|
+
to: string;
|
|
236
|
+
}
|
|
237
|
+
/** A date and time the clocks pass twice where it is read (going back): the offsets from UTC it could have, earliest first. */
|
|
238
|
+
| {
|
|
239
|
+
ok: false;
|
|
240
|
+
problem: 'repeatedTime';
|
|
241
|
+
date: string;
|
|
242
|
+
time: string;
|
|
243
|
+
offsets: [string, string];
|
|
208
244
|
} | {
|
|
209
245
|
ok: false;
|
|
210
246
|
problem: DateProblem;
|
|
@@ -242,14 +278,20 @@ export declare function couldBeMonth(word: string, locale?: string): boolean;
|
|
|
242
278
|
* numbers beside a month in words are its day and its year of four digits,
|
|
243
279
|
* in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
|
|
244
280
|
* 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
|
|
245
|
-
* its zone (2026-10-15T14:30:00Z)
|
|
246
|
-
*
|
|
247
|
-
*
|
|
281
|
+
* its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
|
|
282
|
+
* ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
|
|
283
|
+
* time zone's name; the computer's own when left out) — with `time`, its
|
|
284
|
+
* seconds :00, never dropped — and gives its moment (`at`). Unfinished or
|
|
285
|
+
* impossible dates say why (31/02 → 'noSuchDay' with the days that month
|
|
286
|
+
* has, as an instant's 2026-02-31 does). A date and time is one moment where
|
|
287
|
+
* it is read: one the clocks skip ('skippedTime') or pass twice
|
|
288
|
+
* ('repeatedTime') as summer time comes or goes is said, never moved.
|
|
248
289
|
*/
|
|
249
290
|
export declare function readDate(text: string, options?: {
|
|
250
291
|
order?: DateOrder;
|
|
251
292
|
time?: boolean;
|
|
252
293
|
locale?: string;
|
|
294
|
+
zone?: string;
|
|
253
295
|
}): DateRead;
|
|
254
296
|
export type TimeRead = {
|
|
255
297
|
ok: true;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* copying brings along, are never part of what is read.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.MOST_DIGITS = void 0;
|
|
15
|
+
exports.MOST_DIGITS = exports.foldTyped = void 0;
|
|
16
16
|
exports.stripMarks = stripMarks;
|
|
17
17
|
exports.foldDigits = foldDigits;
|
|
18
18
|
exports.oneLine = oneLine;
|
|
@@ -29,6 +29,7 @@ exports.readDate = readDate;
|
|
|
29
29
|
exports.readTime = readTime;
|
|
30
30
|
exports.placesOfCurrency = placesOfCurrency;
|
|
31
31
|
exports.placesOf = placesOf;
|
|
32
|
+
const zones_1 = require("./zones");
|
|
32
33
|
/** Invisible direction marks: LRM, RLM, ALM, the embeddings and overrides, the isolates, and a byte-order mark. */
|
|
33
34
|
const MARKS = /[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]/g;
|
|
34
35
|
const MARKS_AT_ENDS = /^[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]+|[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]+$/g;
|
|
@@ -114,10 +115,16 @@ const MINUS = '-\u2212\ufe63\uff0d';
|
|
|
114
115
|
const IS_MINUS = new RegExp(`[${MINUS}]`);
|
|
115
116
|
const LINE_BREAKS = /[\t\n\r\v\f\u2028\u2029]/;
|
|
116
117
|
const GROUP_SPACE = /[ \u00a0\u2007\u2009\u202f]/;
|
|
117
|
-
/** Fullwidth marks a Japanese or Chinese keyboard types, as their plain kind. */
|
|
118
|
-
const
|
|
118
|
+
/** Fullwidth marks a Japanese or Chinese keyboard types, as their plain kind: a number's + , . %, a date's /, a time's :, its space. */
|
|
119
|
+
const FULLWIDTH = { '\uff0b': '+', '\uff0c': ',', '\uff0e': '.', '\uff05': '%', '\uff0f': '/', '\uff1a': ':', '\u3000': ' ' };
|
|
120
|
+
const fullwidth = (text) => text.replace(/[\uff0b\uff0c\uff0e\uff05\uff0f\uff1a\u3000]/g, (c) => FULLWIDTH[c]);
|
|
119
121
|
/** Text as the readers see it: no direction marks, digits and fullwidth marks folded. */
|
|
120
122
|
const folded = (text) => fullwidth(foldDigits(stripMarks(text)));
|
|
123
|
+
/**
|
|
124
|
+
* Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
|
|
125
|
+
* types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
|
|
126
|
+
*/
|
|
127
|
+
exports.foldTyped = folded;
|
|
121
128
|
function kindOf(char) {
|
|
122
129
|
if (char === '.')
|
|
123
130
|
return 'dot';
|
|
@@ -281,6 +288,8 @@ function readNumber(text, locale, options = {}) {
|
|
|
281
288
|
}
|
|
282
289
|
/** The most significant digits a number keeps exactly. */
|
|
283
290
|
exports.MOST_DIGITS = 15;
|
|
291
|
+
/** The most digits a field keeps before its decimal mark, where its limits say its places and its digits (precision less scale). */
|
|
292
|
+
const wholeDigits = (limits) => (limits.digits !== undefined && limits.places !== undefined && !limits.percent ? Math.max(1, limits.digits - limits.places) : undefined);
|
|
284
293
|
/**
|
|
285
294
|
* Whether the decimal mark a text reads with may still turn out to group as
|
|
286
295
|
* more is typed: a lone . or , (or ،), last, with one to three digits before
|
|
@@ -349,6 +358,11 @@ function couldBeNumber(text, locale, limits = {}) {
|
|
|
349
358
|
return { problem: 'decimals', places: limits.places };
|
|
350
359
|
const all = [...sign.body].filter((c) => c >= '0' && c <= '9').join('');
|
|
351
360
|
const whole = body.decimal === -1 ? all : all.slice(0, all.length - fraction.length);
|
|
361
|
+
// A field's precision keeps its scale's places after the mark: before it, the rest (Floor area [10, 1]: 9), as its
|
|
362
|
+
// check counts them — never a digit taken here that the check then refuses.
|
|
363
|
+
const wholeMost = wholeDigits(limits);
|
|
364
|
+
if (wholeMost !== undefined && whole.replace(/^0+/, '').length > wholeMost)
|
|
365
|
+
return { problem: 'wholeDigits', n: wholeMost };
|
|
352
366
|
const significant = (whole + places).replace(/^0+/, '').length;
|
|
353
367
|
const digits = whole.replace(/^0+/, '').length + places.length;
|
|
354
368
|
const most = Math.min(exports.MOST_DIGITS, limits.digits ?? exports.MOST_DIGITS);
|
|
@@ -373,6 +387,9 @@ function numberMisfit(value, limits) {
|
|
|
373
387
|
return { problem: 'decimals', places: limits.places };
|
|
374
388
|
const integerPart = Math.trunc(Math.abs(value));
|
|
375
389
|
const whole = integerPart === 0 ? 0 : integerPart < 1e21 ? String(integerPart).length : 22;
|
|
390
|
+
const wholeMost = wholeDigits(limits);
|
|
391
|
+
if (wholeMost !== undefined && whole <= exports.MOST_DIGITS && whole > wholeMost)
|
|
392
|
+
return { problem: 'wholeDigits', n: wholeMost };
|
|
376
393
|
const most = Math.min(exports.MOST_DIGITS, limits.digits ?? exports.MOST_DIGITS);
|
|
377
394
|
if (whole > exports.MOST_DIGITS || whole + places > most)
|
|
378
395
|
return { problem: 'digits', n: most };
|
|
@@ -410,19 +427,42 @@ function formsOf(currency, locale) {
|
|
|
410
427
|
}
|
|
411
428
|
return found;
|
|
412
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* How people in the region write their own currency beside an amount, where Intl's symbols are not how they write it:
|
|
432
|
+
* Egypt's LE, L.E. and ج.م, Jordan's JD, the Gulf's KD, BD, RO, QR, SR and Dhs. (Arabic letters made from their code
|
|
433
|
+
* points: the one-tag script carries no Arabic words.)
|
|
434
|
+
*/
|
|
435
|
+
const WRITTEN = {
|
|
436
|
+
EGP: ['LE', `${letter(0x62c)}.${letter(0x645)}`],
|
|
437
|
+
JOD: ['JD'],
|
|
438
|
+
KWD: ['KD'],
|
|
439
|
+
BHD: ['BD'],
|
|
440
|
+
OMR: ['RO'],
|
|
441
|
+
QAR: ['QR'],
|
|
442
|
+
SAR: ['SR'],
|
|
443
|
+
AED: ['Dhs', 'Dh'],
|
|
444
|
+
};
|
|
445
|
+
/** A currency's or a unit's form as it is matched: its case, its Arabic letters' forms, its dots and spaces let go. */
|
|
446
|
+
const formKey = (form) => foldForSearch(form).replace(/[.\s]/g, '');
|
|
413
447
|
/**
|
|
414
448
|
* Pasted (or dropped) text as the plain number a box takes, in the page's
|
|
415
449
|
* way: direction marks, the box's own currency (its symbol, narrow symbol,
|
|
416
|
-
* code or Arabic symbol
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
450
|
+
* code or Arabic symbol, in any case, as Intl writes it or as people write it
|
|
451
|
+
* — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
|
|
452
|
+
* its box says kg), grouping, % and spaces taken out, and an accounting
|
|
453
|
+
* negative read — (1,200.00) and 1,200- are -1200. Another currency is
|
|
454
|
+
* named; a tab or a line break inside is several values; a single line break
|
|
455
|
+
* at the end, as a copied cell brings, is let go.
|
|
420
456
|
*/
|
|
421
457
|
function cleanPastedNumber(text, locale, options = {}) {
|
|
422
458
|
let t = folded(text).replace(/(\r\n|[\r\n])$/, '');
|
|
423
459
|
if (LINE_BREAKS.test(t.trim()))
|
|
424
460
|
return { ok: false, problem: 'severalValues' };
|
|
425
461
|
t = t.trim();
|
|
462
|
+
// The box's own currency and unit, however they are written: a word or a sign taken out whole where it is one of them.
|
|
463
|
+
const own = new Set([...(options.currency ? [...formsOf(options.currency, locale), ...(WRITTEN[options.currency] ?? [])] : []), ...(options.units ?? [])].map(formKey).filter(Boolean));
|
|
464
|
+
if (own.size)
|
|
465
|
+
t = t.replace(/[\p{L}\p{Sc}][\p{L}\p{Sc}\p{M}.]*/gu, (token) => (own.has(formKey(token)) ? ' ' : token));
|
|
426
466
|
if (options.currency)
|
|
427
467
|
for (const form of formsOf(options.currency, locale))
|
|
428
468
|
t = t.split(form).join(' ');
|
|
@@ -498,10 +538,12 @@ function readDuration(text, locale) {
|
|
|
498
538
|
return { ok: false, problem: 'duration' };
|
|
499
539
|
return { ok: true, value: signed(Number(h) + Number(m) / 60) };
|
|
500
540
|
}
|
|
501
|
-
|
|
541
|
+
// Hours with a decimal and their h after them, as "6h" may have one: "6.5h" is six and a half.
|
|
542
|
+
const decimal = /^(\d+[.,\u060c\u066b]\d+)\s*[hH]$/.exec(body);
|
|
543
|
+
if (/[:hH]/.test(body) && !decimal)
|
|
502
544
|
return { ok: false, problem: 'duration' };
|
|
503
545
|
// Hours as a number, in the page's way: "6.5", "6,5".
|
|
504
|
-
const read = readNumber(body, locale);
|
|
546
|
+
const read = readNumber(decimal ? decimal[1] : body, locale);
|
|
505
547
|
if (read.ok)
|
|
506
548
|
return { ok: true, value: read.value === null ? null : signed(read.value) };
|
|
507
549
|
if (read.problem === 'unfinished')
|
|
@@ -510,8 +552,25 @@ function readDuration(text, locale) {
|
|
|
510
552
|
}
|
|
511
553
|
const daysIn = (year, month) => [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
512
554
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
513
|
-
|
|
514
|
-
|
|
555
|
+
// ---------------------------------------------------------------------------
|
|
556
|
+
// Wall clocks and their zones: zones.ts's, the one set every reader and writer counts by.
|
|
557
|
+
/** A moment as the wall clock reads it in `zone` (a time zone's name; where the computer is when left out): yyyy-mm-ddTHH:MM. */
|
|
558
|
+
const wallAt = (at, zone) => (0, zones_1.wallText)((0, zones_1.wallTime)(at, zone));
|
|
559
|
+
/** An offset from UTC as people write it after a time: +02:00, -05:00. */
|
|
560
|
+
const offsetText = (minutes) => `${minutes < 0 ? '-' : '+'}${pad2(Math.floor(Math.abs(minutes) / 60))}:${pad2(Math.abs(minutes) % 60)}`;
|
|
561
|
+
/** Why a date and time typed (yyyy-mm-ddTHH:MM) is not one moment in `zone`, or null when it is: skipped or passed twice. */
|
|
562
|
+
function clocksMove(local, zone) {
|
|
563
|
+
const wall = (0, zones_1.wallOf)(local);
|
|
564
|
+
const moments = (0, zones_1.instantsAt)(wall, zone);
|
|
565
|
+
const [date, time] = [local.slice(0, 10), local.slice(11)];
|
|
566
|
+
if (moments.length === 2)
|
|
567
|
+
return { ok: false, problem: 'repeatedTime', date, time, offsets: [offsetText((0, zones_1.zoneOffset)(moments[0], zone)), offsetText((0, zones_1.zoneOffset)(moments[1], zone))] };
|
|
568
|
+
if (moments.length)
|
|
569
|
+
return null;
|
|
570
|
+
// Skipped: the clocks moved forward across it, reading `from` just before they moved and `to` from then on.
|
|
571
|
+
const skip = (0, zones_1.clocksSkip)(wall, zone);
|
|
572
|
+
return { ok: false, problem: 'skippedTime', date, time, from: skip ? (0, zones_1.wallClock)(skip.from) : time, to: skip ? (0, zones_1.wallClock)(skip.to) : time };
|
|
573
|
+
}
|
|
515
574
|
/** An instant as a server or a spreadsheet writes one, with its zone: 2026-10-15T14:30:00Z, 2026-10-15T16:30:00.000+02:00. */
|
|
516
575
|
const INSTANT = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?\s*(Z|[+-]\d{2}(?::?\d{2})?)$/i;
|
|
517
576
|
/** The languages whose month names a date may be written with, whatever the page's: the four Fieldia speaks. */
|
|
@@ -575,29 +634,66 @@ function couldBeMonth(word, locale) {
|
|
|
575
634
|
* numbers beside a month in words are its day and its year of four digits,
|
|
576
635
|
* in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
|
|
577
636
|
* 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
|
|
578
|
-
* its zone (2026-10-15T14:30:00Z)
|
|
579
|
-
*
|
|
580
|
-
*
|
|
637
|
+
* its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
|
|
638
|
+
* ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
|
|
639
|
+
* time zone's name; the computer's own when left out) — with `time`, its
|
|
640
|
+
* seconds :00, never dropped — and gives its moment (`at`). Unfinished or
|
|
641
|
+
* impossible dates say why (31/02 → 'noSuchDay' with the days that month
|
|
642
|
+
* has, as an instant's 2026-02-31 does). A date and time is one moment where
|
|
643
|
+
* it is read: one the clocks skip ('skippedTime') or pass twice
|
|
644
|
+
* ('repeatedTime') as summer time comes or goes is said, never moved.
|
|
581
645
|
*/
|
|
582
646
|
function readDate(text, options = {}) {
|
|
583
|
-
|
|
647
|
+
const t = folded(text).trim();
|
|
584
648
|
if (!t)
|
|
585
649
|
return { ok: true, value: null };
|
|
586
650
|
const instant = INSTANT.exec(t);
|
|
587
651
|
if (instant) {
|
|
588
652
|
const [, y, mo, d, h, mi, seconds = '0', fraction = '0', zone] = instant;
|
|
653
|
+
// A day its month does not have is refused as typed, never rolled into the next month (31 February, 3 March).
|
|
654
|
+
if (Number(mo) < 1 || Number(mo) > 12)
|
|
655
|
+
return { ok: false, problem: 'noSuchMonth' };
|
|
656
|
+
const days = daysIn(Number(y), Number(mo));
|
|
657
|
+
if (Number(d) < 1 || Number(d) > days)
|
|
658
|
+
return { ok: false, problem: 'noSuchDay', year: Number(y), month: Number(mo), days };
|
|
589
659
|
// A time is kept to the minute: seconds are never dropped in silence. A date's box takes the instant's day alone.
|
|
590
660
|
if (options.time && (Number(seconds) !== 0 || Number(fraction) !== 0))
|
|
591
661
|
return { ok: false, problem: 'time' };
|
|
592
662
|
const offset = /^z$/i.test(zone) ? 'Z' : zone.length === 3 ? `${zone}:00` : `${zone.slice(0, 3)}:${zone.slice(-2)}`;
|
|
593
|
-
const at = new Date(`${y}-${mo}-${d}T${h}:${mi}:00${offset}`);
|
|
594
|
-
if (Number.isNaN(at
|
|
663
|
+
const at = new Date(`${y}-${mo}-${d}T${h}:${mi}:00${offset}`).getTime();
|
|
664
|
+
if (Number.isNaN(at))
|
|
595
665
|
return { ok: false, problem: 'unfinished' };
|
|
596
|
-
|
|
597
|
-
|
|
666
|
+
// On the clock of the zone it is read in (the computer's own without one): the instant itself said too.
|
|
667
|
+
const local = wallAt(at, options.zone);
|
|
668
|
+
return options.time ? { ok: true, value: local, at } : { ok: true, value: local.slice(0, 10), timeLeftOut: local.slice(11) };
|
|
669
|
+
}
|
|
670
|
+
if (options.time) {
|
|
671
|
+
// A date and time typed with its offset from UTC ("25/10/2026 02:30 +01:00"): the moment it names, the wall clock it
|
|
672
|
+
// is where it is read — the way to write a time the clocks pass twice.
|
|
673
|
+
const zoned = OFFSET_AFTER_TIME.exec(t);
|
|
674
|
+
if (zoned) {
|
|
675
|
+
const [, written, sign, hours, minutes = '00'] = zoned;
|
|
676
|
+
const read = readWritten(written, options);
|
|
677
|
+
if (!read.ok || read.value === null)
|
|
678
|
+
return read;
|
|
679
|
+
const offset = (Number(hours) * 60 + Number(minutes)) * (sign === '+' ? 1 : -1);
|
|
680
|
+
if (Number(hours) > 14 || Number(minutes) > 59)
|
|
681
|
+
return { ok: false, problem: 'time' };
|
|
682
|
+
const wall = (0, zones_1.wallOf)(read.value);
|
|
683
|
+
const at = Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute) - offset * 60000;
|
|
684
|
+
return { ok: true, value: wallAt(at, options.zone), at };
|
|
685
|
+
}
|
|
598
686
|
}
|
|
687
|
+
const read = readWritten(t, options);
|
|
688
|
+
// A wall clock's time is one moment where it is read: one the clocks skip or pass twice is said, never moved.
|
|
689
|
+
return read.ok && read.value !== null && options.time ? (clocksMove(read.value, options.zone) ?? read) : read;
|
|
690
|
+
}
|
|
691
|
+
/** A time after a date, and an offset from UTC after it: "… 02:30 +01:00", "… 2:30 pm UTC-5". */
|
|
692
|
+
const OFFSET_AFTER_TIME = new RegExp(`^(.*\\d{1,2}\\s*[:hH.]\\s*\\d{2}(?:\\s*(?:${HALF}))?)\\s*(?:UTC|GMT)?\\s*([+\\-\\u2212])(\\d{1,2})(?::?(\\d{2}))?$`, 'i');
|
|
693
|
+
/** A date, and with `time` its time, as written — in words or numbers, in the page's order — as a wall clock reads it. */
|
|
694
|
+
function readWritten(text, options) {
|
|
599
695
|
// A comma before a space sets a date's parts apart ("Oct 15, 2026") or its time ("15 Oct 2026, 14:30"): no part of either.
|
|
600
|
-
t =
|
|
696
|
+
const t = text.replace(/\s*[,\u060c](?=\s)/g, '');
|
|
601
697
|
let datePart = t;
|
|
602
698
|
let timePart = '';
|
|
603
699
|
if (options.time) {
|
|
@@ -629,6 +725,9 @@ function readDate(text, options = {}) {
|
|
|
629
725
|
else {
|
|
630
726
|
// ISO, whatever the page's order: a four-digit year first.
|
|
631
727
|
const order = pieces[0].length === 4 ? 'ymd' : (options.order ?? 'dmy');
|
|
728
|
+
// Its year in full last where the page writes it first: written another way than the page's, never read as year 15.
|
|
729
|
+
if (order === 'ymd' && pieces[2].length === 4 && pieces[0].length <= 2)
|
|
730
|
+
return { ok: false, problem: 'order' };
|
|
632
731
|
[yearText, monthText, dayText] = [pieces[order.indexOf('y')], pieces[order.indexOf('m')], pieces[order.indexOf('d')]];
|
|
633
732
|
month = Number(monthText);
|
|
634
733
|
}
|