@rak200/ui 0.2.5 → 0.2.7

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 CHANGED
@@ -37,6 +37,13 @@ npm install @rak200/ui
37
37
  <ui-button>Save</ui-button>
38
38
  <ui-button variant="secondary">Cancel</ui-button>
39
39
  <ui-button disabled>Unavailable</ui-button>
40
+
41
+ <ui-dialog>
42
+ <h2 slot="title">Delete account</h2>
43
+ <p>This cannot be undone.</p>
44
+ <ui-button slot="actions" variant="secondary">Cancel</ui-button>
45
+ <ui-button slot="actions">Delete</ui-button>
46
+ </ui-dialog>
40
47
  ```
41
48
 
42
49
  Every visual decision is a CSS custom property, so a host restyles the kit without forking it:
@@ -64,8 +71,8 @@ components; `docs/` is what describes them.
64
71
 
65
72
  ## Status
66
73
 
67
- **v0.** One component and the token layer, built to the ecosystem's full quality bar rather than
68
- sketched: type-checked at the strictest available setting, formatted, tested in a real browser
69
- and **asserted against axe** for WCAG A/AA, 100% coverage and **100% mutation score**, scanned, and
70
- every public symbol documented. The v0
71
- surface in RFC 0016 grows from here — see [ROADMAP.md](ROADMAP.md).
74
+ **v0.** Three components — `<ui-button>`, `<ui-field>` and `<ui-dialog>` — and the token layer under
75
+ them, built to the ecosystem's full quality bar rather than sketched: type-checked at the strictest available
76
+ setting, formatted, tested in a real browser and **asserted against axe** for WCAG A/AA, 100%
77
+ coverage and **100% mutation score**, scanned, and every public symbol documented. The v0 surface in
78
+ RFC 0016 grows from here — see [ROADMAP.md](ROADMAP.md).
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGjE,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACpC,gBAAyB,MAAM,0BAoE7B;IAEF,gBAAyB,UAAU;;;;;;;;;MAGjC;IAEF;;;;;;;;OAQG;IACH,OAAO,EAAE,aAAa,CAAa;IAEnC,kFAAkF;IAClF,QAAQ,UAAS;IAER,MAAM,IAAI,cAAc;CAOpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,WAAW,EAAE,QAAQ,CAAC;KACzB;CACJ"}
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGjE,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACpC,gBAAyB,MAAM,0BA6E7B;IAEF,gBAAyB,UAAU;;;;;;;;;MAGjC;IAEF;;;;;;;;OAQG;IACH,OAAO,EAAE,aAAa,CAAa;IAEnC,kFAAkF;IAClF,QAAQ,UAAS;IAER,MAAM,IAAI,cAAc;CAOpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,WAAW,EAAE,QAAQ,CAAC;KACzB;CACJ"}
package/dist/button.js CHANGED
@@ -42,6 +42,15 @@ export class UiButton extends LitElement {
42
42
  border-radius: ${reference('--ui-radius')};
43
43
  padding: ${reference('--ui-space')} calc(${reference('--ui-space')} * 2);
44
44
  cursor: pointer;
45
+ /* Measured in WebKit and in Chromium under a phone viewport: the platform
46
+ paints its own wash on tap — 40% black in WebKit, the Android blue in
47
+ Chromium — over whatever the component decided, so the pressed colour below
48
+ arrives underneath it and is not what a finger sees.
49
+
50
+ Removing it is only correct because the pressed state exists. Until it did,
51
+ this wash was the ONLY response a touch got, and turning it off would have
52
+ left a button that answers a finger with nothing. */
53
+ -webkit-tap-highlight-color: transparent;
45
54
  /* Only the colour moves. The focus ring is deliberately not in this list:
46
55
  delaying the affordance that says *this is where you are* is the opposite of
47
56
  what it exists to do. */
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK3C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,QAAS,SAAQ,UAAU;IAAxC;;QA4EI;;;;;;;;WAQG;QACH,YAAO,GAAkB,SAAS,CAAC;QAEnC,kFAAkF;QAClF,aAAQ,GAAG,KAAK,CAAC;IASrB,CAAC;aAhG4B,WAAM,GAAG,GAAG,CAAA;;;;;;;2BAOd,SAAS,CAAC,WAAW,CAAC;;6BAEpB,SAAS,CAAC,aAAa,CAAC;uBAC9B,SAAS,CAAC,YAAY,CAAC,SAAS,SAAS,CAAC,YAAY,CAAC;;;;;2CAKnC,SAAS,CAAC,qBAAqB,CAAC;kBACzD,SAAS,CAAC,mBAAmB,CAAC;;;;;;;;;;;iCAWf,SAAS,CAAC,kBAAkB,CAAC;;;;;0BAKpC,SAAS,CAAC,mBAAmB,CAAC;qBACnC,SAAS,CAAC,4BAA4B,CAAC;;;;0BAIlC,SAAS,CAAC,oBAAoB,CAAC;qBACpC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;0BASvB,SAAS,CAAC,yBAAyB,CAAC;;;;0BAIpC,SAAS,CAAC,kBAAkB,CAAC;;;;;;;;;0BAS7B,SAAS,CAAC,2BAA2B,CAAC;;;;;0BAKtC,SAAS,CAAC,oBAAoB,CAAC;;;KAGpD,AApE8B,CAoE7B;aAEuB,eAAU,GAAG;QAClC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACxC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;KAC7C,AAHkC,CAGjC;IAgBO,MAAM;QACX,OAAO,IAAI,CAAA;4BACS,IAAI,CAAC,OAAO,cAAc,IAAI,CAAC,QAAQ;;;SAG1D,CAAC;IACN,CAAC;;AAGL,kFAAkF;AAClF,wFAAwF;AACxF,yFAAyF;AACzF,4EAA4E;AAC5E,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK3C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,QAAS,SAAQ,UAAU;IAAxC;;QAqFI;;;;;;;;WAQG;QACH,YAAO,GAAkB,SAAS,CAAC;QAEnC,kFAAkF;QAClF,aAAQ,GAAG,KAAK,CAAC;IASrB,CAAC;aAzG4B,WAAM,GAAG,GAAG,CAAA;;;;;;;2BAOd,SAAS,CAAC,WAAW,CAAC;;6BAEpB,SAAS,CAAC,aAAa,CAAC;uBAC9B,SAAS,CAAC,YAAY,CAAC,SAAS,SAAS,CAAC,YAAY,CAAC;;;;;;;;;;;;;;2CAcnC,SAAS,CAAC,qBAAqB,CAAC;kBACzD,SAAS,CAAC,mBAAmB,CAAC;;;;;;;;;;;iCAWf,SAAS,CAAC,kBAAkB,CAAC;;;;;0BAKpC,SAAS,CAAC,mBAAmB,CAAC;qBACnC,SAAS,CAAC,4BAA4B,CAAC;;;;0BAIlC,SAAS,CAAC,oBAAoB,CAAC;qBACpC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;0BASvB,SAAS,CAAC,yBAAyB,CAAC;;;;0BAIpC,SAAS,CAAC,kBAAkB,CAAC;;;;;;;;;0BAS7B,SAAS,CAAC,2BAA2B,CAAC;;;;;0BAKtC,SAAS,CAAC,oBAAoB,CAAC;;;KAGpD,AA7E8B,CA6E7B;aAEuB,eAAU,GAAG;QAClC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACxC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;KAC7C,AAHkC,CAGjC;IAgBO,MAAM;QACX,OAAO,IAAI,CAAA;4BACS,IAAI,CAAC,OAAO,cAAc,IAAI,CAAC,QAAQ;;;SAG1D,CAAC;IACN,CAAC;;AAGL,kFAAkF;AAClF,wFAAwF;AACxF,yFAAyF;AACzF,4EAA4E;AAC5E,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { LitElement, type TemplateResult } from 'lit';
2
+ /**
3
+ * A modal dialog.
4
+ *
5
+ * It delegates to a real `<dialog>` and opens it with `showModal()`, which is what makes
6
+ * the top layer, the inert background, the focus trap, <kbd>Esc</kbd> and — the half
7
+ * everyone forgets — **focus returning to whatever opened it** the platform's job rather
8
+ * than this file's. What is written here is the part the platform leaves out: the scroll
9
+ * lock, the accessible name across the shadow boundary, and an exit that can be seen.
10
+ *
11
+ * **This is where RFC 0016 expected Zag to arrive, and it does not.** Zag was adopted for
12
+ * dismissable layers and focus trapping, and its dialog machine implements both over a
13
+ * `<div>` — which means giving up the top layer and taking a JS focus trap in exchange for
14
+ * one the user agent enforces. *The platform owns what the platform is good at* is the
15
+ * older rule and the stronger one here. Zag arrives with the first component the platform
16
+ * gives nothing for; `ARCHITECTURE.md` carries that decision for a consumer, and
17
+ * `ROADMAP.md` names the component.
18
+ *
19
+ * @example
20
+ * ```html
21
+ * <ui-dialog>
22
+ * <h2 slot="title">Delete account</h2>
23
+ * <p>This cannot be undone.</p>
24
+ * <ui-button slot="actions" variant="secondary">Cancel</ui-button>
25
+ * <ui-button slot="actions">Delete</ui-button>
26
+ * </ui-dialog>
27
+ * ```
28
+ */
29
+ export declare class UiDialog extends LitElement {
30
+ #private;
31
+ static readonly styles: import("lit").CSSResult;
32
+ static readonly properties: {
33
+ open: {
34
+ type: BooleanConstructor;
35
+ reflect: boolean;
36
+ };
37
+ };
38
+ /**
39
+ * Whether the dialog is showing.
40
+ *
41
+ * **The single source of truth, and the only one.** {@link show} and {@link close} set
42
+ * it, <kbd>Esc</kbd> sets it, and the component reacts to it — so an attribute, a
43
+ * property and a method are three ways of saying one thing rather than three paths
44
+ * that can disagree. Reflected, so a host stylesheet can select on it.
45
+ *
46
+ * A plain field rather than the `accessor` keyword, for the reason `src/button.ts`
47
+ * states beside its own.
48
+ */
49
+ open: boolean;
50
+ constructor();
51
+ connectedCallback(): void;
52
+ disconnectedCallback(): void;
53
+ firstUpdated(): void;
54
+ updated(): void;
55
+ render(): TemplateResult;
56
+ /**
57
+ * Opens the dialog.
58
+ *
59
+ * Sugar over `open = true` rather than a second way in — a method that opened by its
60
+ * own route would be a state the attribute does not describe.
61
+ */
62
+ show(): void;
63
+ /** Closes the dialog, running the exit. Sugar over `open = false`, for the same reason. */
64
+ close(): void;
65
+ }
66
+ declare global {
67
+ interface HTMLElementTagNameMap {
68
+ 'ui-dialog': UiDialog;
69
+ }
70
+ }
71
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../src/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAoGjE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,QAAS,SAAQ,UAAU;;IACpC,gBAAyB,MAAM,0BAwG7B;IAEF,gBAAyB,UAAU;;;;;MAEjC;IAEF;;;;;;;;;;OAUG;IACH,IAAI,UAAS;;IAyCJ,iBAAiB,IAAI,IAAI;IASzB,oBAAoB,IAAI,IAAI;IAU5B,YAAY,IAAI,IAAI;IAIpB,OAAO,IAAI,IAAI;IAiBf,MAAM,IAAI,cAAc;IAIjC;;;;;OAKG;IACH,IAAI,IAAI,IAAI;IAIZ,2FAA2F;IAC3F,KAAK,IAAI,IAAI;CA0FhB;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,WAAW,EAAE,QAAQ,CAAC;KACzB;CACJ"}
package/dist/dialog.js ADDED
@@ -0,0 +1,406 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { reference } from './reference.js';
3
+ /** The class the exit is carried by, on the dialog for exactly as long as the exit runs. */
4
+ const closing = 'closing';
5
+ /** How many dialogs are holding the page still. */
6
+ let locks = 0;
7
+ /**
8
+ * The inline values the first hold displaced, given back by the last release.
9
+ *
10
+ * No initial value, deliberately: nothing reads it before {@link hold} writes it, and an
11
+ * initialiser here would be a value no test could ever observe.
12
+ */
13
+ let displaced;
14
+ /**
15
+ * Holds the page still behind a modal, and gives it back when the last one leaves.
16
+ *
17
+ * `<dialog>` gives a modal the top layer, an inert background, a focus trap and focus
18
+ * restoration. It does not stop the page behind it scrolling, and that is the one piece of
19
+ * modality left to write.
20
+ *
21
+ * **The gutter is reserved rather than the scrollbar measured**, which is the difference
22
+ * between a rule and an arithmetic nothing can check. `overflow: hidden` takes the
23
+ * scrollbar away and the page jumps left by its width; the usual answer adds that width
24
+ * back as padding — and the browser this suite runs in has overlay scrollbars, so the
25
+ * width is `0`, every sign error in that arithmetic passes, and the check is theatre.
26
+ * `scrollbar-gutter: stable` asks the browser to keep the space it was already using,
27
+ * with no number to get wrong.
28
+ *
29
+ * Counted rather than flagged, because a dialog opening a dialog is ordinary — a confirm
30
+ * over a form — and the second one closing must not give the page back while the first is
31
+ * still up.
32
+ */
33
+ function hold() {
34
+ locks += 1;
35
+ if (locks > 1) {
36
+ return;
37
+ }
38
+ const root = document.documentElement;
39
+ displaced = {
40
+ overflow: root.style.overflow,
41
+ gutter: root.style.getPropertyValue('scrollbar-gutter'),
42
+ };
43
+ // Only where there was a scrollbar to lose. `stable` on a page that never had one
44
+ // reserves space that was not there, which is the same layout shift in the other
45
+ // direction.
46
+ if (root.scrollHeight > root.clientHeight) {
47
+ root.style.setProperty('scrollbar-gutter', 'stable');
48
+ }
49
+ root.style.overflow = 'hidden';
50
+ }
51
+ /** Gives the page back, once the last dialog has closed. */
52
+ function release() {
53
+ locks -= 1;
54
+ if (locks > 0) {
55
+ return;
56
+ }
57
+ const root = document.documentElement;
58
+ // What the host had, not what this module would have chosen. A page that sets its own
59
+ // `overflow` or reserves its own gutter gets it back rather than being cleared.
60
+ root.style.overflow = displaced.overflow;
61
+ root.style.setProperty('scrollbar-gutter', displaced.gutter);
62
+ }
63
+ /** One of the dialog's slots — named, or the default one when the name is empty. */
64
+ function slot(name = '') {
65
+ return Object.assign(document.createElement('slot'), { name });
66
+ }
67
+ /**
68
+ * The shadow content, which is static all the way down: a `<dialog>` and three slots.
69
+ *
70
+ * Built here rather than queried out of the render root, and the reason is the floors
71
+ * this repository holds. `renderRoot.querySelector('dialog')` is typed `| null`, and that
72
+ * null is unreachable in a component that renders the element unconditionally — an
73
+ * unreachable branch is one no test can cover and no mutant can be killed on, against a
74
+ * coverage floor of 100 and a mutation floor of 100. The honest way to satisfy both is to
75
+ * not create the branch.
76
+ */
77
+ function build() {
78
+ const dialog = document.createElement('dialog');
79
+ dialog.setAttribute('part', 'dialog');
80
+ dialog.append(slot('title'), slot(), slot('actions'));
81
+ return dialog;
82
+ }
83
+ /**
84
+ * A modal dialog.
85
+ *
86
+ * It delegates to a real `<dialog>` and opens it with `showModal()`, which is what makes
87
+ * the top layer, the inert background, the focus trap, <kbd>Esc</kbd> and — the half
88
+ * everyone forgets — **focus returning to whatever opened it** the platform's job rather
89
+ * than this file's. What is written here is the part the platform leaves out: the scroll
90
+ * lock, the accessible name across the shadow boundary, and an exit that can be seen.
91
+ *
92
+ * **This is where RFC 0016 expected Zag to arrive, and it does not.** Zag was adopted for
93
+ * dismissable layers and focus trapping, and its dialog machine implements both over a
94
+ * `<div>` — which means giving up the top layer and taking a JS focus trap in exchange for
95
+ * one the user agent enforces. *The platform owns what the platform is good at* is the
96
+ * older rule and the stronger one here. Zag arrives with the first component the platform
97
+ * gives nothing for; `ARCHITECTURE.md` carries that decision for a consumer, and
98
+ * `ROADMAP.md` names the component.
99
+ *
100
+ * @example
101
+ * ```html
102
+ * <ui-dialog>
103
+ * <h2 slot="title">Delete account</h2>
104
+ * <p>This cannot be undone.</p>
105
+ * <ui-button slot="actions" variant="secondary">Cancel</ui-button>
106
+ * <ui-button slot="actions">Delete</ui-button>
107
+ * </ui-dialog>
108
+ * ```
109
+ */
110
+ export class UiDialog extends LitElement {
111
+ static { this.styles = css `
112
+ /* The dialog is promoted to the top layer, so the host must take no space of its
113
+ own: a <ui-dialog> laid out in the flow would leave a gap on every page where
114
+ the dialog is closed. */
115
+ :host {
116
+ display: contents;
117
+ }
118
+
119
+ /* display is named under [open] and nowhere else, and that is not a style
120
+ choice. The user agent closes a dialog with display: none; a rule here that
121
+ names display unconditionally outranks it, and the closed dialog simply stays
122
+ on the page. The attribute is still there through the exit below, which is what
123
+ lets the transition run at all. */
124
+ dialog[open] {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: calc(${reference('--ui-space')} * 2);
128
+ }
129
+
130
+ dialog {
131
+ /* The user agent's own 2px border and white background are decisions this
132
+ component owns. Nothing is raised underneath: elevation arrives with the
133
+ component that needs to read as lifted, and a panel over a scrim is already
134
+ separated from the page by the scrim. */
135
+ border: none;
136
+ border-radius: ${reference('--ui-radius')};
137
+ padding: calc(${reference('--ui-space')} * 3);
138
+ background: ${reference('--ui-color-surface')};
139
+ color: ${reference('--ui-color-text')};
140
+ font-family: ${reference('--ui-font')};
141
+
142
+ /* A cap is a layout decision rather than a visual one, so it is not a token
143
+ and opens no category. A host that wants another size reaches
144
+ ::part(dialog), which is the surface for what the token set does not name.
145
+
146
+ border-box, and the mutation floor is what found this: a dialog is
147
+ content-box by default, so the cap applied to the content alone and the
148
+ padding was added on top of it — the panel then ran wider than the cap by
149
+ six spacing steps, and the clearance the calc promises was not the
150
+ clearance it left. */
151
+ box-sizing: border-box;
152
+ max-inline-size: min(32rem, calc(100% - ${reference('--ui-space')} * 8));
153
+
154
+ opacity: 1;
155
+ transition: opacity ${reference('--ui-duration-state')}
156
+ ${reference('--ui-easing-enter')};
157
+ }
158
+
159
+ /* Where the entrance comes *from*. Without it there is nothing to transition out
160
+ of: the dialog goes from display: none to laid out in a single frame, and a
161
+ transition needs a before-change style that an unrendered element does not
162
+ have. */
163
+ @starting-style {
164
+ dialog[open] {
165
+ opacity: 0;
166
+ }
167
+ }
168
+
169
+ dialog::backdrop {
170
+ background: ${reference('--ui-color-scrim')};
171
+ opacity: 1;
172
+ transition: opacity ${reference('--ui-duration-state')}
173
+ ${reference('--ui-easing-enter')};
174
+ }
175
+
176
+ @starting-style {
177
+ dialog[open]::backdrop {
178
+ opacity: 0;
179
+ }
180
+ }
181
+
182
+ /* The exit, driven from the class rather than from [open] going away: the
183
+ platform removes a closed dialog in the same frame it is closed, so an exit
184
+ waiting on the attribute would never be seen. Both halves are declared together
185
+ because moving together is what lets #exit await one of them. */
186
+ dialog.closing,
187
+ dialog.closing::backdrop {
188
+ opacity: 0;
189
+ transition-timing-function: ${reference('--ui-easing-exit')};
190
+ }
191
+
192
+ /* showModal() focuses the dialog itself when nothing inside it can take focus,
193
+ and a focused thing with no ring is the most common way a component stops being
194
+ usable by keyboard. */
195
+ dialog:focus-visible {
196
+ outline: 2px solid ${reference('--ui-color-focus')};
197
+ outline-offset: 2px;
198
+ }
199
+
200
+ /* A heading's own margin would add to the padding and to the gap above it. This
201
+ resets the user agent's default and yields to a decision: for slotted content
202
+ the outer tree wins, so a host's own rule for the element still beats this. */
203
+ slot[name='title']::slotted(*) {
204
+ margin: 0;
205
+ }
206
+
207
+ /* A slot is display: contents until something says otherwise, which is what
208
+ makes this the line that turns three buttons into a row. */
209
+ slot[name='actions'] {
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ justify-content: flex-end;
213
+ gap: ${reference('--ui-space')};
214
+ }
215
+ `; }
216
+ static { this.properties = {
217
+ open: { type: Boolean, reflect: true },
218
+ }; }
219
+ /** The real `<dialog>` this component is a shell around. */
220
+ #dialog;
221
+ /**
222
+ * Watches the light DOM for a title that changed without the element changing.
223
+ *
224
+ * Built here rather than on connection so it is never absent: a disconnect that has to
225
+ * ask whether the observer exists is a branch no test can reach.
226
+ */
227
+ #observer;
228
+ constructor() {
229
+ super();
230
+ /**
231
+ * Whether the dialog is showing.
232
+ *
233
+ * **The single source of truth, and the only one.** {@link show} and {@link close} set
234
+ * it, <kbd>Esc</kbd> sets it, and the component reacts to it — so an attribute, a
235
+ * property and a method are three ways of saying one thing rather than three paths
236
+ * that can disagree. Reflected, so a host stylesheet can select on it.
237
+ *
238
+ * A plain field rather than the `accessor` keyword, for the reason `src/button.ts`
239
+ * states beside its own.
240
+ */
241
+ this.open = false;
242
+ /** The real `<dialog>` this component is a shell around. */
243
+ this.#dialog = build();
244
+ /**
245
+ * Watches the light DOM for a title that changed without the element changing.
246
+ *
247
+ * Built here rather than on connection so it is never absent: a disconnect that has to
248
+ * ask whether the observer exists is a branch no test can reach.
249
+ */
250
+ this.#observer = new MutationObserver(() => {
251
+ this.#label();
252
+ });
253
+ this.#dialog.addEventListener('cancel', (event) => {
254
+ // Esc reaches a modal as `cancel`, and letting it through closes in the same
255
+ // frame — the exit this component just gained would never be seen. Prevented
256
+ // and routed back through `open`, so however a close was asked for it leaves
257
+ // by the one door.
258
+ event.preventDefault();
259
+ this.open = false;
260
+ });
261
+ this.#dialog.addEventListener('close', () => {
262
+ // The announcement only. The platform fires `close` as a *queued task* rather
263
+ // than synchronously, so this runs a turn after the dialog actually closed —
264
+ // which is fine for telling a host, and was wrong for giving the page back:
265
+ // the lock outlived the dialog by a task, and a second dialog opening inside
266
+ // that window counted against a page nobody was holding any more. Measured on
267
+ // Node 22 in CI and not on Node 24, which is what a timing dependency looks
268
+ // like from the outside. {@link UiDialog.#shut} owns the release instead, at
269
+ // both of the synchronous points where this element closes the dialog.
270
+ this.open = false;
271
+ this.dispatchEvent(new Event('ui-close', { bubbles: true, composed: true }));
272
+ });
273
+ }
274
+ connectedCallback() {
275
+ super.connectedCallback();
276
+ // The title may be replaced, or its text rewritten in place without any element
277
+ // changing, which is what a host's re-render looks like from here — so
278
+ // characterData matters as much as childList, and both can happen below a wrapper.
279
+ this.#observer.observe(this, { childList: true, characterData: true, subtree: true });
280
+ }
281
+ disconnectedCallback() {
282
+ this.#observer.disconnect();
283
+ // Removing an open dialog takes it out of the top layer and announces nothing, so
284
+ // the page would stay held by a dialog that is no longer anywhere.
285
+ this.#shut();
286
+ super.disconnectedCallback();
287
+ }
288
+ firstUpdated() {
289
+ this.#label();
290
+ }
291
+ updated() {
292
+ // Compared against the platform's own state rather than against Lit's changed
293
+ // properties, and it has to be: on the first update Lit reports every initialised
294
+ // property as changed, so a dialog that has never been open would run the closing
295
+ // path and announce a close that never happened. It is also what makes a re-render
296
+ // for any other reason a no-op instead of a second `showModal()`, which throws.
297
+ if (this.open === this.#dialog.open) {
298
+ return;
299
+ }
300
+ if (this.open) {
301
+ this.#enter();
302
+ }
303
+ else {
304
+ void this.#exit();
305
+ }
306
+ }
307
+ render() {
308
+ return html `${this.#dialog}`;
309
+ }
310
+ /**
311
+ * Opens the dialog.
312
+ *
313
+ * Sugar over `open = true` rather than a second way in — a method that opened by its
314
+ * own route would be a state the attribute does not describe.
315
+ */
316
+ show() {
317
+ this.open = true;
318
+ }
319
+ /** Closes the dialog, running the exit. Sugar over `open = false`, for the same reason. */
320
+ close() {
321
+ this.open = false;
322
+ }
323
+ /**
324
+ * Puts the dialog in the top layer.
325
+ *
326
+ * `showModal()` rather than the `open` attribute, and the distance between the two is
327
+ * the whole component: the attribute opens a dialog *in the flow*, with no top layer,
328
+ * no inert background, no focus trap and no focus restoration. It is the one way to
329
+ * get something that looks modal and traps nobody — which is why this host's `open` is
330
+ * never bound onto the inner element's.
331
+ */
332
+ #enter() {
333
+ this.#dialog.showModal();
334
+ hold();
335
+ }
336
+ /** Runs the exit, and closes once it has finished. */
337
+ async #exit() {
338
+ const dialog = this.#dialog;
339
+ dialog.classList.add(closing);
340
+ // `finished` rejects when an animation is cancelled, which is exactly what
341
+ // reopening mid-exit does — so what this waits for is settlement rather than
342
+ // fulfilment. `getAnimations` flushes the pending style change before it answers,
343
+ // so the transition the line above started is already in the list.
344
+ //
345
+ // The backdrop is not in that list: a pseudo-element needs `subtree: true`,
346
+ // measured. It does not need to be — the panel and the scrim move the same
347
+ // property over the same duration token, so they start together and end together,
348
+ // and awaiting one is awaiting both. `tests/dialog.test.ts` asserts that shared
349
+ // duration rather than leaving it to this paragraph.
350
+ await Promise.allSettled(dialog.getAnimations().map((animation) => animation.finished));
351
+ dialog.classList.remove(closing);
352
+ // Reopened while the exit was running. The dialog never left the top layer, so
353
+ // there is nothing to close, and taking the class back off is the whole of the
354
+ // way back.
355
+ if (this.open) {
356
+ return;
357
+ }
358
+ this.#shut();
359
+ }
360
+ /**
361
+ * Closes the dialog and gives the page back, in that order and in one place.
362
+ *
363
+ * Both ways out run through here — the exit above and the disconnect below — so the
364
+ * release is paired with the close rather than with the event the close will later
365
+ * fire. The guard is the platform's own state rather than a flag this element keeps:
366
+ * a dialog that is not open is holding nothing, so calling this twice gives the page
367
+ * back once.
368
+ */
369
+ #shut() {
370
+ if (!this.#dialog.open) {
371
+ return;
372
+ }
373
+ this.#dialog.close();
374
+ release();
375
+ }
376
+ /**
377
+ * Names the dialog after its own title.
378
+ *
379
+ * **A copy of the text rather than a reference to the element, and that is forced.**
380
+ * APG names a modal with `aria-labelledby` pointing at its heading, and an IDREF does
381
+ * not cross a shadow boundary — so a `<dialog>` in this shadow root cannot point at a
382
+ * heading in the host's tree. `ui-field` answers the same constraint by leaving every
383
+ * associated element in the light DOM; a dialog cannot, because the element that has
384
+ * to reach the top layer is the one this component renders. What does cross is a
385
+ * string, so the name is copied and the observer above keeps the copy honest.
386
+ */
387
+ #label() {
388
+ const title = this.querySelector(':scope > [slot="title"]');
389
+ const label = title?.textContent.trim() ?? '';
390
+ // A dialog with no title has no accessible name, and a blank `aria-label` would be
391
+ // worse than none: a screen reader announces "dialog" either way, and an attribute
392
+ // that is present and empty is the shape that makes an audit read as handled.
393
+ if (label === '') {
394
+ this.#dialog.removeAttribute('aria-label');
395
+ }
396
+ else {
397
+ this.#dialog.setAttribute('aria-label', label);
398
+ }
399
+ }
400
+ }
401
+ // Stryker disable next-line StringLiteral: the registration runs once, at import, inside
402
+ // the warm process Stryker switches mutants in — so by the time a mutant on this line is
403
+ // active the element is already defined under the original name, and no test can observe
404
+ // the change. Outside the runner's reach, not an equivalent mutant.
405
+ customElements.define('ui-dialog', UiDialog);
406
+ //# sourceMappingURL=dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.js","sourceRoot":"","sources":["../src/dialog.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { UiButton, type ButtonVariant } from './button.js';
2
+ export { UiDialog } from './dialog.js';
2
3
  export { UiField } from './field.js';
