be-intl 0.0.50 → 0.0.52
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 +88 -0
- package/be-intl.js +27 -5
- package/def.js +21 -0
- package/emc.json +20 -1
- package/package.json +6 -6
- package//360/237/214/220.json +19 -0
package/README.md
CHANGED
|
@@ -60,6 +60,34 @@ Changing the formatted element's **own** `lang` after it has been enhanced re-fo
|
|
|
60
60
|
if you opt in with `be-intl-observe-lang` (`🌐-observe-lang`); changes to an ancestor's `lang`
|
|
61
61
|
after enhancement are not tracked.
|
|
62
62
|
|
|
63
|
+
## Announcing updates to assistive technology
|
|
64
|
+
|
|
65
|
+
`be-intl` writes the formatted string into `textContent` on first render and again whenever the
|
|
66
|
+
bound value or the effective locale changes. By default it leaves ARIA untouched — most
|
|
67
|
+
formatted `<data>` / `<time>` elements are static readouts, and turning every one into a live
|
|
68
|
+
region (especially for a locale switch that only changes *presentation*, not the value) is
|
|
69
|
+
usually just screen-reader noise.
|
|
70
|
+
|
|
71
|
+
When a particular value *is* something the user is watching change, opt that element in with
|
|
72
|
+
`be-intl-announce` (`🌐-announce`):
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<output be-intl be-intl-announce></output>
|
|
76
|
+
<data be-intl be-intl-announce value="0" id="unread"></data>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With the attribute set, `be-intl` — **after** the element's first render, so the initial value
|
|
80
|
+
isn't spoken on load — marks it as a polite live region:
|
|
81
|
+
|
|
82
|
+
- `aria-live="polite"` (skipped for `<output>`, which is already an implicit polite live
|
|
83
|
+
region), and
|
|
84
|
+
- `aria-atomic="true"`, so multi-token output such as a formatted date is announced as one
|
|
85
|
+
phrase rather than word-by-word.
|
|
86
|
+
|
|
87
|
+
Later re-formats then mutate an already-registered region and are announced. `assertive` is
|
|
88
|
+
intentionally not offered — interrupting the user to read out a reformatted number is almost
|
|
89
|
+
never the right call.
|
|
90
|
+
|
|
63
91
|
## Alternative names
|
|
64
92
|
|
|
65
93
|
The semantic example above involves a lot of keyboard tapping of the letters "be-intl". To avoid blisters on your itty bitty fingers, we provide an alternative base attribute you can use:
|
|
@@ -69,6 +97,66 @@ The semantic example above involves a lot of keyboard tapping of the letters "be
|
|
|
69
97
|
🌐-weekday=long 🌐-year=numeric 🌐-month=long 🌐-day=numeric></time>
|
|
70
98
|
```
|
|
71
99
|
|
|
100
|
+
## Programmatic attachment (no attribute)
|
|
101
|
+
|
|
102
|
+
The attribute syntax shown above shines for server-rendered HTML and progressive enhancement: the markup alone says how each value should be formatted. But most web development today renders on the client, with a framework (Lit, React, Vue, Svelte, etc.) that already has a JavaScript reference to each element it creates. In that setting, attaching be-intl programmatically is the better fit:
|
|
103
|
+
|
|
104
|
+
1. **A less clunky API.** Frameworks tend to be awkward about setting arbitrary (let alone emoji) attributes, and quoting JSON inside an attribute, like `be-intl='{ "style": "currency", "currency": "EUR" }'`, is error prone. Setting `format` to `{style: 'currency', currency: 'EUR'}` is ordinary JavaScript, which TypeScript can check against `Intl.NumberFormatOptions` / `Intl.DateTimeFormatOptions`. You can also set `locale` directly, which has no attribute equivalent.
|
|
105
|
+
2. **Less stringifying and parsing.** With an attribute, the framework serializes the format options to JSON, and be-intl then parses that JSON back into an object. Setting `format` directly skips both steps.
|
|
106
|
+
3. **Less overhead monitoring attributes.** The attribute approach relies on [be-hive](https://github.com/bahrus/be-hive) / [mount-observer](https://github.com/bahrus/mount-observer) watching the DOM for elements that carry (or gain) the attribute, and for changes to its value. The programmatic approach needs none of that -- `def.js` just registers the enhancement's config, and the enhancement is attached exactly when, and to exactly the elements, your code says.
|
|
107
|
+
|
|
108
|
+
Both approaches produce the same enhancement, with the same locale resolution and formatting rules, so you can mix them in one app -- attributes for server-rendered islands, programmatic attachment inside client-rendered components.
|
|
109
|
+
|
|
110
|
+
First register the enhancement's config once:
|
|
111
|
+
|
|
112
|
+
```JS
|
|
113
|
+
import { defBeIntl } from 'be-intl/def.js';
|
|
114
|
+
const emc = await defBeIntl(document.body); // or a shadow root's host, for a scoped registry
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then set any of these properties:
|
|
118
|
+
|
|
119
|
+
| Attribute | Property | Notes |
|
|
120
|
+
|---------------------------------------------|-----------------------------------|----------------------------------------------------------------------------------------|
|
|
121
|
+
| `be-intl` / `🌐` | `format` | An `Intl.NumberFormatOptions` / `Intl.DateTimeFormatOptions` object. A bare attribute is `{}`. |
|
|
122
|
+
| `be-intl-style`, `-currency`, `-weekday`, `-year`, `-month`, `-day` | `style`, `currency`, `weekday`, `year`, `month`, `day` | Folded into `format`; keys set explicitly in `format` win. |
|
|
123
|
+
| `be-intl-observe-lang` | `observeLang` | `true` / `false`. |
|
|
124
|
+
| `be-intl-announce` | `announce` | `true` / `false`. |
|
|
125
|
+
| *(none -- `lang`)* | `locale` | A BCP-47 tag. Overrides the locale resolved from `lang`. |
|
|
126
|
+
|
|
127
|
+
### Declarative -- via `enh.set`
|
|
128
|
+
|
|
129
|
+
```JS
|
|
130
|
+
// equivalent to <data value=123456.789 lang="de-DE" be-intl='{ "style": "currency", "currency": "EUR" }'>
|
|
131
|
+
data.enh.set.beIntl.format = {style: 'currency', currency: 'EUR'};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Only the first property needs `.set` -- it's what triggers the attachment. This can be done before or after `defBeIntl` has been called.
|
|
135
|
+
|
|
136
|
+
### Imperative -- via `enh.get()`
|
|
137
|
+
|
|
138
|
+
```JS
|
|
139
|
+
Object.assign(data.enh.get(emc), {
|
|
140
|
+
locale: 'de-DE',
|
|
141
|
+
style: 'currency',
|
|
142
|
+
currency: 'USD',
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`data.enh.get(emc)` with no properties at all is equivalent to a bare `be-intl` attribute.
|
|
147
|
+
|
|
148
|
+
### Changing the formatting later
|
|
149
|
+
|
|
150
|
+
After the first render, setting `locale`, `format` or one of the semantic properties re-formats the element:
|
|
151
|
+
|
|
152
|
+
```JS
|
|
153
|
+
data.enh.beIntl.currency = 'EUR';
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`format` is compared by reference, so assign a new object rather than mutating the existing one.
|
|
157
|
+
|
|
158
|
+
See [demo/Programmatic](demo/Programmatic/) for runnable examples.
|
|
159
|
+
|
|
72
160
|
## Viewing Demos Locally
|
|
73
161
|
|
|
74
162
|
1. Install git
|
package/be-intl.js
CHANGED
|
@@ -32,7 +32,9 @@ class BeIntl {
|
|
|
32
32
|
* @param {PAP} initVals
|
|
33
33
|
*/
|
|
34
34
|
async init(self, enhancedElement, ctx, initVals){
|
|
35
|
-
|
|
35
|
+
// ctx.emc is only populated on the attribute (mount-observer) path;
|
|
36
|
+
// enh.get() / enh.set only pass the registry item -- see def.js.
|
|
37
|
+
const {customData} = /** @type {EMC<any, AllProps, Element, RAConfig<AllProps, Actions>>} */ (ctx.emc || ctx.config);
|
|
36
38
|
/**
|
|
37
39
|
* @type {RoundaboutOptions}
|
|
38
40
|
*/
|
|
@@ -56,9 +58,6 @@ class BeIntl {
|
|
|
56
58
|
*/
|
|
57
59
|
async hydrate(self){
|
|
58
60
|
const {enhancedElement} = self;
|
|
59
|
-
if(enhancedElement instanceof HTMLElement){
|
|
60
|
-
enhancedElement.ariaLive = 'polite';
|
|
61
|
-
}
|
|
62
61
|
|
|
63
62
|
// Re-format whenever the element's underlying value property changes.
|
|
64
63
|
// <data>/<output> reflect through `value`, <time> through `dateTime`.
|
|
@@ -111,6 +110,7 @@ class BeIntl {
|
|
|
111
110
|
|
|
112
111
|
/**
|
|
113
112
|
* @param {AP} self
|
|
113
|
+
* @returns {PAP | void}
|
|
114
114
|
*/
|
|
115
115
|
formatNumber(self){
|
|
116
116
|
const {enhancedElement, value, intlNumberFormat} = self;
|
|
@@ -119,15 +119,37 @@ class BeIntl {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
enhancedElement.textContent = intlNumberFormat.format(/** @type {number} */ (value));
|
|
122
|
+
if(!self.rendered) return {rendered: true};
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
/**
|
|
125
126
|
* @param {AP} self
|
|
127
|
+
* @returns {PAP | void}
|
|
126
128
|
*/
|
|
127
129
|
formatDate(self){
|
|
128
130
|
const {enhancedElement, value, intlDateFormat} = self;
|
|
129
131
|
if(intlDateFormat === undefined || value === undefined || value === null) return;
|
|
130
132
|
enhancedElement.textContent = intlDateFormat.format(/** @type {Date} */ (value));
|
|
133
|
+
if(!self.rendered) return {rendered: true};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Opt-in via `be-intl-announce`. Once the value has rendered at least once,
|
|
138
|
+
* mark the element as a polite ARIA live region so subsequent re-formats
|
|
139
|
+
* (value or locale changes) are announced by assistive tech. Deliberately
|
|
140
|
+
* deferred past the first render so the initial value isn't spoken on load,
|
|
141
|
+
* and `aria-live` is skipped for `<output>` (already an implicit polite live
|
|
142
|
+
* region). `aria-atomic` keeps multi-token output (e.g. a formatted date)
|
|
143
|
+
* announcing as one unit.
|
|
144
|
+
* @param {AP} self
|
|
145
|
+
*/
|
|
146
|
+
armLiveRegion(self){
|
|
147
|
+
const {enhancedElement} = self;
|
|
148
|
+
if(!(enhancedElement instanceof HTMLElement)) return;
|
|
149
|
+
enhancedElement.setAttribute('aria-atomic', 'true');
|
|
150
|
+
if(enhancedElement.localName !== 'output'){
|
|
151
|
+
enhancedElement.setAttribute('aria-live', 'polite');
|
|
152
|
+
}
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
155
|
|
|
@@ -141,7 +163,7 @@ class BeIntl {
|
|
|
141
163
|
*/
|
|
142
164
|
async function infer(from){
|
|
143
165
|
return /** @type {any} */ (
|
|
144
|
-
from.enh.get((await import('inferencer/inferencer.js')).registryItem)
|
|
166
|
+
from.enh.get((await import('assign-gingerly/inferencer/inferencer.js')).registryItem)
|
|
145
167
|
);
|
|
146
168
|
}
|
|
147
169
|
|
package/def.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import 'assign-gingerly/object-extension.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registers be-intl's config, so it can be attached programmatically
|
|
5
|
+
* via el.enh.set.beIntl / el.enh.get(emc).
|
|
6
|
+
* @param {Element | undefined} ref element whose customElementRegistry to register against
|
|
7
|
+
*/
|
|
8
|
+
export async function defBeIntl(ref){
|
|
9
|
+
const {default: emc} = await import('./emc.json', {with: {type: 'json'}});
|
|
10
|
+
return await push(ref, emc);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function push(ref, emc){
|
|
14
|
+
const {BeIntl} = await import('./be-intl.js');
|
|
15
|
+
const {enhConfig} = emc;
|
|
16
|
+
enhConfig.spawn = BeIntl;
|
|
17
|
+
enhConfig.customData = emc.customData; // the registry only stores enhConfig, not the full emc
|
|
18
|
+
const registry = (ref?.customElementRegistry ?? customElements).enhancementRegistry;
|
|
19
|
+
registry.push(enhConfig);
|
|
20
|
+
return enhConfig;
|
|
21
|
+
}
|
package/emc.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enhConfig": {
|
|
3
|
-
"enhKey": "
|
|
3
|
+
"enhKey": "beIntl",
|
|
4
4
|
"spawn": "be-intl/be-intl.js",
|
|
5
5
|
"withAttrs": {
|
|
6
6
|
"base": "be-intl",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"observeLang": "${base}-observe-lang",
|
|
19
19
|
"_observeLang": {
|
|
20
20
|
"instanceOf": "Boolean"
|
|
21
|
+
},
|
|
22
|
+
"announce": "${base}-announce",
|
|
23
|
+
"_announce": {
|
|
24
|
+
"instanceOf": "Boolean"
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
},
|
|
@@ -45,6 +49,12 @@
|
|
|
45
49
|
"ifKeyIn": [
|
|
46
50
|
"locale",
|
|
47
51
|
"format",
|
|
52
|
+
"style",
|
|
53
|
+
"currency",
|
|
54
|
+
"weekday",
|
|
55
|
+
"year",
|
|
56
|
+
"month",
|
|
57
|
+
"day",
|
|
48
58
|
"initialized"
|
|
49
59
|
]
|
|
50
60
|
},
|
|
@@ -65,6 +75,15 @@
|
|
|
65
75
|
"value",
|
|
66
76
|
"intlDateFormat"
|
|
67
77
|
]
|
|
78
|
+
},
|
|
79
|
+
"armLiveRegion": {
|
|
80
|
+
"ifAllOf": [
|
|
81
|
+
"announce",
|
|
82
|
+
"rendered"
|
|
83
|
+
],
|
|
84
|
+
"ifKeyIn": [
|
|
85
|
+
"rendered"
|
|
86
|
+
]
|
|
68
87
|
}
|
|
69
88
|
}
|
|
70
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "be-intl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.52",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"web-components",
|
|
6
6
|
"web-component",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
".": "./be-intl.js",
|
|
15
15
|
"./be-intl.js": "./be-intl.js",
|
|
16
16
|
"./🌐.js": "./🌐.js",
|
|
17
|
+
"./def.js": "./def.js",
|
|
17
18
|
"./emc.json": "./emc.json",
|
|
18
19
|
"./🌐.json": "./🌐.json"
|
|
19
20
|
},
|
|
@@ -31,15 +32,14 @@
|
|
|
31
32
|
"update": "ncu -u && npm install"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"assign-gingerly": "0.0.
|
|
35
|
+
"assign-gingerly": "0.0.97",
|
|
35
36
|
"be-hive": "0.1.18",
|
|
36
|
-
"inferencer": "0.0.13",
|
|
37
37
|
"mount-observer": "0.1.53",
|
|
38
|
-
"roundabout-lib": "0.0.
|
|
38
|
+
"roundabout-lib": "0.0.38"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"spa-ssi": "0.0.
|
|
42
|
-
"@playwright/test": "1.
|
|
41
|
+
"spa-ssi": "0.0.28",
|
|
42
|
+
"@playwright/test": "1.63.0"
|
|
43
43
|
},
|
|
44
44
|
"author": "anderson.bruce.b@gmail.com",
|
|
45
45
|
"license": "MIT"
|
package//360/237/214/220.json
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"observeLang": "${base}-observe-lang",
|
|
19
19
|
"_observeLang": {
|
|
20
20
|
"instanceOf": "Boolean"
|
|
21
|
+
},
|
|
22
|
+
"announce": "${base}-announce",
|
|
23
|
+
"_announce": {
|
|
24
|
+
"instanceOf": "Boolean"
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
},
|
|
@@ -45,6 +49,12 @@
|
|
|
45
49
|
"ifKeyIn": [
|
|
46
50
|
"locale",
|
|
47
51
|
"format",
|
|
52
|
+
"style",
|
|
53
|
+
"currency",
|
|
54
|
+
"weekday",
|
|
55
|
+
"year",
|
|
56
|
+
"month",
|
|
57
|
+
"day",
|
|
48
58
|
"initialized"
|
|
49
59
|
]
|
|
50
60
|
},
|
|
@@ -65,6 +75,15 @@
|
|
|
65
75
|
"value",
|
|
66
76
|
"intlDateFormat"
|
|
67
77
|
]
|
|
78
|
+
},
|
|
79
|
+
"armLiveRegion": {
|
|
80
|
+
"ifAllOf": [
|
|
81
|
+
"announce",
|
|
82
|
+
"rendered"
|
|
83
|
+
],
|
|
84
|
+
"ifKeyIn": [
|
|
85
|
+
"rendered"
|
|
86
|
+
]
|
|
68
87
|
}
|
|
69
88
|
}
|
|
70
89
|
}
|