@fusion.dev/helix-core 0.164.0 → 0.165.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/custom-elements.json +83 -2
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +1 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +86 -21
- package/dist/utils/number-format/number-format.d.ts +6 -0
- package/dist/utils/number-format/number-format.js +9 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
package/custom-elements.json
CHANGED
|
@@ -21908,6 +21908,61 @@
|
|
|
21908
21908
|
}
|
|
21909
21909
|
]
|
|
21910
21910
|
},
|
|
21911
|
+
{
|
|
21912
|
+
"kind": "method",
|
|
21913
|
+
"name": "edit",
|
|
21914
|
+
"privacy": "private",
|
|
21915
|
+
"return": {
|
|
21916
|
+
"type": {
|
|
21917
|
+
"text": "boolean"
|
|
21918
|
+
}
|
|
21919
|
+
},
|
|
21920
|
+
"parameters": [
|
|
21921
|
+
{
|
|
21922
|
+
"name": "element",
|
|
21923
|
+
"type": {
|
|
21924
|
+
"text": "HTMLElement"
|
|
21925
|
+
}
|
|
21926
|
+
},
|
|
21927
|
+
{
|
|
21928
|
+
"name": "key",
|
|
21929
|
+
"type": {
|
|
21930
|
+
"text": "string"
|
|
21931
|
+
}
|
|
21932
|
+
}
|
|
21933
|
+
],
|
|
21934
|
+
"description": "Applies an editing key to the segment, the arrows, Backspace, a digit or\na letter, and says whether it edited anything."
|
|
21935
|
+
},
|
|
21936
|
+
{
|
|
21937
|
+
"kind": "field",
|
|
21938
|
+
"name": "composing",
|
|
21939
|
+
"type": {
|
|
21940
|
+
"text": "[Node, string | null][]"
|
|
21941
|
+
},
|
|
21942
|
+
"privacy": "private",
|
|
21943
|
+
"default": "[]",
|
|
21944
|
+
"description": "What the segment's text was when a composition started to write into it."
|
|
21945
|
+
},
|
|
21946
|
+
{
|
|
21947
|
+
"kind": "field",
|
|
21948
|
+
"name": "handleSegmentBeforeInput",
|
|
21949
|
+
"privacy": "private",
|
|
21950
|
+
"readonly": true,
|
|
21951
|
+
"description": "Text a touch keyboard writes into a segment, which arrives as input and\nnot as a key: an on-screen keyboard sends `Unidentified` for its keys,\nso a digit tapped on a phone is read here, as React Aria's segments read\nit. Every input is canceled, so the segment holds only what the field\ndraws in it."
|
|
21952
|
+
},
|
|
21953
|
+
{
|
|
21954
|
+
"kind": "field",
|
|
21955
|
+
"name": "handleSegmentInput",
|
|
21956
|
+
"privacy": "private",
|
|
21957
|
+
"readonly": true
|
|
21958
|
+
},
|
|
21959
|
+
{
|
|
21960
|
+
"kind": "field",
|
|
21961
|
+
"name": "collapseSelection",
|
|
21962
|
+
"privacy": "private",
|
|
21963
|
+
"readonly": true,
|
|
21964
|
+
"description": "Keeps the caret at the start of the segment with focus, where Chromium\nfires input for a typed key, and where Android's keyboard does not\ncompose over the segment's text. A selection already there, or outside\nthe segment, is left alone."
|
|
21965
|
+
},
|
|
21911
21966
|
{
|
|
21912
21967
|
"kind": "field",
|
|
21913
21968
|
"name": "handleSegmentKeydown",
|
|
@@ -28842,6 +28897,24 @@
|
|
|
28842
28897
|
],
|
|
28843
28898
|
"description": "A reading in words: `value` written with `options` where they say another\nstyle, and otherwise `share`, from 0 to 1, as a percentage, whole unless\n`options` say more.\n\nMeter and Progress each wrote this, with the value each holds to its range\nand the share of it that is filled."
|
|
28844
28899
|
},
|
|
28900
|
+
{
|
|
28901
|
+
"kind": "function",
|
|
28902
|
+
"name": "latinDigits",
|
|
28903
|
+
"return": {
|
|
28904
|
+
"type": {
|
|
28905
|
+
"text": "string"
|
|
28906
|
+
}
|
|
28907
|
+
},
|
|
28908
|
+
"parameters": [
|
|
28909
|
+
{
|
|
28910
|
+
"name": "text",
|
|
28911
|
+
"type": {
|
|
28912
|
+
"text": "string"
|
|
28913
|
+
}
|
|
28914
|
+
}
|
|
28915
|
+
],
|
|
28916
|
+
"description": "Text with every decimal digit written in ASCII: Arabic-Indic `١٢٣`, Persian\n`۱۲۳` and Devanagari `१२३` read as `123`. A keyboard in one of those\nlocales types its own digits, and `Number` reads only ASCII."
|
|
28917
|
+
},
|
|
28845
28918
|
{
|
|
28846
28919
|
"kind": "function",
|
|
28847
28920
|
"name": "parseNumber",
|
|
@@ -28897,6 +28970,14 @@
|
|
|
28897
28970
|
"module": "src/utils/number-format/number-format.ts"
|
|
28898
28971
|
}
|
|
28899
28972
|
},
|
|
28973
|
+
{
|
|
28974
|
+
"kind": "js",
|
|
28975
|
+
"name": "latinDigits",
|
|
28976
|
+
"declaration": {
|
|
28977
|
+
"name": "latinDigits",
|
|
28978
|
+
"module": "src/utils/number-format/number-format.ts"
|
|
28979
|
+
}
|
|
28980
|
+
},
|
|
28900
28981
|
{
|
|
28901
28982
|
"kind": "js",
|
|
28902
28983
|
"name": "parseNumber",
|
|
@@ -33183,7 +33264,7 @@
|
|
|
33183
33264
|
"type": {
|
|
33184
33265
|
"text": "string"
|
|
33185
33266
|
},
|
|
33186
|
-
"default": "'0.
|
|
33267
|
+
"default": "'0.165.0'",
|
|
33187
33268
|
"description": "The version of the package this file ships in.\n\n**Written by `fusion/scripts/version.mjs`** wherever it writes the package's\n`package.json`, and stamped back into the repository beside it after a\nrelease, so the two never disagree; a spec beside this holds them to it.\nNothing else in the running code knows which release it is."
|
|
33188
33269
|
}
|
|
33189
33270
|
],
|
|
@@ -33743,6 +33824,6 @@
|
|
|
33743
33824
|
],
|
|
33744
33825
|
"helix": {
|
|
33745
33826
|
"package": "@fusion.dev/helix-core",
|
|
33746
|
-
"version": "0.
|
|
33827
|
+
"version": "0.165.0"
|
|
33747
33828
|
}
|
|
33748
33829
|
}
|
|
@@ -8,6 +8,7 @@ import { html, nothing } from 'lit';
|
|
|
8
8
|
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { isPartial, isTwelveHourLayout, segmentKey, segmentLayout, segmentName, segmentRange, segmentsOf, segmentsValue, segmentText, segmentValueText, shownValue, } from '../../utils/date-segments/date-segments.js';
|
|
10
10
|
import { inlineStep } from '../../utils/inline-step/inline-step.js';
|
|
11
|
+
import { latinDigits } from '../../utils/number-format/number-format.js';
|
|
11
12
|
import { appleTouch } from '../../utils/platform/platform.js';
|
|
12
13
|
import { verdictFlags } from '../../utils/verdict-flags/verdict-flags.js';
|
|
13
14
|
import { formatWords, words } from '../../words/words.js';
|
|
@@ -27,11 +28,59 @@ export const MixinSegmentedField = (Base) => {
|
|
|
27
28
|
this.touchApple = appleTouch(typeof navigator === 'undefined' ? undefined : navigator);
|
|
28
29
|
this.entered = undefined;
|
|
29
30
|
this.opener = undefined;
|
|
31
|
+
this.composing = [];
|
|
32
|
+
this.handleSegmentBeforeInput = (event) => {
|
|
33
|
+
const element = event.currentTarget;
|
|
34
|
+
if (event.inputType === 'insertCompositionText') {
|
|
35
|
+
this.composing = [...element.childNodes].map((node) => [node, node.nodeValue]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
if (event.inputType === 'deleteContentBackward' ||
|
|
40
|
+
event.inputType === 'deleteContentForward') {
|
|
41
|
+
this.typed = '';
|
|
42
|
+
this.edit(element, 'Backspace');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const character of latinDigits(event.data ?? '')) {
|
|
46
|
+
this.edit(element, character);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this.handleSegmentInput = (event) => {
|
|
50
|
+
const element = event.currentTarget;
|
|
51
|
+
const { data, inputType } = event;
|
|
52
|
+
if (inputType !== 'insertCompositionText') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const kept = new Set(this.composing.map(([node]) => node));
|
|
56
|
+
for (const node of [...element.childNodes]) {
|
|
57
|
+
if (!kept.has(node)) {
|
|
58
|
+
node.remove();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const [node, text] of this.composing) {
|
|
62
|
+
node.nodeValue = text;
|
|
63
|
+
}
|
|
64
|
+
this.composing = [];
|
|
65
|
+
if (element.dataset.segment === 'dayPeriod' && data) {
|
|
66
|
+
this.edit(element, data.slice(-1));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this.collapseSelection = () => {
|
|
70
|
+
const root = this.renderRoot;
|
|
71
|
+
const active = this.segmentElements.find((one) => one === root?.activeElement);
|
|
72
|
+
const selection = document.getSelection();
|
|
73
|
+
const at = selection?.anchorNode;
|
|
74
|
+
if (active &&
|
|
75
|
+
selection &&
|
|
76
|
+
at &&
|
|
77
|
+
active.contains(at) &&
|
|
78
|
+
!(at === active && selection.isCollapsed && selection.anchorOffset === 0)) {
|
|
79
|
+
selection.collapse(active, 0);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
30
82
|
this.handleSegmentKeydown = (event) => {
|
|
31
83
|
const element = event.currentTarget;
|
|
32
|
-
const kind = element.dataset.segment;
|
|
33
|
-
const row = Number(element.dataset.row ?? 0);
|
|
34
|
-
const twelve = isTwelveHourLayout(this.layout);
|
|
35
84
|
if (event.key === 'ArrowDown' && event.altKey) {
|
|
36
85
|
event.preventDefault();
|
|
37
86
|
this.opener = element;
|
|
@@ -56,36 +105,23 @@ export const MixinSegmentedField = (Base) => {
|
|
|
56
105
|
this.typed = '';
|
|
57
106
|
return;
|
|
58
107
|
}
|
|
59
|
-
if (this.
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const edit = segmentKey(event.key, {
|
|
63
|
-
kind,
|
|
64
|
-
locale: this.segmentLocale,
|
|
65
|
-
segments: this.parts[row] ?? {},
|
|
66
|
-
system: this.segmentSystem,
|
|
67
|
-
twelve,
|
|
68
|
-
typed: this.typed,
|
|
69
|
-
});
|
|
70
|
-
if (edit) {
|
|
108
|
+
if (this.edit(element, latinDigits(event.key))) {
|
|
71
109
|
event.preventDefault();
|
|
72
|
-
this.typed = edit.typed;
|
|
73
|
-
this.take(row, edit.segments);
|
|
74
|
-
if (edit.advance) {
|
|
75
|
-
this.moveFocus(element, 1);
|
|
76
|
-
}
|
|
77
110
|
}
|
|
78
111
|
};
|
|
79
112
|
this.handleSegmentsFocusIn = (event) => {
|
|
80
113
|
this.typed = '';
|
|
114
|
+
document.getSelection()?.collapse(event.target, 0);
|
|
81
115
|
if (!this.segmentElements.includes(event.relatedTarget)) {
|
|
82
116
|
this.entered = this.value;
|
|
117
|
+
document.addEventListener('selectionchange', this.collapseSelection);
|
|
83
118
|
}
|
|
84
119
|
};
|
|
85
120
|
this.handleSegmentsFocusOut = (event) => {
|
|
86
121
|
if (this.segmentElements.includes(event.relatedTarget)) {
|
|
87
122
|
return;
|
|
88
123
|
}
|
|
124
|
+
document.removeEventListener('selectionchange', this.collapseSelection);
|
|
89
125
|
if (this.entered !== undefined && this.entered !== this.value) {
|
|
90
126
|
this.announceChange();
|
|
91
127
|
}
|
|
@@ -174,6 +210,29 @@ export const MixinSegmentedField = (Base) => {
|
|
|
174
210
|
const all = this.segmentElements;
|
|
175
211
|
all[all.indexOf(from) + by]?.focus();
|
|
176
212
|
}
|
|
213
|
+
edit(element, key) {
|
|
214
|
+
if (this.readOnly || this.isDisabled) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const row = Number(element.dataset.row ?? 0);
|
|
218
|
+
const edit = segmentKey(key, {
|
|
219
|
+
kind: element.dataset.segment,
|
|
220
|
+
locale: this.segmentLocale,
|
|
221
|
+
segments: this.parts[row] ?? {},
|
|
222
|
+
system: this.segmentSystem,
|
|
223
|
+
twelve: isTwelveHourLayout(this.layout),
|
|
224
|
+
typed: this.typed,
|
|
225
|
+
});
|
|
226
|
+
if (!edit) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
this.typed = edit.typed;
|
|
230
|
+
this.take(row, edit.segments);
|
|
231
|
+
if (edit.advance) {
|
|
232
|
+
this.moveFocus(element, 1);
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
177
236
|
willUpdate(changed) {
|
|
178
237
|
super.willUpdate(changed);
|
|
179
238
|
const own = this.own;
|
|
@@ -196,6 +255,7 @@ export const MixinSegmentedField = (Base) => {
|
|
|
196
255
|
disconnectedCallback() {
|
|
197
256
|
super.disconnectedCallback();
|
|
198
257
|
this.removeEventListener('click', this.handleLabelPress);
|
|
258
|
+
document.removeEventListener('selectionchange', this.collapseSelection);
|
|
199
259
|
}
|
|
200
260
|
get blocksImplicitSubmission() {
|
|
201
261
|
return this.segmentsShown || super.blocksImplicitSubmission;
|
|
@@ -260,7 +320,10 @@ export const MixinSegmentedField = (Base) => {
|
|
|
260
320
|
data-segment=${kind}
|
|
261
321
|
role=${spin ? 'spinbutton' : 'textbox'}
|
|
262
322
|
tabindex=${this.isDisabled ? '-1' : '0'}
|
|
263
|
-
|
|
323
|
+
contenteditable=${editable ? 'true' : nothing}
|
|
324
|
+
spellcheck=${editable ? 'false' : nothing}
|
|
325
|
+
autocorrect=${editable ? 'off' : nothing}
|
|
326
|
+
inputmode=${kind === 'dayPeriod' || !editable ? nothing : 'numeric'}
|
|
264
327
|
enterkeyhint=${editable ? 'next' : nothing}
|
|
265
328
|
aria-label=${named === id ? segmentName(kind, locale) : `${segmentName(kind, locale)},`}
|
|
266
329
|
aria-labelledby=${named === id ? nothing : named}
|
|
@@ -273,6 +336,8 @@ export const MixinSegmentedField = (Base) => {
|
|
|
273
336
|
aria-required=${this.required ? 'true' : nothing}
|
|
274
337
|
aria-readonly=${this.readOnly ? 'true' : nothing}
|
|
275
338
|
@keydown=${this.handleSegmentKeydown}
|
|
339
|
+
@beforeinput=${this.handleSegmentBeforeInput}
|
|
340
|
+
@input=${this.handleSegmentInput}
|
|
276
341
|
>${text}</span
|
|
277
342
|
>`;
|
|
278
343
|
})}`;
|
|
@@ -25,6 +25,12 @@ export declare const formatPercent: (share: number, locale?: string) => string;
|
|
|
25
25
|
* and the share of it that is filled.
|
|
26
26
|
*/
|
|
27
27
|
export declare const readingText: (value: number, share: number, locale?: string, options?: Intl.NumberFormatOptions) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Text with every decimal digit written in ASCII: Arabic-Indic `١٢٣`, Persian
|
|
30
|
+
* `۱۲۳` and Devanagari `१२३` read as `123`. A keyboard in one of those
|
|
31
|
+
* locales types its own digits, and `Number` reads only ASCII.
|
|
32
|
+
*/
|
|
33
|
+
export declare const latinDigits: (text: string) => string;
|
|
28
34
|
/**
|
|
29
35
|
* Reads a number written in a locale with the same options it is shown with,
|
|
30
36
|
* or `null` where the text is no number.
|
|
@@ -14,6 +14,14 @@ export const formatPercent = (share, locale = '') => formatNumber(share, locale,
|
|
|
14
14
|
export const readingText = (value, share, locale = '', options) => options && options.style !== 'percent'
|
|
15
15
|
? formatNumber(value, locale, options)
|
|
16
16
|
: formatNumber(share, locale, options ?? WHOLE_PERCENT);
|
|
17
|
+
export const latinDigits = (text) => text.replace(/\p{Nd}/gu, (digit) => {
|
|
18
|
+
const point = digit.codePointAt(0) ?? 0;
|
|
19
|
+
let zero = point;
|
|
20
|
+
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
|
|
21
|
+
zero -= 1;
|
|
22
|
+
}
|
|
23
|
+
return String((point - zero) % 10);
|
|
24
|
+
});
|
|
17
25
|
const grouped = (text, separator, decimal) => {
|
|
18
26
|
const whole = (text.split(decimal)[0] ?? '').replace(/^[+-]/u, '');
|
|
19
27
|
return new RegExp(`^\\d{1,3}(${literal(separator)}\\d{3})+$`, 'u').test(whole);
|
|
@@ -36,7 +44,7 @@ export const parseNumber = (text, locale = '', options) => {
|
|
|
36
44
|
.filter(Boolean)
|
|
37
45
|
.sort((a, b) => b.length - a.length);
|
|
38
46
|
const percent = options?.style === 'percent' && /[%\u066a\u2030]/u.test(text);
|
|
39
|
-
let read = text.replace(/[\s\u00a0\u202f]/gu, '');
|
|
47
|
+
let read = latinDigits(text).replace(/[\s\u00a0\u202f\u061c\u200e\u200f]/gu, '');
|
|
40
48
|
for (const one of dropped) {
|
|
41
49
|
read = read.replace(new RegExp(literal(one), 'gu'), '');
|
|
42
50
|
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.165.0';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-core",
|
|
3
3
|
"description": "Helix element primitives: base classes, mixins, decorators, utilities.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.165.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@floating-ui/dom": "^1.8.0",
|
|
59
|
-
"@fusion.dev/helix-icons": "0.
|
|
59
|
+
"@fusion.dev/helix-icons": "0.165.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@fusion.dev/helix-cem": "0.
|
|
63
|
-
"@fusion.dev/helix-testing": "0.
|
|
62
|
+
"@fusion.dev/helix-cem": "0.165.0",
|
|
63
|
+
"@fusion.dev/helix-testing": "0.165.0",
|
|
64
64
|
"@lit-labs/ssr": "^4.1.0",
|
|
65
65
|
"lit": "^3.2.1"
|
|
66
66
|
},
|