3
4
  export { tokens, derivedTokens, defaults, formulas, darkScheme, tokenStyleSheet, type Token, type DerivedToken, } from './tokens.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EACf,KAAK,KAAK,EACV,KAAK,YAAY,GACpB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EACf,KAAK,KAAK,EACV,KAAK,YAAY,GACpB,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { UiButton } from './button.js';
2
+ export { UiDialog } from './dialog.js';
2
3
  export { UiField } from './field.js';
3
4
  export { tokens, derivedTokens, defaults, formulas, darkScheme, tokenStyleSheet, } from './tokens.js';
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,GAGlB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,GAGlB,MAAM,aAAa,CAAC"}
package/dist/tokens.d.ts CHANGED
@@ -31,7 +31,7 @@
31
31
  * it does break is code that **enumerates** the set, an exhaustive `Record<Token, string>`
32
32
  * above all. The supported shape is the partial map, which is what a theme is anyway.
33
33
  */
34
- export declare const tokens: readonly ["--ui-color-accent", "--ui-color-accent-contrast", "--ui-color-surface", "--ui-color-text", "--ui-color-focus", "--ui-color-danger", "--ui-radius", "--ui-space", "--ui-font", "--ui-duration-100", "--ui-easing-state"];
34
+ export declare const tokens: readonly ["--ui-color-accent", "--ui-color-accent-contrast", "--ui-color-surface", "--ui-color-text", "--ui-color-focus", "--ui-color-danger", "--ui-color-scrim", "--ui-radius", "--ui-space", "--ui-font", "--ui-duration-100", "--ui-easing-state", "--ui-easing-enter", "--ui-easing-exit"];
35
35
  /** A CSS custom property this package defines and gives a default. */
