be-intl 0.0.51 → 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 +60 -0
- package/be-intl.js +170 -168
- package/def.js +21 -0
- package/emc.json +7 -1
- package/package.json +5 -4
- package//360/237/214/220.json +6 -0
package/README.md
CHANGED
|
@@ -97,6 +97,66 @@ The semantic example above involves a lot of keyboard tapping of the letters "be
|
|
|
97
97
|
🌐-weekday=long 🌐-year=numeric 🌐-month=long 🌐-day=numeric></time>
|
|
98
98
|
```
|
|
99
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
|
+
|
|
100
160
|
## Viewing Demos Locally
|
|
101
161
|
|
|
102
162
|
1. Install git
|
package/be-intl.js
CHANGED
|
@@ -1,168 +1,170 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
/** @import {Actions, PAP, AllProps, AP} from './types/be-intl/types' */;
|
|
3
|
-
/** @import {RoundaboutOptions} from './types/roundabout/types' */;
|
|
4
|
-
/** @import {ElementEnhancementGateway, SpawnContext} from './types/assign-gingerly/types' */;
|
|
5
|
-
/** @import {EMC} from './types/mount-observer/types' */;
|
|
6
|
-
/** @import {RAConfig} from './types/roundabout/types' */;
|
|
7
|
-
|
|
8
|
-
const defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
|
|
9
|
-
|
|
10
|
-
/** Semantic sugar attributes folded into `format`. */
|
|
11
|
-
const SEMANTIC_KEYS = ['style', 'currency', 'weekday', 'year', 'month', 'day'];
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @implements {Actions}
|
|
15
|
-
*/
|
|
16
|
-
class BeIntl {
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @this {AllProps & Actions}
|
|
20
|
-
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
21
|
-
* @param {SpawnContext} ctx
|
|
22
|
-
* @param {PAP} initVals
|
|
23
|
-
*/
|
|
24
|
-
constructor(enhancedElement, ctx, initVals){
|
|
25
|
-
this.init(this, enhancedElement, ctx, initVals);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {AllProps} self
|
|
30
|
-
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
31
|
-
* @param {SpawnContext} ctx
|
|
32
|
-
* @param {PAP} initVals
|
|
33
|
-
*/
|
|
34
|
-
async init(self, enhancedElement, ctx, initVals){
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
enhancedElement
|
|
131
|
-
if(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
enhancedElement
|
|
148
|
-
if(enhancedElement
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*
|
|
158
|
-
* `
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import {Actions, PAP, AllProps, AP} from './types/be-intl/types' */;
|
|
3
|
+
/** @import {RoundaboutOptions} from './types/roundabout/types' */;
|
|
4
|
+
/** @import {ElementEnhancementGateway, SpawnContext} from './types/assign-gingerly/types' */;
|
|
5
|
+
/** @import {EMC} from './types/mount-observer/types' */;
|
|
6
|
+
/** @import {RAConfig} from './types/roundabout/types' */;
|
|
7
|
+
|
|
8
|
+
const defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
|
|
9
|
+
|
|
10
|
+
/** Semantic sugar attributes folded into `format`. */
|
|
11
|
+
const SEMANTIC_KEYS = ['style', 'currency', 'weekday', 'year', 'month', 'day'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @implements {Actions}
|
|
15
|
+
*/
|
|
16
|
+
class BeIntl {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @this {AllProps & Actions}
|
|
20
|
+
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
21
|
+
* @param {SpawnContext} ctx
|
|
22
|
+
* @param {PAP} initVals
|
|
23
|
+
*/
|
|
24
|
+
constructor(enhancedElement, ctx, initVals){
|
|
25
|
+
this.init(this, enhancedElement, ctx, initVals);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {AllProps} self
|
|
30
|
+
* @param {Element & ElementEnhancementGateway} enhancedElement
|
|
31
|
+
* @param {SpawnContext} ctx
|
|
32
|
+
* @param {PAP} initVals
|
|
33
|
+
*/
|
|
34
|
+
async init(self, enhancedElement, ctx, initVals){
|
|
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);
|
|
38
|
+
/**
|
|
39
|
+
* @type {RoundaboutOptions}
|
|
40
|
+
*/
|
|
41
|
+
const raOptions = {
|
|
42
|
+
...customData,
|
|
43
|
+
vm: self,
|
|
44
|
+
initialPropVals: {
|
|
45
|
+
enhancedElement,
|
|
46
|
+
...customData?.defaultPropVals,
|
|
47
|
+
...initVals
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
await (await import('roundabout-lib/roundabout.js')).roundabout(raOptions);
|
|
51
|
+
self.initialized = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Wire up live-update observation, resolve the locale, and seed the value.
|
|
56
|
+
* @param {AP} self
|
|
57
|
+
* @returns {Promise<PAP>}
|
|
58
|
+
*/
|
|
59
|
+
async hydrate(self){
|
|
60
|
+
const {enhancedElement} = self;
|
|
61
|
+
|
|
62
|
+
// Re-format whenever the element's underlying value property changes.
|
|
63
|
+
// <data>/<output> reflect through `value`, <time> through `dateTime`.
|
|
64
|
+
const valueProp = enhancedElement.localName === 'time' ? 'dateTime' : 'value';
|
|
65
|
+
const inference = await infer(enhancedElement);
|
|
66
|
+
const propagator = await inference.getPropagator();
|
|
67
|
+
propagator.addEventListener(valueProp, () => {
|
|
68
|
+
self.value = inference.value;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Opt-in: track lang changes on the element itself. Container-`lang`
|
|
72
|
+
// changes after mount aren't observed (rare); `inference.lang` still
|
|
73
|
+
// walks ancestors + shadow hosts on each read.
|
|
74
|
+
if(self.observeLang && enhancedElement instanceof HTMLElement){
|
|
75
|
+
const langObserver = new MutationObserver(() => {
|
|
76
|
+
self.locale = inference.lang || defaultLocale;
|
|
77
|
+
});
|
|
78
|
+
langObserver.observe(enhancedElement, {attributes: true, attributeFilter: ['lang']});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const locale = self.locale || inference.lang || defaultLocale;
|
|
82
|
+
return {locale, value: inference.value};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* (Re)build the Intl formatter. `value` is seeded by `hydrate` and kept
|
|
87
|
+
* fresh by the propagator, so it isn't read here.
|
|
88
|
+
* @param {AP} self
|
|
89
|
+
* @returns {PAP}
|
|
90
|
+
*/
|
|
91
|
+
onFormattingChange(self){
|
|
92
|
+
const {enhancedElement, locale} = self;
|
|
93
|
+
// Explicit JSON (`self.format`) wins; semantic sub-attributes fill the gaps.
|
|
94
|
+
const format = {.../** @type {Record<string, any>} */ (self.format || {})};
|
|
95
|
+
for(const key of SEMANTIC_KEYS){
|
|
96
|
+
const val = /** @type {Record<string, any>} */ (self)[key];
|
|
97
|
+
if(val !== undefined && format[key] === undefined) format[key] = val;
|
|
98
|
+
}
|
|
99
|
+
if(enhancedElement.localName === 'time'){
|
|
100
|
+
return {
|
|
101
|
+
intlDateFormat: new Intl.DateTimeFormat(locale, /** @type {Intl.DateTimeFormatOptions} */ (format)),
|
|
102
|
+
resolved: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
intlNumberFormat: new Intl.NumberFormat(locale, /** @type {Intl.NumberFormatOptions} */ (format)),
|
|
107
|
+
resolved: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {AP} self
|
|
113
|
+
* @returns {PAP | void}
|
|
114
|
+
*/
|
|
115
|
+
formatNumber(self){
|
|
116
|
+
const {enhancedElement, value, intlNumberFormat} = self;
|
|
117
|
+
if(intlNumberFormat === undefined || value === undefined || value === null){
|
|
118
|
+
enhancedElement.textContent = '';
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
enhancedElement.textContent = intlNumberFormat.format(/** @type {number} */ (value));
|
|
122
|
+
if(!self.rendered) return {rendered: true};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {AP} self
|
|
127
|
+
* @returns {PAP | void}
|
|
128
|
+
*/
|
|
129
|
+
formatDate(self){
|
|
130
|
+
const {enhancedElement, value, intlDateFormat} = self;
|
|
131
|
+
if(intlDateFormat === undefined || value === undefined || value === null) return;
|
|
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
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve the `inferencer` enhancement instance for an element. Typed loosely
|
|
158
|
+
* (`Promise<any>`) so the class doesn't couple to the `Infer` shape; `.value`
|
|
159
|
+
* is a live, type-coerced read (`Date` for `<time>`, `number` for `<data>`),
|
|
160
|
+
* `getPropagator()` emits on value-property changes.
|
|
161
|
+
* @param {Element & ElementEnhancementGateway} from
|
|
162
|
+
* @returns {Promise<any>}
|
|
163
|
+
*/
|
|
164
|
+
async function infer(from){
|
|
165
|
+
return /** @type {any} */ (
|
|
166
|
+
from.enh.get((await import('assign-gingerly/inferencer/inferencer.js')).registryItem)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { BeIntl };
|
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",
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
"ifKeyIn": [
|
|
50
50
|
"locale",
|
|
51
51
|
"format",
|
|
52
|
+
"style",
|
|
53
|
+
"currency",
|
|
54
|
+
"weekday",
|
|
55
|
+
"year",
|
|
56
|
+
"month",
|
|
57
|
+
"day",
|
|
52
58
|
"initialized"
|
|
53
59
|
]
|
|
54
60
|
},
|
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,14 +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
37
|
"mount-observer": "0.1.53",
|
|
37
|
-
"roundabout-lib": "0.0.
|
|
38
|
+
"roundabout-lib": "0.0.38"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"spa-ssi": "0.0.28",
|
|
41
|
-
"@playwright/test": "1.
|
|
42
|
+
"@playwright/test": "1.63.0"
|
|
42
43
|
},
|
|
43
44
|
"author": "anderson.bruce.b@gmail.com",
|
|
44
45
|
"license": "MIT"
|