vireuikit 0.6.0 → 0.7.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 +56 -1
- package/dist/{chunk-I3BD3XGC.js → chunk-AR2AM3BR.js} +179 -14
- package/dist/chunk-AR2AM3BR.js.map +1 -0
- package/dist/index.cjs +190 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -4
- package/dist/index.d.ts +165 -4
- package/dist/index.js +1 -1
- package/dist/web.cjs +1087 -21
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.cts +12 -2
- package/dist/web.d.ts +12 -2
- package/dist/web.js +786 -14
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-I3BD3XGC.js.map +0 -1
package/dist/web.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONTROL_IDLE, A11Y_NONE, pressResponse, POINTER_INPUT, createControlVisual, TOUCH_INPUT, CONTROL_SIZES, defaultSize, stepControl, minTarget, hitSlop, settleControlVisual, presentationFor, sameA11y, iconButtonSize, BUTTON_SIZES, visualAtomState, atomMaterial, stepControlVisual, atomBody, resolveA11y } from './chunk-
|
|
1
|
+
import { CONTROL_IDLE, A11Y_NONE, pressResponse, POINTER_INPUT, createControlVisual, TOUCH_INPUT, CONTROL_SIZES, defaultSize, stepControl, minTarget, hitSlop, settleControlVisual, presentationFor, sameA11y, iconButtonSize, BUTTON_SIZES, visualAtomState, atomMaterial, stepControlVisual, atomBody, MENU_CLOSED, createGrowthState, stepMenu, MENU_CORNERS, menuSize, menuPlacement, stepGrowth, growthPhase, menuSurfaceFrame, menuBody, menuMaterial, MENU_ITEM_FILL, resolveA11y } from './chunk-AR2AM3BR.js';
|
|
2
2
|
import { VIREGLASS_CONTROL_MATERIAL } from 'vireglass';
|
|
3
3
|
|
|
4
4
|
// src/web/a11y.ts
|
|
@@ -48,7 +48,7 @@ function effectiveA11y(own) {
|
|
|
48
48
|
|
|
49
49
|
// src/web/glass.ts
|
|
50
50
|
async function attachControlGlass(el, params, stillWanted) {
|
|
51
|
-
const { attachGlass } = await import('vireglass/dom');
|
|
51
|
+
const { attachGlass, probeBackdrop } = await import('vireglass/dom');
|
|
52
52
|
if (!stillWanted()) return null;
|
|
53
53
|
const opts = {
|
|
54
54
|
material: params.material,
|
|
@@ -59,6 +59,7 @@ async function attachControlGlass(el, params, stillWanted) {
|
|
|
59
59
|
// near black under reduced transparency with light ink (docs/acceptance.md, "Resting body").
|
|
60
60
|
body: false
|
|
61
61
|
};
|
|
62
|
+
if (params.interactive === false) opts.interactive = false;
|
|
62
63
|
if (params.sample) opts.sample = params.sample;
|
|
63
64
|
const handle = attachGlass(el, opts);
|
|
64
65
|
return {
|
|
@@ -70,9 +71,16 @@ async function attachControlGlass(el, params, stillWanted) {
|
|
|
70
71
|
opts.geometry = geometry;
|
|
71
72
|
handle.update();
|
|
72
73
|
},
|
|
74
|
+
frame(next) {
|
|
75
|
+
if (next.material) opts.material = next.material;
|
|
76
|
+
if (next.geometry) opts.geometry = next.geometry;
|
|
77
|
+
if (next.morph !== void 0) handle.setMorph(next.morph);
|
|
78
|
+
else handle.update();
|
|
79
|
+
},
|
|
73
80
|
setAppear: (t) => handle.setAppear(t),
|
|
74
81
|
setMorph: (morph) => handle.setMorph(morph),
|
|
75
82
|
setSample: (sample) => handle.setSample(sample),
|
|
83
|
+
probe: () => probeBackdrop(el),
|
|
76
84
|
destroy: () => handle.destroy()
|
|
77
85
|
};
|
|
78
86
|
}
|
|
@@ -93,30 +101,37 @@ function watchCoarsePointer(onChange) {
|
|
|
93
101
|
// src/web/styles.ts
|
|
94
102
|
var HOST = ":is(vire-button,vire-icon-button)[data-vk]";
|
|
95
103
|
var BODY = `${HOST}>button`;
|
|
104
|
+
var MENU = "vire-menu[data-vk]";
|
|
105
|
+
var SURFACE = `${MENU}>[popover]`;
|
|
106
|
+
var ITEM = `${SURFACE}>button`;
|
|
107
|
+
var GLASS_BODY = `background:color-mix(in srgb,color-mix(in srgb,#fff calc((var(--vk-body-white) + var(--vk-body-lift)*2)*100%),var(--vireglass-tint-color,#fff)) calc(var(--vk-body-alpha)*100%),transparent);
|
|
108
|
+
background:oklab(from color-mix(in srgb,#fff calc(var(--vk-body-white)*100%),var(--vireglass-tint-color,#fff)) calc(l + var(--vk-body-lift)) a b / var(--vk-body-alpha))`;
|
|
109
|
+
var FROST_INK = "color:oklab(from var(--vireglass-tint-color,#fff) calc(.95 - .87*clamp(0,(l + var(--vk-body-lift) - .6)*20,1)) 0 0)";
|
|
110
|
+
var CONTRAST_GLASS = "background:color-mix(in srgb,color-mix(in srgb,#fff calc((1 - var(--vireglass-ink,0))*100%),#000) 88%,transparent)";
|
|
111
|
+
var SHADOW = "box-shadow:0 0 0 1px color-mix(in srgb,currentColor 14%,transparent),0 10px 30px color-mix(in srgb,currentColor 18%,transparent)";
|
|
96
112
|
var STYLES = `
|
|
97
|
-
vire-button,vire-icon-button{display:inline-flex;vertical-align:middle}
|
|
98
|
-
${HOST}{position:relative;--vk-press:0;--vk-hover:0;--vk-body-alpha:0;--vk-body-white:0;--vk-body-lift:0;--vk-grow:0;--vk-defocus:0;--vk-slop-x:0;--vk-slop-y:0}
|
|
113
|
+
vire-button,vire-icon-button,vire-menu{display:inline-flex;vertical-align:middle}
|
|
114
|
+
${HOST}{position:relative;--vk-press:0;--vk-hover:0;--vk-carry:1;--vk-body-alpha:0;--vk-body-white:0;--vk-body-lift:0;--vk-grow:0;--vk-defocus:0;--vk-slop-x:0;--vk-slop-y:0}
|
|
99
115
|
${HOST}::before{content:'';position:absolute;inset:calc(var(--vk-slop-y)*-1px) calc(var(--vk-slop-x)*-1px)}
|
|
100
116
|
${BODY}{appearance:none;-webkit-appearance:none;box-sizing:border-box;margin:0;border:0;outline:none;
|
|
101
117
|
font:inherit;font-weight:500;line-height:1;white-space:nowrap;color:inherit;
|
|
102
118
|
display:inline-flex;align-items:center;justify-content:center;gap:8px;position:relative;
|
|
103
119
|
height:var(--vk-height);min-width:var(--vk-height);padding:0 var(--vk-pad-x);border-radius:var(--vk-radius);
|
|
104
|
-
-webkit-text-fill-color:color-mix(in srgb,currentColor calc((1 - var(--vk-press)*.55)*100%),transparent);
|
|
105
|
-
text-shadow:0 0 calc(var(--vk-press)*var(--vk-defocus)*1px) currentColor;
|
|
120
|
+
-webkit-text-fill-color:color-mix(in srgb,currentColor calc((1 - var(--vk-press)*.55)*var(--vk-carry)*100%),transparent);
|
|
121
|
+
text-shadow:0 0 calc((var(--vk-press)*var(--vk-defocus) + (1 - var(--vk-carry))*4)*1px) color-mix(in srgb,currentColor calc(var(--vk-carry)*100%),transparent);
|
|
106
122
|
background:transparent;cursor:pointer;touch-action:manipulation;user-select:none;-webkit-tap-highlight-color:transparent;
|
|
107
123
|
transform:scale(calc(1 + var(--vk-press)*var(--vk-grow)))}
|
|
108
124
|
vire-icon-button[data-vk]>button{padding:0;width:var(--vk-height)}
|
|
109
125
|
${BODY}>svg{width:var(--vk-icon);height:var(--vk-icon);flex:none;
|
|
110
|
-
opacity:calc(1 - var(--vk-press)*.45);filter:blur(calc(var(--vk-press)*var(--vk-defocus)*1px))}
|
|
126
|
+
opacity:calc((1 - var(--vk-press)*.45)*var(--vk-carry));filter:blur(calc((var(--vk-press)*var(--vk-defocus) + (1 - var(--vk-carry))*4)*1px))}
|
|
111
127
|
${BODY}:disabled{cursor:default}
|
|
112
128
|
${HOST}[data-disabled]>button{-webkit-text-fill-color:color-mix(in srgb,currentColor 40%,transparent)}
|
|
113
129
|
${HOST}[data-disabled]>button>svg{opacity:.4}
|
|
114
130
|
${HOST}[data-focus]>button{outline:2px solid currentColor;outline-offset:2px}
|
|
115
131
|
|
|
116
132
|
${HOST}[data-surface=glass]>button{color:var(--vireglass-ink-color,inherit);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
${HOST}[data-frost][data-surface=glass]>button{color:oklab(from var(--vireglass-tint-color,#fff) calc(.95 - .87*clamp(0,(l + var(--vk-body-lift) - .6)*20,1)) 0 0)}
|
|
133
|
+
${GLASS_BODY}}
|
|
134
|
+
${HOST}[data-frost][data-surface=glass]>button{${FROST_INK}}
|
|
120
135
|
|
|
121
136
|
${HOST}[data-surface=plain]>button{background:color-mix(in srgb,currentColor calc(10% + var(--vk-hover)*6% + var(--vk-press)*14%),transparent)}
|
|
122
137
|
|
|
@@ -125,19 +140,64 @@ ${HOST}[data-surface=frosted]>button{
|
|
|
125
140
|
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
|
126
141
|
box-shadow:0 1px 3px color-mix(in srgb,currentColor 22%,transparent)}
|
|
127
142
|
|
|
143
|
+
${MENU}{position:relative;--vk-ink-in:1;--vk-cx:0px;--vk-cy:0px}
|
|
144
|
+
${SURFACE}{position:fixed;inset:auto;left:var(--vk-x,0);top:var(--vk-y,0);width:var(--vk-w,auto);height:var(--vk-h,auto);
|
|
145
|
+
margin:0;padding:var(--vk-menu-pad);border:0;border-radius:var(--vk-r,var(--vk-menu-radius));box-sizing:border-box;
|
|
146
|
+
overflow:clip;color:inherit;font:inherit;background:transparent;outline:none;
|
|
147
|
+
--vk-body-alpha:0;--vk-body-white:0;--vk-body-lift:0}
|
|
148
|
+
${SURFACE}:popover-open{display:flex;flex-direction:column;align-items:flex-start}
|
|
149
|
+
${MENU}[data-phase=open]>[popover][data-scroll]{overflow-y:auto}
|
|
150
|
+
${ITEM}{appearance:none;-webkit-appearance:none;box-sizing:border-box;margin:0;border:0;outline:none;flex:none;position:relative;
|
|
151
|
+
font:inherit;line-height:1.2;color:inherit;text-align:start;white-space:nowrap;
|
|
152
|
+
display:flex;align-items:center;width:var(--vk-content-w,auto);height:var(--vk-menu-row);
|
|
153
|
+
padding-block:0;padding-inline:var(--vk-label-pad) var(--vk-menu-row);border-radius:var(--vk-item-radius);
|
|
154
|
+
background:color-mix(in srgb,currentColor calc(var(--vk-fill,0)*100%),transparent);
|
|
155
|
+
cursor:pointer;user-select:none;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
|
|
156
|
+
${SURFACE}:has(>button>svg)>button{padding-inline-start:var(--vk-menu-row)}
|
|
157
|
+
${ITEM}>svg{position:absolute;inset-inline-start:calc((var(--vk-menu-row) - var(--vk-menu-icon))/2);top:calc((var(--vk-menu-row) - var(--vk-menu-icon))/2);
|
|
158
|
+
width:var(--vk-menu-icon);height:var(--vk-menu-icon)}
|
|
159
|
+
${ITEM}:is(:focus,[data-vire-force-state=hover]){--vk-fill:var(--vk-fill-active)}
|
|
160
|
+
${ITEM}:is([data-pressed],[data-vire-force-state=pressed]){--vk-fill:var(--vk-fill-pressed)}
|
|
161
|
+
${ITEM}:focus-visible{outline:2px solid currentColor;outline-offset:-2px}
|
|
162
|
+
${ITEM}:is(:disabled,[aria-disabled=true]){cursor:default;--vk-fill:0;-webkit-text-fill-color:color-mix(in srgb,currentColor 40%,transparent)}
|
|
163
|
+
${ITEM}:is(:disabled,[aria-disabled=true])>svg{opacity:.4}
|
|
164
|
+
${MENU}[data-phase=moving]>[popover]{padding:0}
|
|
165
|
+
${MENU}[data-phase=moving]>[popover]>*{transform:translate(calc(var(--vk-cx) + var(--vk-menu-pad)),calc(var(--vk-cy) + var(--vk-menu-pad)));
|
|
166
|
+
opacity:min(1,calc(var(--vk-ink-in)*2));filter:blur(calc((1 - var(--vk-ink-in))*8px))}
|
|
167
|
+
${MENU}[data-measure]>[popover]{width:max-content!important;height:auto!important;left:0!important;top:0!important;padding:var(--vk-menu-pad)!important;visibility:hidden}
|
|
168
|
+
${MENU}[data-measure]>[popover]>*{width:auto!important;transform:none!important}
|
|
169
|
+
|
|
170
|
+
${MENU}[data-surface=glass]>[popover]{color:var(--vireglass-ink-color,inherit);
|
|
171
|
+
${GLASS_BODY}}
|
|
172
|
+
${MENU}[data-frost][data-surface=glass]>[popover]{${FROST_INK}}
|
|
173
|
+
${MENU}[data-surface=plain]>[popover]{background:Canvas;${SHADOW}}
|
|
174
|
+
${MENU}[data-surface=frosted]>[popover]{background:color-mix(in srgb,Canvas 88%,currentColor);
|
|
175
|
+
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);${SHADOW}}
|
|
176
|
+
|
|
128
177
|
@supports not (color:oklab(from red l a b)){
|
|
129
|
-
${HOST}[data-frost][data-surface=glass]>button{color:inherit;background:color-mix(in srgb,Canvas 82%,currentColor)}
|
|
178
|
+
${HOST}[data-frost][data-surface=glass]>button,${MENU}[data-frost][data-surface=glass]>[popover]{color:inherit;background:color-mix(in srgb,Canvas 82%,currentColor)}
|
|
130
179
|
}
|
|
131
180
|
|
|
132
181
|
${HOST}[data-contrast]:is([data-surface=plain],[data-surface=frosted])>button{box-shadow:inset 0 0 0 2px currentColor}
|
|
133
182
|
${HOST}[data-contrast][data-surface=frosted]>button{background:Canvas}
|
|
134
|
-
${HOST}[data-contrast][data-surface=glass]>button{
|
|
183
|
+
${HOST}[data-contrast][data-surface=glass]>button{${CONTRAST_GLASS}}
|
|
184
|
+
${MENU}[data-contrast]:is([data-surface=plain],[data-surface=frosted])>[popover]{background:Canvas;box-shadow:inset 0 0 0 2px currentColor}
|
|
185
|
+
${MENU}[data-contrast][data-surface=glass]>[popover]{${CONTRAST_GLASS}}
|
|
186
|
+
|
|
187
|
+
${HOST}[data-carried]>button{background:transparent!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;box-shadow:none!important;pointer-events:none}
|
|
188
|
+
${HOST}[data-carried]>button::after{visibility:hidden}
|
|
135
189
|
|
|
136
190
|
@media (forced-colors:active){
|
|
137
191
|
${BODY}{-webkit-text-fill-color:currentColor;text-shadow:none;color:ButtonText;background:ButtonFace;border:1px solid ButtonText;backdrop-filter:none;-webkit-backdrop-filter:none;transform:none}
|
|
138
192
|
${BODY}>svg{opacity:1;filter:none}
|
|
139
193
|
${HOST}[data-disabled]>button{-webkit-text-fill-color:currentColor;color:GrayText;border-color:GrayText}
|
|
140
194
|
${HOST}[data-focus]>button{outline-color:Highlight}
|
|
195
|
+
${SURFACE}{color:CanvasText!important;background:Canvas!important;border:1px solid CanvasText;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
|
|
196
|
+
${SURFACE}>*{filter:none!important;opacity:1!important}
|
|
197
|
+
${ITEM}{-webkit-text-fill-color:currentColor!important;color:CanvasText;background:Canvas}
|
|
198
|
+
${ITEM}:is(:focus,[data-vire-force-state=hover],[data-pressed]){color:HighlightText;background:Highlight}
|
|
199
|
+
${ITEM}:is(:disabled,[aria-disabled=true]){color:GrayText;background:Canvas}
|
|
200
|
+
${HOST}[data-carried]>button{opacity:var(--vk-carry)}
|
|
141
201
|
}
|
|
142
202
|
`;
|
|
143
203
|
var STYLE_ATTR = "data-vireuikit";
|
|
@@ -187,6 +247,8 @@ function createControlElementClass(kind, options = {}) {
|
|
|
187
247
|
#slop = { x: 0, y: 0 };
|
|
188
248
|
#morph = null;
|
|
189
249
|
#sample = null;
|
|
250
|
+
#carried = null;
|
|
251
|
+
#hadStyle = { host: false, control: false };
|
|
190
252
|
get a11y() {
|
|
191
253
|
return this.#own;
|
|
192
254
|
}
|
|
@@ -205,6 +267,14 @@ function createControlElementClass(kind, options = {}) {
|
|
|
205
267
|
this.#morph = morph;
|
|
206
268
|
this.#glass?.setMorph(morph);
|
|
207
269
|
}
|
|
270
|
+
setCarried(ink) {
|
|
271
|
+
const handedOver = this.#carried === null !== (ink === null);
|
|
272
|
+
this.#carried = ink === null ? null : Math.min(Math.max(ink, 0), 1);
|
|
273
|
+
this.toggleAttribute("data-carried", this.#carried !== null);
|
|
274
|
+
if (this.#carried === null) this.style.removeProperty("--vk-carry");
|
|
275
|
+
else this.style.setProperty("--vk-carry", this.#carried.toFixed(3));
|
|
276
|
+
if (handedOver) this.#glass?.setAppear(this.#carried === null && this.#glassMode() !== "touch" ? 1 : 0);
|
|
277
|
+
}
|
|
208
278
|
connectedCallback() {
|
|
209
279
|
for (const key of ["a11y", "sample"]) {
|
|
210
280
|
if (!Object.prototype.hasOwnProperty.call(this, key)) continue;
|
|
@@ -212,6 +282,7 @@ function createControlElementClass(kind, options = {}) {
|
|
|
212
282
|
Reflect.deleteProperty(this, key);
|
|
213
283
|
Reflect.set(this, key, value);
|
|
214
284
|
}
|
|
285
|
+
this.#hadStyle.host = this.hasAttribute("style");
|
|
215
286
|
ensureStyles(this, options.nonce);
|
|
216
287
|
this.#cleanups.push(watchCoarsePointer(() => this.#onInputChange()));
|
|
217
288
|
this.#cleanups.push(watchSystemA11y(() => this.#refreshMode()));
|
|
@@ -224,11 +295,14 @@ function createControlElementClass(kind, options = {}) {
|
|
|
224
295
|
clearTimeout(this.#suppressTimer);
|
|
225
296
|
this.#frame = 0;
|
|
226
297
|
this.#dropGlass();
|
|
298
|
+
if (this.#control && !this.#hadStyle.control && this.#control.getAttribute("style") === "") this.#control.removeAttribute("style");
|
|
227
299
|
this.#control = null;
|
|
228
300
|
this.#state = CONTROL_IDLE;
|
|
229
301
|
this.#suppressClick = false;
|
|
230
|
-
|
|
302
|
+
this.#carried = null;
|
|
303
|
+
for (const name of ["data-vk", "data-surface", "data-glass", "data-frost", "data-size", "data-carried"]) this.removeAttribute(name);
|
|
231
304
|
for (const name of Array.from(this.style)) if (name.startsWith("--vk-")) this.style.removeProperty(name);
|
|
305
|
+
if (!this.#hadStyle.host && this.getAttribute("style") === "") this.removeAttribute("style");
|
|
232
306
|
this.#presentation = null;
|
|
233
307
|
this.#mode = null;
|
|
234
308
|
}
|
|
@@ -263,6 +337,7 @@ function createControlElementClass(kind, options = {}) {
|
|
|
263
337
|
}
|
|
264
338
|
#upgrade(control) {
|
|
265
339
|
this.#control = control;
|
|
340
|
+
this.#hadStyle.control = control.hasAttribute("style");
|
|
266
341
|
this.setAttribute("data-vk", "");
|
|
267
342
|
this.#caps = isCoarsePointer() ? TOUCH_INPUT : POINTER_INPUT;
|
|
268
343
|
this.#listen(control);
|
|
@@ -490,6 +565,7 @@ function createControlElementClass(kind, options = {}) {
|
|
|
490
565
|
if (!session) return;
|
|
491
566
|
this.#glass = session;
|
|
492
567
|
if (this.#morph) session.setMorph(this.#morph);
|
|
568
|
+
if (this.#carried !== null) session.setAppear(0);
|
|
493
569
|
if (this.#sample !== sample) session.setSample(this.#sample);
|
|
494
570
|
this.#pushMaterial(true);
|
|
495
571
|
this.dispatchEvent(new CustomEvent("vire-glass-ready"));
|
|
@@ -546,10 +622,705 @@ function createControlElementClass(kind, options = {}) {
|
|
|
546
622
|
}
|
|
547
623
|
};
|
|
548
624
|
}
|
|
625
|
+
var FORCE_ATTRIBUTE2 = "data-vire-force-state";
|
|
626
|
+
var FORCED2 = {
|
|
627
|
+
closed: null,
|
|
628
|
+
growing: { shrink: 1, grow: 0.3 },
|
|
629
|
+
open: { shrink: 1, grow: 1 },
|
|
630
|
+
retracting: { shrink: 0.55, grow: 0 }
|
|
631
|
+
};
|
|
632
|
+
var FORCED_PHASE = /^\d*\.?\d+ \d*\.?\d+$/;
|
|
633
|
+
var GLASS_ON = /* @__PURE__ */ new Set(["", "present", "touch"]);
|
|
634
|
+
var MATERIAL_STEPS2 = 10;
|
|
635
|
+
var VIEWPORT_MARGIN = 8;
|
|
636
|
+
var TRIGGER = ":scope > button, :scope > vire-button > button, :scope > vire-icon-button > button";
|
|
637
|
+
var ACTIVATION_KEYS = /* @__PURE__ */ new Set(["Enter", " "]);
|
|
638
|
+
var ids = 0;
|
|
639
|
+
var nativelyDisabled = (item) => item.hasAttribute("disabled");
|
|
640
|
+
var isDisabled = (item) => nativelyDisabled(item) || item.getAttribute("aria-disabled") === "true";
|
|
641
|
+
function rootsOf(el) {
|
|
642
|
+
const roots = [];
|
|
643
|
+
let node = el.getRootNode();
|
|
644
|
+
while (node instanceof ShadowRoot) {
|
|
645
|
+
roots.push(node);
|
|
646
|
+
node = node.host.getRootNode();
|
|
647
|
+
}
|
|
648
|
+
roots.push(el.ownerDocument);
|
|
649
|
+
return roots;
|
|
650
|
+
}
|
|
651
|
+
function dropEmptyStyle(el, hadStyle) {
|
|
652
|
+
if (!hadStyle && el.getAttribute("style") === "") el.removeAttribute("style");
|
|
653
|
+
}
|
|
654
|
+
function createMenuElementClass(options = {}) {
|
|
655
|
+
return class VireMenu extends HTMLElement {
|
|
656
|
+
static observedAttributes = ["glass", "size", "corner", FORCE_ATTRIBUTE2];
|
|
657
|
+
#trigger = null;
|
|
658
|
+
#carrier = null;
|
|
659
|
+
#list = null;
|
|
660
|
+
#restore = [];
|
|
661
|
+
#itemRestore = /* @__PURE__ */ new Map();
|
|
662
|
+
#hadStyle = { host: false, list: false };
|
|
663
|
+
#cleanups = [];
|
|
664
|
+
#state = MENU_CLOSED;
|
|
665
|
+
#growth = createGrowthState();
|
|
666
|
+
#from = null;
|
|
667
|
+
#to = null;
|
|
668
|
+
#anchor = null;
|
|
669
|
+
#natural = null;
|
|
670
|
+
#out = false;
|
|
671
|
+
#viaPointer = false;
|
|
672
|
+
#frame = 0;
|
|
673
|
+
#placeFrame = 0;
|
|
674
|
+
#inertTimer = 0;
|
|
675
|
+
#lastFrame = 0;
|
|
676
|
+
/** When the content was last fully in focus; null while it is still arriving or leaving. */
|
|
677
|
+
#readySince = null;
|
|
678
|
+
/** When the content came into focus, which is when a deliberate key may choose. */
|
|
679
|
+
#inSince = null;
|
|
680
|
+
#downAt = Number.NEGATIVE_INFINITY;
|
|
681
|
+
#own = {};
|
|
682
|
+
#mode = null;
|
|
683
|
+
#presentation = null;
|
|
684
|
+
#glass = null;
|
|
685
|
+
#glassToken = 0;
|
|
686
|
+
#glassKey = "";
|
|
687
|
+
#glassBroken = false;
|
|
688
|
+
#lastLens = { material: "", morph: "", geometry: "" };
|
|
689
|
+
#sample = null;
|
|
690
|
+
#held = null;
|
|
691
|
+
get a11y() {
|
|
692
|
+
return this.#own;
|
|
693
|
+
}
|
|
694
|
+
set a11y(next) {
|
|
695
|
+
this.#own = { ...next };
|
|
696
|
+
if (this.#list) this.#refreshMode();
|
|
697
|
+
}
|
|
698
|
+
get sample() {
|
|
699
|
+
return this.#sample;
|
|
700
|
+
}
|
|
701
|
+
set sample(next) {
|
|
702
|
+
this.#sample = next;
|
|
703
|
+
if (!this.#glass) return;
|
|
704
|
+
if (this.#out) this.#holdSample();
|
|
705
|
+
else this.#glass.setSample(next);
|
|
706
|
+
}
|
|
707
|
+
get open() {
|
|
708
|
+
return this.#state.open;
|
|
709
|
+
}
|
|
710
|
+
connectedCallback() {
|
|
711
|
+
for (const key of ["a11y", "sample"]) {
|
|
712
|
+
if (!Object.prototype.hasOwnProperty.call(this, key)) continue;
|
|
713
|
+
const value = Reflect.get(this, key);
|
|
714
|
+
Reflect.deleteProperty(this, key);
|
|
715
|
+
Reflect.set(this, key, value);
|
|
716
|
+
}
|
|
717
|
+
this.#hadStyle.host = this.hasAttribute("style");
|
|
718
|
+
ensureStyles(this, options.nonce);
|
|
719
|
+
this.#cleanups.push(watchCoarsePointer(() => this.#list && this.#refreshMode(true)));
|
|
720
|
+
this.#cleanups.push(watchSystemA11y(() => this.#list && this.#refreshMode()));
|
|
721
|
+
this.#cleanups.push(watchA11yOverride(() => this.#list && this.#refreshMode()));
|
|
722
|
+
this.#awaitParts();
|
|
723
|
+
}
|
|
724
|
+
disconnectedCallback() {
|
|
725
|
+
for (const cleanup of this.#cleanups.splice(0)) cleanup();
|
|
726
|
+
cancelAnimationFrame(this.#frame);
|
|
727
|
+
cancelAnimationFrame(this.#placeFrame);
|
|
728
|
+
clearTimeout(this.#inertTimer);
|
|
729
|
+
this.#frame = this.#placeFrame = this.#inertTimer = 0;
|
|
730
|
+
this.#dropGlass();
|
|
731
|
+
const list = this.#list;
|
|
732
|
+
if (list) {
|
|
733
|
+
if (list.matches(":popover-open")) list.hidePopover();
|
|
734
|
+
list.inert = false;
|
|
735
|
+
list.removeAttribute("data-scroll");
|
|
736
|
+
for (const name of Array.from(list.style)) if (name.startsWith("--vk-")) list.style.removeProperty(name);
|
|
737
|
+
for (const item of this.#items()) item.removeAttribute("data-pressed");
|
|
738
|
+
}
|
|
739
|
+
this.#carrier?.setCarried?.(null);
|
|
740
|
+
for (const restore of this.#itemRestore.values()) restore();
|
|
741
|
+
this.#itemRestore.clear();
|
|
742
|
+
for (const restore of this.#restore.splice(0).reverse()) restore();
|
|
743
|
+
if (list) dropEmptyStyle(list, this.#hadStyle.list);
|
|
744
|
+
this.#trigger = null;
|
|
745
|
+
this.#carrier = null;
|
|
746
|
+
this.#list = null;
|
|
747
|
+
this.#state = MENU_CLOSED;
|
|
748
|
+
this.#growth = createGrowthState();
|
|
749
|
+
this.#out = false;
|
|
750
|
+
this.#from = this.#to = this.#anchor = null;
|
|
751
|
+
this.#natural = null;
|
|
752
|
+
this.#held = null;
|
|
753
|
+
this.#readySince = null;
|
|
754
|
+
this.#inSince = null;
|
|
755
|
+
for (const name of ["data-vk", "data-surface", "data-glass", "data-frost", "data-contrast", "data-size", "data-phase", "data-measure"]) {
|
|
756
|
+
this.removeAttribute(name);
|
|
757
|
+
}
|
|
758
|
+
for (const name of Array.from(this.style)) if (name.startsWith("--vk-")) this.style.removeProperty(name);
|
|
759
|
+
dropEmptyStyle(this, this.#hadStyle.host);
|
|
760
|
+
this.#presentation = null;
|
|
761
|
+
this.#mode = null;
|
|
762
|
+
}
|
|
763
|
+
attributeChangedCallback(name) {
|
|
764
|
+
if (!this.#list) return;
|
|
765
|
+
if (name === FORCE_ATTRIBUTE2) this.#applyForced();
|
|
766
|
+
else if (name === "corner") this.#out && this.#place();
|
|
767
|
+
else this.#refreshMode(true);
|
|
768
|
+
}
|
|
769
|
+
// The parser connects the element before its children exist, so the parts are waited for, not made up.
|
|
770
|
+
#awaitParts() {
|
|
771
|
+
if (this.#tryUpgrade()) return;
|
|
772
|
+
const observer = new MutationObserver(() => {
|
|
773
|
+
if (this.#tryUpgrade()) observer.disconnect();
|
|
774
|
+
});
|
|
775
|
+
observer.observe(this, { childList: true, subtree: true });
|
|
776
|
+
this.#cleanups.push(() => observer.disconnect());
|
|
777
|
+
}
|
|
778
|
+
#tryUpgrade() {
|
|
779
|
+
const list = this.querySelector(":scope > [popover]");
|
|
780
|
+
const trigger = this.querySelector(TRIGGER);
|
|
781
|
+
if (!list || !trigger) return false;
|
|
782
|
+
this.#upgrade(trigger, list);
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
#keep(el, name, value, into = this.#restore) {
|
|
786
|
+
const was = el.getAttribute(name);
|
|
787
|
+
if (value === null) el.removeAttribute(name);
|
|
788
|
+
else el.setAttribute(name, value);
|
|
789
|
+
into.push(() => was === null ? el.removeAttribute(name) : el.setAttribute(name, was));
|
|
790
|
+
}
|
|
791
|
+
#upgrade(trigger, list) {
|
|
792
|
+
this.#trigger = trigger;
|
|
793
|
+
this.#list = list;
|
|
794
|
+
this.#hadStyle.list = list.hasAttribute("style");
|
|
795
|
+
const parent = trigger.parentElement;
|
|
796
|
+
this.#carrier = parent && parent !== this ? parent : null;
|
|
797
|
+
const wasOpen = list.matches(":popover-open");
|
|
798
|
+
this.setAttribute("data-vk", "");
|
|
799
|
+
ids += 1;
|
|
800
|
+
if (!list.id) this.#keep(list, "id", `vire-menu-${ids}`);
|
|
801
|
+
if (!trigger.id) this.#keep(trigger, "id", `vire-menu-trigger-${ids}`);
|
|
802
|
+
this.#keep(trigger, "popovertarget", null);
|
|
803
|
+
this.#keep(trigger, "popovertargetaction", null);
|
|
804
|
+
this.#keep(trigger, "aria-haspopup", "menu");
|
|
805
|
+
this.#keep(trigger, "aria-expanded", "false");
|
|
806
|
+
this.#keep(trigger, "aria-controls", list.id);
|
|
807
|
+
this.#keep(list, "popover", "manual");
|
|
808
|
+
this.#keep(list, "role", "menu");
|
|
809
|
+
this.#keep(list, "tabindex", "-1");
|
|
810
|
+
this.#keep(list, "aria-labelledby", trigger.id);
|
|
811
|
+
this.#adoptItems();
|
|
812
|
+
const items = new MutationObserver((records) => {
|
|
813
|
+
let changed = false;
|
|
814
|
+
for (const record of records) {
|
|
815
|
+
if (record.type === "childList" && record.target === list) {
|
|
816
|
+
changed = true;
|
|
817
|
+
for (const node of record.removedNodes) this.#releaseItem(node);
|
|
818
|
+
} else if (record.type === "attributes") {
|
|
819
|
+
this.#keepFocusOff(record.target);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!changed) return;
|
|
823
|
+
this.#adoptItems();
|
|
824
|
+
if (this.#out) {
|
|
825
|
+
this.#natural = null;
|
|
826
|
+
this.#place();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
items.observe(list, { childList: true, subtree: true, attributes: true, attributeFilter: ["disabled"] });
|
|
830
|
+
this.#cleanups.push(() => items.disconnect());
|
|
831
|
+
this.#listen(trigger, list);
|
|
832
|
+
this.#mode = null;
|
|
833
|
+
this.#refreshMode();
|
|
834
|
+
if (this.#forced()) this.#applyForced();
|
|
835
|
+
else if (wasOpen) this.#show("menu");
|
|
836
|
+
}
|
|
837
|
+
#adoptItems() {
|
|
838
|
+
for (const item of this.#items()) {
|
|
839
|
+
if (this.#itemRestore.has(item)) continue;
|
|
840
|
+
const restore = [];
|
|
841
|
+
this.#keep(item, "popovertarget", null, restore);
|
|
842
|
+
this.#keep(item, "popovertargetaction", null, restore);
|
|
843
|
+
this.#keep(item, "role", "menuitem", restore);
|
|
844
|
+
this.#keep(item, "tabindex", "-1", restore);
|
|
845
|
+
this.#itemRestore.set(item, () => {
|
|
846
|
+
for (const undo of restore.reverse()) undo();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/** A focused item that turns natively disabled loses focus to the page; the menu keeps it instead. */
|
|
851
|
+
#keepFocusOff(item) {
|
|
852
|
+
const list = this.#list;
|
|
853
|
+
if (!list || !this.#state.open || item.parentElement !== list || !nativelyDisabled(item)) return;
|
|
854
|
+
const active = list.getRootNode().activeElement;
|
|
855
|
+
if (active !== item && list.contains(active)) return;
|
|
856
|
+
this.#state = { ...this.#state, active: -1 };
|
|
857
|
+
list.focus({ preventScroll: true });
|
|
858
|
+
}
|
|
859
|
+
#releaseItem(node) {
|
|
860
|
+
const restore = this.#itemRestore.get(node);
|
|
861
|
+
if (!restore) return;
|
|
862
|
+
this.#itemRestore.delete(node);
|
|
863
|
+
restore();
|
|
864
|
+
}
|
|
865
|
+
#items() {
|
|
866
|
+
return this.#list ? Array.from(this.#list.querySelectorAll(":scope > button")) : [];
|
|
867
|
+
}
|
|
868
|
+
#model() {
|
|
869
|
+
return this.#items().map((item) => ({
|
|
870
|
+
label: item.getAttribute("aria-label") ?? item.textContent ?? "",
|
|
871
|
+
disabled: isDisabled(item),
|
|
872
|
+
focusable: !nativelyDisabled(item)
|
|
873
|
+
}));
|
|
874
|
+
}
|
|
875
|
+
#listen(trigger, list) {
|
|
876
|
+
const on = (target, type, handler, capture = false) => {
|
|
877
|
+
target.addEventListener(type, handler, capture);
|
|
878
|
+
this.#cleanups.push(() => target.removeEventListener(type, handler, capture));
|
|
879
|
+
};
|
|
880
|
+
const itemOf = (target) => {
|
|
881
|
+
const item = target instanceof Element ? target.closest("button") : null;
|
|
882
|
+
return item && item.parentElement === list ? item : null;
|
|
883
|
+
};
|
|
884
|
+
const drop = (e) => {
|
|
885
|
+
e.preventDefault();
|
|
886
|
+
e.stopImmediatePropagation();
|
|
887
|
+
};
|
|
888
|
+
on(this.#carrier ?? trigger, "pointerdown", () => this.#viaPointer = true);
|
|
889
|
+
on(trigger, "keydown", (e) => {
|
|
890
|
+
this.#viaPointer = false;
|
|
891
|
+
if (this.#forced()) return;
|
|
892
|
+
if (e.repeat && ACTIVATION_KEYS.has(e.key)) {
|
|
893
|
+
e.preventDefault();
|
|
894
|
+
} else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
895
|
+
e.preventDefault();
|
|
896
|
+
this.#show(e.key === "ArrowDown" ? "first" : "last");
|
|
897
|
+
} else if (e.key === "Escape" && this.#state.open) {
|
|
898
|
+
e.preventDefault();
|
|
899
|
+
this.#close({ type: "dismiss", reason: "escape" });
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
on(trigger, "click", (e) => {
|
|
903
|
+
if (this.#forced()) return;
|
|
904
|
+
const pointer = e.detail > 0 || this.#viaPointer;
|
|
905
|
+
const early = e.detail > 1 || pointer && this.#readySince === null;
|
|
906
|
+
if (this.#state.open && !early) this.#close({ type: "dismiss", reason: "trigger" });
|
|
907
|
+
else if (!this.#state.open) this.#show(this.#viaPointer ? "menu" : "first");
|
|
908
|
+
this.#viaPointer = false;
|
|
909
|
+
});
|
|
910
|
+
on(list, "keydown", (e) => {
|
|
911
|
+
if (!this.#state.open || this.#forced()) return;
|
|
912
|
+
const move = { ArrowDown: "next", ArrowUp: "previous", Home: "first", End: "last" };
|
|
913
|
+
if (e.repeat && ACTIVATION_KEYS.has(e.key)) {
|
|
914
|
+
e.preventDefault();
|
|
915
|
+
} else if (e.key in move) {
|
|
916
|
+
e.preventDefault();
|
|
917
|
+
this.#step({ type: "move", to: move[e.key] });
|
|
918
|
+
} else if (e.key === "Escape") {
|
|
919
|
+
e.preventDefault();
|
|
920
|
+
this.#close({ type: "dismiss", reason: "escape" });
|
|
921
|
+
} else if (e.key === "Tab") {
|
|
922
|
+
this.#close({ type: "dismiss", reason: "tab" });
|
|
923
|
+
} else if (e.key.length === 1 && e.key !== " " && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
924
|
+
this.#step({ type: "type", key: e.key, at: e.timeStamp / 1e3 });
|
|
925
|
+
} else if (ACTIVATION_KEYS.has(e.key)) {
|
|
926
|
+
const item = itemOf(e.target);
|
|
927
|
+
if (item && !isDisabled(item)) item.setAttribute("data-pressed", "");
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
on(list, "keyup", (e) => itemOf(e.target)?.removeAttribute("data-pressed"));
|
|
931
|
+
on(
|
|
932
|
+
list,
|
|
933
|
+
"click",
|
|
934
|
+
(e) => {
|
|
935
|
+
const item = itemOf(e.target);
|
|
936
|
+
if (!item || this.#forced()) return;
|
|
937
|
+
if (isDisabled(item)) return drop(e);
|
|
938
|
+
if (!this.#state.open) {
|
|
939
|
+
if (this.#out) drop(e);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const ready = this.#readySince;
|
|
943
|
+
const pointerEarly = e.detail > 0 && (ready === null || this.#downAt < ready);
|
|
944
|
+
if (e.detail > 1 || pointerEarly || this.#inSince === null) return drop(e);
|
|
945
|
+
this.#close({ type: "choose", index: this.#items().indexOf(item) });
|
|
946
|
+
},
|
|
947
|
+
true
|
|
948
|
+
);
|
|
949
|
+
on(
|
|
950
|
+
list,
|
|
951
|
+
"pointerdown",
|
|
952
|
+
(e) => {
|
|
953
|
+
this.#downAt = e.timeStamp;
|
|
954
|
+
const item = itemOf(e.target);
|
|
955
|
+
if (e.button === 0 && item && !isDisabled(item) && !this.#forced()) item.setAttribute("data-pressed", "");
|
|
956
|
+
},
|
|
957
|
+
true
|
|
958
|
+
);
|
|
959
|
+
on(list, "pointermove", (e) => {
|
|
960
|
+
const item = itemOf(e.target);
|
|
961
|
+
if (item && this.#state.open && !this.#forced()) this.#step({ type: "point", index: this.#items().indexOf(item) });
|
|
962
|
+
});
|
|
963
|
+
for (const type of ["pointerup", "pointercancel", "pointerout"]) {
|
|
964
|
+
on(list, type, (e) => itemOf(e.target)?.removeAttribute("data-pressed"));
|
|
965
|
+
}
|
|
966
|
+
on(list, "focusout", (e) => {
|
|
967
|
+
const to = e.relatedTarget;
|
|
968
|
+
if (!this.#state.open || !to || this.#forced()) return;
|
|
969
|
+
if (list.contains(to) || trigger.contains(to) || this.#carrier?.contains(to)) return;
|
|
970
|
+
this.#close({ type: "dismiss", reason: "outside" });
|
|
971
|
+
});
|
|
972
|
+
const roots = rootsOf(this);
|
|
973
|
+
roots.forEach((root, i) => {
|
|
974
|
+
const inner = i === 0 ? null : roots[i - 1].host;
|
|
975
|
+
on(
|
|
976
|
+
root,
|
|
977
|
+
"pointerdown",
|
|
978
|
+
(e) => {
|
|
979
|
+
if (!this.#state.open || this.#forced()) return;
|
|
980
|
+
const path = e.composedPath();
|
|
981
|
+
if (inner && path.includes(inner)) return;
|
|
982
|
+
if ([list, trigger, this.#carrier].some((node) => node !== null && path.includes(node))) return;
|
|
983
|
+
this.#close({ type: "dismiss", reason: "outside" });
|
|
984
|
+
},
|
|
985
|
+
true
|
|
986
|
+
);
|
|
987
|
+
on(root, "scroll", (e) => this.#follow(e), true);
|
|
988
|
+
});
|
|
989
|
+
on(window, "resize", (e) => this.#follow(e));
|
|
990
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
991
|
+
const resized = new ResizeObserver(() => this.#out && this.#follow());
|
|
992
|
+
resized.observe(this.#carrier ?? trigger);
|
|
993
|
+
this.#cleanups.push(() => resized.disconnect());
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/** Puts the menu back over its trigger after a scroll or a resize, once per frame. */
|
|
997
|
+
#follow(e) {
|
|
998
|
+
if (!this.#out || this.#placeFrame) return;
|
|
999
|
+
if (e?.target instanceof Node && this.#list?.contains(e.target)) return;
|
|
1000
|
+
this.#placeFrame = requestAnimationFrame(() => {
|
|
1001
|
+
this.#placeFrame = 0;
|
|
1002
|
+
if (this.#out) this.#place();
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
#step(event) {
|
|
1006
|
+
const step = stepMenu(this.#state, event, this.#model());
|
|
1007
|
+
this.#state = step.state;
|
|
1008
|
+
this.#applyFocus(step.focus);
|
|
1009
|
+
}
|
|
1010
|
+
#applyFocus(focus) {
|
|
1011
|
+
const list = this.#list;
|
|
1012
|
+
if (focus.to === "item") this.#items()[focus.index]?.focus({ preventScroll: true });
|
|
1013
|
+
else if (focus.to === "menu") list.focus({ preventScroll: true });
|
|
1014
|
+
else if (focus.to === "trigger") this.#trigger.focus({ preventScroll: true });
|
|
1015
|
+
else if (focus.to === "none") {
|
|
1016
|
+
const active = list.getRootNode().activeElement;
|
|
1017
|
+
if (active instanceof HTMLElement && list.contains(active)) active.blur();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
#show(focus) {
|
|
1021
|
+
const list = this.#list;
|
|
1022
|
+
const step = stepMenu(this.#state, { type: "open", focus }, this.#model());
|
|
1023
|
+
this.#state = step.state;
|
|
1024
|
+
this.#readySince = null;
|
|
1025
|
+
this.#inSince = null;
|
|
1026
|
+
clearTimeout(this.#inertTimer);
|
|
1027
|
+
list.inert = false;
|
|
1028
|
+
this.#trigger.setAttribute("aria-expanded", "true");
|
|
1029
|
+
this.#comeOut();
|
|
1030
|
+
this.#run();
|
|
1031
|
+
this.#applyFocus(step.focus);
|
|
1032
|
+
}
|
|
1033
|
+
#close(event) {
|
|
1034
|
+
const step = stepMenu(this.#state, event, this.#model());
|
|
1035
|
+
const closing = this.#state.open && !step.state.open;
|
|
1036
|
+
this.#state = step.state;
|
|
1037
|
+
this.#applyFocus(step.focus);
|
|
1038
|
+
if (!closing) return;
|
|
1039
|
+
this.#readySince = null;
|
|
1040
|
+
this.#inSince = null;
|
|
1041
|
+
this.#trigger.setAttribute("aria-expanded", "false");
|
|
1042
|
+
for (const item of this.#items()) item.removeAttribute("data-pressed");
|
|
1043
|
+
clearTimeout(this.#inertTimer);
|
|
1044
|
+
this.#inertTimer = window.setTimeout(() => {
|
|
1045
|
+
if (this.#list && !this.#state.open && this.#out) this.#list.inert = true;
|
|
1046
|
+
});
|
|
1047
|
+
this.#run();
|
|
1048
|
+
}
|
|
1049
|
+
#comeOut() {
|
|
1050
|
+
const list = this.#list;
|
|
1051
|
+
const wasOut = this.#out;
|
|
1052
|
+
if (!list.matches(":popover-open")) list.showPopover();
|
|
1053
|
+
this.#out = true;
|
|
1054
|
+
if (!wasOut) this.#natural = null;
|
|
1055
|
+
this.#place();
|
|
1056
|
+
if (wasOut) return;
|
|
1057
|
+
this.#carrier?.setCarried?.(1);
|
|
1058
|
+
this.#holdSample();
|
|
1059
|
+
}
|
|
1060
|
+
#triggerBox() {
|
|
1061
|
+
const trigger = this.#trigger;
|
|
1062
|
+
const r = (this.#carrier ?? trigger).getBoundingClientRect();
|
|
1063
|
+
const radius = parseFloat(getComputedStyle(trigger).borderTopLeftRadius) || 0;
|
|
1064
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height, radius: Math.min(radius, r.width / 2, r.height / 2) };
|
|
1065
|
+
}
|
|
1066
|
+
#corner() {
|
|
1067
|
+
const wanted = this.getAttribute("corner");
|
|
1068
|
+
return wanted && Object.hasOwn(MENU_CORNERS, wanted) ? MENU_CORNERS[wanted] : void 0;
|
|
1069
|
+
}
|
|
1070
|
+
#measure() {
|
|
1071
|
+
const list = this.#list;
|
|
1072
|
+
this.setAttribute("data-measure", "");
|
|
1073
|
+
const natural = { width: list.offsetWidth, height: list.offsetHeight };
|
|
1074
|
+
this.removeAttribute("data-measure");
|
|
1075
|
+
return natural;
|
|
1076
|
+
}
|
|
1077
|
+
#place() {
|
|
1078
|
+
const list = this.#list;
|
|
1079
|
+
const spec = menuSize(this.#size());
|
|
1080
|
+
this.#natural ??= this.#measure();
|
|
1081
|
+
const natural = this.#natural;
|
|
1082
|
+
const viewport = { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
|
|
1083
|
+
const trigger = this.#triggerBox();
|
|
1084
|
+
const placement = menuPlacement(trigger, natural, viewport, { corner: this.#corner(), margin: VIEWPORT_MARGIN });
|
|
1085
|
+
this.#from = trigger;
|
|
1086
|
+
this.#to = { ...placement.rect, radius: spec.radius };
|
|
1087
|
+
this.#anchor = placement.anchor;
|
|
1088
|
+
list.style.setProperty("--vk-content-w", `${placement.rect.width - 2 * spec.padding}px`);
|
|
1089
|
+
list.toggleAttribute("data-scroll", placement.rect.height < natural.height);
|
|
1090
|
+
this.#paint();
|
|
1091
|
+
}
|
|
1092
|
+
#holdSample() {
|
|
1093
|
+
const glass = this.#glass;
|
|
1094
|
+
if (!glass || !this.#to) return;
|
|
1095
|
+
let sample = this.#sample;
|
|
1096
|
+
if (!sample) {
|
|
1097
|
+
this.#setBox(this.#to);
|
|
1098
|
+
sample = glass.probe();
|
|
1099
|
+
}
|
|
1100
|
+
this.#held = sample;
|
|
1101
|
+
glass.setSample(sample);
|
|
1102
|
+
this.#paint();
|
|
1103
|
+
}
|
|
1104
|
+
#setBox(box) {
|
|
1105
|
+
const s = this.#list.style;
|
|
1106
|
+
const px = (v) => `${v.toFixed(2)}px`;
|
|
1107
|
+
s.setProperty("--vk-x", px(box.x));
|
|
1108
|
+
s.setProperty("--vk-y", px(box.y));
|
|
1109
|
+
s.setProperty("--vk-w", px(box.width));
|
|
1110
|
+
s.setProperty("--vk-h", px(box.height));
|
|
1111
|
+
s.setProperty("--vk-r", px(box.radius));
|
|
1112
|
+
}
|
|
1113
|
+
#run() {
|
|
1114
|
+
const instant = this.#presentation?.instantSprings ?? false;
|
|
1115
|
+
if (instant) stepGrowth(this.#growth, 0, this.#state.open, true);
|
|
1116
|
+
this.#paint();
|
|
1117
|
+
if (instant) {
|
|
1118
|
+
this.#settle();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (this.#frame) return;
|
|
1122
|
+
this.#lastFrame = 0;
|
|
1123
|
+
this.#frame = requestAnimationFrame((t) => this.#tick(t));
|
|
1124
|
+
}
|
|
1125
|
+
#tick(now) {
|
|
1126
|
+
this.#frame = 0;
|
|
1127
|
+
if (!this.isConnected || this.#forced()) return;
|
|
1128
|
+
const dt = this.#lastFrame ? Math.min((now - this.#lastFrame) / 1e3, 0.05) : 1 / 60;
|
|
1129
|
+
this.#lastFrame = now;
|
|
1130
|
+
const instant = this.#presentation?.instantSprings ?? false;
|
|
1131
|
+
const moving = stepGrowth(this.#growth, dt, this.#state.open, instant);
|
|
1132
|
+
this.#paint();
|
|
1133
|
+
if (moving) this.#frame = requestAnimationFrame((t) => this.#tick(t));
|
|
1134
|
+
else this.#settle();
|
|
1135
|
+
}
|
|
1136
|
+
#settle() {
|
|
1137
|
+
if (this.#state.open) {
|
|
1138
|
+
this.#paint();
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const list = this.#list;
|
|
1142
|
+
if (list.matches(":popover-open")) list.hidePopover();
|
|
1143
|
+
clearTimeout(this.#inertTimer);
|
|
1144
|
+
list.inert = false;
|
|
1145
|
+
list.removeAttribute("data-scroll");
|
|
1146
|
+
for (const name of Array.from(list.style)) if (name.startsWith("--vk-")) list.style.removeProperty(name);
|
|
1147
|
+
this.removeAttribute("data-phase");
|
|
1148
|
+
this.#out = false;
|
|
1149
|
+
this.#held = null;
|
|
1150
|
+
this.#from = this.#to = this.#anchor = null;
|
|
1151
|
+
this.#glass?.setSample(this.#sample);
|
|
1152
|
+
this.#carrier?.setCarried?.(null);
|
|
1153
|
+
}
|
|
1154
|
+
#paint() {
|
|
1155
|
+
const list = this.#list;
|
|
1156
|
+
if (!list || !this.#out || !this.#from || !this.#to || !this.#anchor) return;
|
|
1157
|
+
const phase = growthPhase(this.#growth);
|
|
1158
|
+
const f = menuSurfaceFrame(this.#from, this.#to, this.#anchor, phase);
|
|
1159
|
+
const settled = phase.shrink === 1 && phase.grow === 1;
|
|
1160
|
+
const inkIn = Math.round(f.ink.incoming * 1e3) / 1e3;
|
|
1161
|
+
if (this.#state.open && inkIn >= 1) {
|
|
1162
|
+
this.#inSince ??= performance.now();
|
|
1163
|
+
if (this.#growth.grow.v <= 0) this.#readySince ??= performance.now();
|
|
1164
|
+
}
|
|
1165
|
+
this.setAttribute("data-phase", settled ? "open" : "moving");
|
|
1166
|
+
this.#setBox(f.box);
|
|
1167
|
+
const s = list.style;
|
|
1168
|
+
s.setProperty("--vk-cx", `${f.content.x.toFixed(2)}px`);
|
|
1169
|
+
s.setProperty("--vk-cy", `${f.content.y.toFixed(2)}px`);
|
|
1170
|
+
s.setProperty("--vk-ink-in", inkIn.toFixed(3));
|
|
1171
|
+
const body = menuBody(VIREGLASS_CONTROL_MATERIAL, f.activity, this.#presentation?.frost ?? false);
|
|
1172
|
+
s.setProperty("--vk-body-alpha", body.alpha.toFixed(3));
|
|
1173
|
+
s.setProperty("--vk-body-white", body.white.toFixed(3));
|
|
1174
|
+
s.setProperty("--vk-body-lift", body.lift.toFixed(3));
|
|
1175
|
+
this.#carrier?.setCarried?.(f.ink.outgoing);
|
|
1176
|
+
this.#pushGlass(f.box, f.morph, f.activity);
|
|
1177
|
+
}
|
|
1178
|
+
/** Hands the lens only what changed: a new morph or material rebuilds its filter, a moved box needs nothing. */
|
|
1179
|
+
#pushGlass(box, morph, activity) {
|
|
1180
|
+
const glass = this.#glass;
|
|
1181
|
+
if (!glass) return;
|
|
1182
|
+
const round = Math.round;
|
|
1183
|
+
const step = round(Math.min(Math.max(activity, 0), 1) * MATERIAL_STEPS2) / MATERIAL_STEPS2;
|
|
1184
|
+
const geometry = { width: round(box.width), height: round(box.height), cornerRadius: round(box.radius) };
|
|
1185
|
+
const lens = morph ? {
|
|
1186
|
+
smoothing: round(morph.smoothing),
|
|
1187
|
+
shape: {
|
|
1188
|
+
offsetX: round(morph.offsetX),
|
|
1189
|
+
offsetY: round(morph.offsetY),
|
|
1190
|
+
width: round(morph.width),
|
|
1191
|
+
height: round(morph.height),
|
|
1192
|
+
cornerRadius: round(morph.cornerRadius)
|
|
1193
|
+
}
|
|
1194
|
+
} : null;
|
|
1195
|
+
const key = {
|
|
1196
|
+
material: String(step),
|
|
1197
|
+
morph: lens ? JSON.stringify(lens) : "none",
|
|
1198
|
+
geometry: `${geometry.width}x${geometry.height}r${geometry.cornerRadius}`
|
|
1199
|
+
};
|
|
1200
|
+
const last = this.#lastLens;
|
|
1201
|
+
if (key.material === last.material && key.morph === last.morph && key.geometry === last.geometry) return;
|
|
1202
|
+
const next = {};
|
|
1203
|
+
if (key.geometry !== last.geometry) next.geometry = geometry;
|
|
1204
|
+
if (key.material !== last.material) next.material = menuMaterial(VIREGLASS_CONTROL_MATERIAL, step);
|
|
1205
|
+
if (key.morph !== last.morph) next.morph = lens;
|
|
1206
|
+
this.#lastLens = key;
|
|
1207
|
+
glass.frame(next);
|
|
1208
|
+
}
|
|
1209
|
+
#forced() {
|
|
1210
|
+
const wanted = this.getAttribute(FORCE_ATTRIBUTE2);
|
|
1211
|
+
return wanted !== null && (Object.hasOwn(FORCED2, wanted) || FORCED_PHASE.test(wanted)) ? wanted : null;
|
|
1212
|
+
}
|
|
1213
|
+
#applyForced() {
|
|
1214
|
+
const forced = this.#forced();
|
|
1215
|
+
const [shrink, grow] = (forced ?? "").split(" ").map(Number);
|
|
1216
|
+
const phase = !forced ? null : Object.hasOwn(FORCED2, forced) ? FORCED2[forced] : { shrink, grow };
|
|
1217
|
+
cancelAnimationFrame(this.#frame);
|
|
1218
|
+
this.#frame = 0;
|
|
1219
|
+
if (!phase) {
|
|
1220
|
+
this.#state = MENU_CLOSED;
|
|
1221
|
+
this.#growth = createGrowthState();
|
|
1222
|
+
this.#trigger.setAttribute("aria-expanded", "false");
|
|
1223
|
+
if (this.#out) this.#settle();
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
this.#state = { ...MENU_CLOSED, open: forced !== "retracting" };
|
|
1227
|
+
this.#growth = {
|
|
1228
|
+
shrink: { x: phase.shrink, v: 0, target: phase.shrink },
|
|
1229
|
+
grow: { x: phase.grow, v: 0, target: phase.grow }
|
|
1230
|
+
};
|
|
1231
|
+
this.#trigger.setAttribute("aria-expanded", String(this.#state.open));
|
|
1232
|
+
this.#list.inert = true;
|
|
1233
|
+
this.#comeOut();
|
|
1234
|
+
}
|
|
1235
|
+
#size() {
|
|
1236
|
+
const wanted = this.getAttribute("size");
|
|
1237
|
+
if (wanted && CONTROL_SIZES.includes(wanted)) return wanted;
|
|
1238
|
+
return defaultSize({ touch: isCoarsePointer() });
|
|
1239
|
+
}
|
|
1240
|
+
#wantsGlass() {
|
|
1241
|
+
const wanted = this.getAttribute("glass");
|
|
1242
|
+
return wanted !== null && GLASS_ON.has(wanted);
|
|
1243
|
+
}
|
|
1244
|
+
#refreshMode(force = false, afterFailure = false) {
|
|
1245
|
+
if (!afterFailure) this.#glassBroken = false;
|
|
1246
|
+
const mode = effectiveA11y(this.#own);
|
|
1247
|
+
let next = presentationFor(mode, this.#wantsGlass());
|
|
1248
|
+
if (this.#glassBroken && next.glass) next = { ...next, glass: false, surface: next.frost ? "frosted" : "plain" };
|
|
1249
|
+
const changed = force || !this.#mode || !sameA11y(this.#mode, mode) || next.surface !== this.#presentation?.surface;
|
|
1250
|
+
this.#mode = mode;
|
|
1251
|
+
this.#presentation = next;
|
|
1252
|
+
this.#applySize();
|
|
1253
|
+
this.setAttribute("data-surface", next.surface);
|
|
1254
|
+
this.toggleAttribute("data-contrast", next.contrastBorder);
|
|
1255
|
+
this.toggleAttribute("data-frost", next.frost);
|
|
1256
|
+
this.setAttribute("data-glass", next.glass ? "on" : "off");
|
|
1257
|
+
if (changed) this.#reattachGlass(mode);
|
|
1258
|
+
if (this.#out) {
|
|
1259
|
+
this.#natural = null;
|
|
1260
|
+
this.#place();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
#applySize() {
|
|
1264
|
+
const spec = menuSize(this.#size());
|
|
1265
|
+
const set = (name, value) => this.style.setProperty(name, value);
|
|
1266
|
+
set("--vk-menu-row", `${spec.row}px`);
|
|
1267
|
+
set("--vk-menu-pad", `${spec.padding}px`);
|
|
1268
|
+
set("--vk-menu-radius", `${spec.radius}px`);
|
|
1269
|
+
set("--vk-item-radius", `${spec.itemRadius}px`);
|
|
1270
|
+
set("--vk-menu-icon", `${spec.icon}px`);
|
|
1271
|
+
set("--vk-label-pad", `${spec.labelPad}px`);
|
|
1272
|
+
set("--vk-fill-active", String(MENU_ITEM_FILL.active));
|
|
1273
|
+
set("--vk-fill-pressed", String(MENU_ITEM_FILL.pressed));
|
|
1274
|
+
this.setAttribute("data-size", this.#size());
|
|
1275
|
+
}
|
|
1276
|
+
#dropGlass() {
|
|
1277
|
+
this.#glassToken += 1;
|
|
1278
|
+
this.#glass?.destroy();
|
|
1279
|
+
this.#glass = null;
|
|
1280
|
+
this.#glassKey = "";
|
|
1281
|
+
this.#lastLens = { material: "", morph: "", geometry: "" };
|
|
1282
|
+
}
|
|
1283
|
+
#reattachGlass(mode) {
|
|
1284
|
+
const list = this.#list;
|
|
1285
|
+
const want = this.#presentation?.glass === true && list !== null;
|
|
1286
|
+
const key = want ? `${JSON.stringify(mode)}|${this.#size()}` : "";
|
|
1287
|
+
if (key === this.#glassKey) return;
|
|
1288
|
+
this.#dropGlass();
|
|
1289
|
+
this.#glassKey = key;
|
|
1290
|
+
if (!want || !list) return;
|
|
1291
|
+
const token = this.#glassToken;
|
|
1292
|
+
const box = this.#from ?? this.#triggerBox();
|
|
1293
|
+
void attachControlGlass(
|
|
1294
|
+
list,
|
|
1295
|
+
{
|
|
1296
|
+
material: menuMaterial(VIREGLASS_CONTROL_MATERIAL, 0),
|
|
1297
|
+
geometry: { width: Math.round(box.width), height: Math.round(box.height), cornerRadius: menuSize(this.#size()).radius },
|
|
1298
|
+
accessibility: mode,
|
|
1299
|
+
onTouch: false,
|
|
1300
|
+
sample: this.#held ?? this.#sample,
|
|
1301
|
+
interactive: false
|
|
1302
|
+
},
|
|
1303
|
+
() => token === this.#glassToken && this.isConnected
|
|
1304
|
+
).then((session) => {
|
|
1305
|
+
if (!session) return;
|
|
1306
|
+
this.#glass = session;
|
|
1307
|
+
this.#lastLens = { material: "", morph: "", geometry: "" };
|
|
1308
|
+
if (this.#out) this.#holdSample();
|
|
1309
|
+
this.dispatchEvent(new CustomEvent("vire-glass-ready"));
|
|
1310
|
+
}).catch(() => {
|
|
1311
|
+
if (token !== this.#glassToken) return;
|
|
1312
|
+
this.#glassKey = "";
|
|
1313
|
+
this.#glassBroken = true;
|
|
1314
|
+
this.#refreshMode(false, true);
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
549
1319
|
|
|
550
1320
|
// src/web/index.ts
|
|
551
1321
|
var BUTTON_TAG = "vire-button";
|
|
552
1322
|
var ICON_BUTTON_TAG = "vire-icon-button";
|
|
1323
|
+
var MENU_TAG = "vire-menu";
|
|
553
1324
|
function defineVireElements(options = {}) {
|
|
554
1325
|
const registry = options.registry ?? (typeof customElements === "undefined" ? void 0 : customElements);
|
|
555
1326
|
if (!registry) return;
|
|
@@ -560,8 +1331,9 @@ function defineVireElements(options = {}) {
|
|
|
560
1331
|
for (const [tag, kind] of pairs) {
|
|
561
1332
|
if (!registry.get(tag)) registry.define(tag, createControlElementClass(kind, { nonce: options.nonce }));
|
|
562
1333
|
}
|
|
1334
|
+
if (!registry.get(MENU_TAG)) registry.define(MENU_TAG, createMenuElementClass({ nonce: options.nonce }));
|
|
563
1335
|
}
|
|
564
1336
|
|
|
565
|
-
export { BUTTON_TAG, ICON_BUTTON_TAG, STYLES, defineVireElements, getA11yOverride, setA11yOverride, systemA11y };
|
|
1337
|
+
export { BUTTON_TAG, ICON_BUTTON_TAG, MENU_TAG, STYLES, defineVireElements, getA11yOverride, setA11yOverride, systemA11y };
|
|
566
1338
|
//# sourceMappingURL=web.js.map
|
|
567
1339
|
//# sourceMappingURL=web.js.map
|