36
36
  export type Token = (typeof tokens)[number];
37
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,oOAqBT,CAAC;AAEX,sEAAsE;AACtE,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,oIAShB,CAAC;AAEX,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CA6BpD,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAY3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAW/D,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAsBxC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,iSA6CT,CAAC;AAEX,sEAAsE;AACtE,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,oIAShB,CAAC;AAEX,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAuCpD,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAY3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAW/D,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAsBxC"}
package/dist/tokens.js CHANGED
@@ -38,6 +38,19 @@ export const tokens = [
38
38
  '--ui-color-text',
39
39
  '--ui-color-focus',
40
40
  '--ui-color-danger',
41
+ // The dim behind a modal, and a *ground* rather than a derivation even though every
42
+ // other neutral here is derived. A derived neutral mixes toward the text, which is
43
+ // what makes one formula right in both schemes — and it is exactly wrong for this
44
+ // one: on a dark page the text is the light pole, so the mix would *lighten* the
45
+ // page behind the dialog instead of dimming it. A scrim dims in both schemes, so it
46
+ // carries a literal and appears in neither `darkScheme` nor `formulas`.
47
+ //
48
+ // **This departs from RFC 0002**, whose survey of the queued components answered
49
+ // *text at an alpha* for this role. That table asks whether a role needs a new hue,
50
+ // and the answer to that question is still no — what it could not weigh, with no
51
+ // overlay yet written to judge against, is which pole the mix should run toward. The
52
+ // proposal says so in the same breath: read as a judgement, not a measurement.
53
+ '--ui-color-scrim',
41
54
  '--ui-radius',
42
55
  '--ui-space',
43
56
  '--ui-font',
@@ -52,6 +65,17 @@ export const tokens = [
52
65
  // ground and a component reads it directly.
53
66
  '--ui-duration-100',
54
67
  '--ui-easing-state',
68
+ // The pair `--ui-easing-state` promised, arriving with the overlay that has an enter
69
+ // and an exit to name — `ui-dialog`. A state change reverses mid-flight and wants a
70
+ // symmetric curve; an overlay does not reverse, it arrives and it leaves, and the two
71
+ // directions are asked to feel different on purpose.
72
+ //
73
+ // No duration steps come with them. The scale is ordinal with gaps precisely so a step
74
+ // can be inserted when something needs one, and the dialog needs no duration the state
75
+ // step does not already give it — inventing `--ui-duration-200` before a component
76
+ // judges it against something is the claim ROADMAP.md declines to make about Zag.
77
+ '--ui-easing-enter',
78
+ '--ui-easing-exit',
55
79
  ];
56
80
  /**
57
81
  * The roles computed from the grounds rather than declared beside them.
@@ -94,6 +118,11 @@ export const defaults = {
94
118
  // because no axe rule does.
95
119
  '--ui-color-focus': '#b45309',
96
120
  '--ui-color-danger': '#b91c1c',
121
+ // Half black. Enough to push the page behind a modal out of the reading order for the
122
+ // eye as well as for the accessibility tree, and not so much that the context a modal
123
+ // is *about* stops being visible. The alpha is the whole point, so this is the one
124
+ // default that is not an opaque hex.
125
+ '--ui-color-scrim': 'rgb(0 0 0 / 0.5)',
97
126
  '--ui-radius': '0.375rem',
98
127
  '--ui-space': '0.5rem',
99
128
  '--ui-font': 'system-ui, sans-serif',
@@ -108,6 +137,11 @@ export const defaults = {
108
137
  // for. `enter` will want an ease-out and `exit` an ease-in, and they arrive with the
109
138
  // overlays that have an enter and an exit to name.
110
139
  '--ui-easing-state': 'ease-in-out',
140
+ // Fast out of the gate and settling at the end, which is what makes an arriving
141
+ // overlay feel like it was already on its way. The exit is its mirror: slow to let go
142
+ // and quick to be gone, so a dismissal does not linger over a decision already made.
143
+ '--ui-easing-enter': 'ease-out',
144
+ '--ui-easing-exit': 'ease-in',
111
145
  };
112
146
  /**
113
147
  * A ground as it is written inside a formula: the name, with its own default behind it.
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,mBAAmB;IACnB,4BAA4B;IAC5B,oBAAoB;IACpB,iBAAiB;IACjB,kBAAkB;IAClB,mBAAmB;IACnB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,kFAAkF;IAClF,sFAAsF;IACtF,+CAA+C;IAC/C,EAAE;IACF,uFAAuF;IACvF,sFAAsF;IACtF,uFAAuF;IACvF,oFAAoF;IACpF,4CAA4C;IAC5C,mBAAmB;IACnB,mBAAmB;CACb,CAAC;AAKX;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,qBAAqB;IACrB,oFAAoF;IACpF,iFAAiF;IACjF,6EAA6E;IAC7E,kBAAkB;IAClB,oBAAoB;IACpB,yBAAyB;IACzB,2BAA2B;CACrB,CAAC;AAKX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAoC;IACrD,mBAAmB,EAAE,SAAS;IAC9B,4BAA4B,EAAE,SAAS;IACvC,oBAAoB,EAAE,SAAS;IAC/B,iBAAiB,EAAE,SAAS;IAC5B,mFAAmF;IACnF,qFAAqF;IACrF,gFAAgF;IAChF,mFAAmF;IACnF,iFAAiF;IACjF,oFAAoF;IACpF,iFAAiF;IACjF,4BAA4B;IAC5B,kBAAkB,EAAE,SAAS;IAC7B,mBAAmB,EAAE,SAAS;IAC9B,aAAa,EAAE,UAAU;IACzB,YAAY,EAAE,QAAQ;IACtB,WAAW,EAAE,uBAAuB;IACpC,sFAAsF;IACtF,iFAAiF;IACjF,yEAAyE;IACzE,mBAAmB,EAAE,OAAO;IAC5B,kFAAkF;IAClF,qFAAqF;IACrF,4EAA4E;IAC5E,qFAAqF;IACrF,qFAAqF;IACrF,mDAAmD;IACnD,mBAAmB,EAAE,aAAa;CACrC,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,MAAM,CAAC,IAAW;IACvB,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;AAC7C,CAAC;AAED,gFAAgF;AAChF,SAAS,GAAG,CAAC,UAAiB,EAAE,MAAc,EAAE,UAAiB;IAC7D,OAAO,uBAAuB,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;AAClG,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAA2C;IAC5D,oFAAoF;IACpF,oFAAoF;IACpF,qBAAqB,EAAE,MAAM,CAAC,mBAAmB,CAAC;IAClD,qFAAqF;IACrF,sFAAsF;IACtF,uFAAuF;IACvF,qEAAqE;IACrE,kBAAkB,EAAE,GAAG,CAAC,iBAAiB,EAAE,CAAC,EAAE,oBAAoB,CAAC;IACnE,oBAAoB,EAAE,GAAG,CAAC,iBAAiB,EAAE,EAAE,EAAE,oBAAoB,CAAC;IACtE,yBAAyB,EAAE,GAAG,CAAC,iBAAiB,EAAE,EAAE,EAAE,mBAAmB,CAAC;IAC1E,2BAA2B,EAAE,GAAG,CAAC,iBAAiB,EAAE,EAAE,EAAE,mBAAmB,CAAC;CAC/E,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,UAAU,GAA6C;IAChE,qFAAqF;IACrF,iFAAiF;IACjF,oCAAoC;IACpC,mBAAmB,EAAE,SAAS;IAC9B,4BAA4B,EAAE,SAAS;IACvC,oBAAoB,EAAE,SAAS;IAC/B,iBAAiB,EAAE,SAAS;IAC5B,oFAAoF;IACpF,sFAAsF;IACtF,mBAAmB,EAAE,SAAS;CACjC,CAAC;AAEF,uFAAuF;AACvF,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe;IAC3B,MAAM,OAAO,GAAG,MAAM;SACjB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACX,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAE/B,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,GAAG,CAAC;IAC9G,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhB,kFAAkF;IAClF,qFAAqF;IACrF,sFAAsF;IACtF,gFAAgF;IAChF,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,aAAa,CAAC;SAC1C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;SAC7C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,WAAW,CAAC;SACvC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhB,OAAO;QACH,yCAAyC,OAAO,KAAK;QACrD,yDAAyD,SAAS,UAAU;KAC/E,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rak200/ui",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Host-agnostic custom-element UI components for the rak200 ecosystem",
5
5
  "license": "MIT",
6
6
  "author": "rak200 <rak.ricardo@windowslive.com>",