@filip.mazev/blocks 1.4.3 → 1.4.5

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.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/_index.scss +1 -1
  3. package/core/styles/_field-state.scss +16 -4
  4. package/core/styles/_index.scss +0 -1
  5. package/core/styles/_mixins.scss +2 -2
  6. package/core/styles/_motion.scss +1 -1
  7. package/core/styles/_palettes.scss +127 -132
  8. package/core/styles/_slider.scss +158 -40
  9. package/core/styles/_theme-engine.scss +43 -63
  10. package/core/styles/_utilities.scss +3 -4
  11. package/core/styles/_validation.scss +6 -1
  12. package/core/styles/_variables.scss +24 -14
  13. package/core/styles/themes/_cyberpunk-theme.scss +1 -1
  14. package/core/styles/themes/_green-theme.scss +1 -1
  15. package/core/styles/themes/_high-contrast-theme.scss +152 -133
  16. package/core/styles/themes/_orange-theme.scss +1 -1
  17. package/core/styles/themes/_purple-theme.scss +1 -1
  18. package/core/styles/themes/_red-theme.scss +1 -1
  19. package/core/styles/themes/_white-theme.scss +26 -20
  20. package/effects/README.md +5 -9
  21. package/effects/effects.css +200 -121
  22. package/effects/runtime/README.md +19 -11
  23. package/fesm2022/filip.mazev-blocks-core.mjs +27 -22
  24. package/fesm2022/filip.mazev-blocks-core.mjs.map +1 -1
  25. package/fesm2022/filip.mazev-blocks-effects-runtime.mjs +887 -67
  26. package/fesm2022/filip.mazev-blocks-effects-runtime.mjs.map +1 -1
  27. package/fesm2022/filip.mazev-blocks-effects.mjs +8 -48
  28. package/fesm2022/filip.mazev-blocks-effects.mjs.map +1 -1
  29. package/fesm2022/filip.mazev-blocks-forms.mjs +65 -61
  30. package/fesm2022/filip.mazev-blocks-forms.mjs.map +1 -1
  31. package/fesm2022/filip.mazev-blocks-modal.mjs +29 -32
  32. package/fesm2022/filip.mazev-blocks-modal.mjs.map +1 -1
  33. package/fesm2022/filip.mazev-blocks-navigation.mjs +35 -1
  34. package/fesm2022/filip.mazev-blocks-navigation.mjs.map +1 -1
  35. package/fesm2022/filip.mazev-blocks-primitives.mjs +106 -32
  36. package/fesm2022/filip.mazev-blocks-primitives.mjs.map +1 -1
  37. package/fesm2022/filip.mazev-blocks-toastr.mjs +5 -7
  38. package/fesm2022/filip.mazev-blocks-toastr.mjs.map +1 -1
  39. package/fesm2022/filip.mazev-blocks.mjs +1 -1
  40. package/fesm2022/filip.mazev-blocks.mjs.map +1 -1
  41. package/navigation/README.md +44 -0
  42. package/package.json +1 -1
  43. package/schematics/ng-add/index.js +4 -1
  44. package/types/filip.mazev-blocks-core.d.ts +3 -4
  45. package/types/filip.mazev-blocks-effects-runtime.d.ts +10 -8
  46. package/types/filip.mazev-blocks-effects.d.ts +5 -17
  47. package/types/filip.mazev-blocks-forms.d.ts +13 -11
  48. package/types/filip.mazev-blocks-modal.d.ts +1 -0
  49. package/types/filip.mazev-blocks-navigation.d.ts +20 -4
  50. package/types/filip.mazev-blocks-primitives.d.ts +12 -2
  51. package/types/filip.mazev-blocks-toastr.d.ts +4 -4
  52. package/types/filip.mazev-blocks.d.ts +1 -1
@@ -25,12 +25,11 @@ function smoothstep(low, high, value) {
25
25
  }
26
26
  const configurationEvent = "bx-glass-config-change";
27
27
  const attributes = [
28
- "data-bx-glass-material",
29
28
  "data-bx-glass-quality",
30
29
  "data-bx-reduced-motion",
31
30
  "data-bx-reduced-transparency"
32
31
  ];
33
- const properties = ["--bx-glass-blur", "--bx-glass-opacity"];
32
+ const properties = ["--bx-glassiness"];
34
33
  const configured = /* @__PURE__ */ new WeakSet();
35
34
  function configureAppearance(root, initial = {}) {
36
35
  if (configured.has(root)) throw new Error("A glass configuration is already mounted on this root.");
@@ -40,21 +39,19 @@ function configureAppearance(root, initial = {}) {
40
39
  let options = { ...initial };
41
40
  let disposed = false;
42
41
  const notify = () => {
43
- const ViewEvent = root.ownerDocument.defaultView?.CustomEvent;
44
- if (ViewEvent) root.ownerDocument.dispatchEvent(new ViewEvent(configurationEvent, { detail: root }));
42
+ const viewEvent = root.ownerDocument.defaultView?.CustomEvent;
43
+ if (viewEvent) root.ownerDocument.dispatchEvent(new viewEvent(configurationEvent, { detail: root }));
45
44
  };
46
45
  const paint = () => {
47
46
  restoreAttributes();
48
47
  restoreStyles();
49
48
  const values = [
50
- ["data-bx-glass-material", options.material],
51
49
  ["data-bx-glass-quality", options.quality],
52
50
  ["data-bx-reduced-motion", options.reducedMotion],
53
51
  ["data-bx-reduced-transparency", options.reducedTransparency]
54
52
  ];
55
53
  for (const [name, value] of values) if (value !== void 0) root.setAttribute(name, String(value));
56
- if (options.blur !== void 0) root.style.setProperty("--bx-glass-blur", `${finiteClamp(options.blur, 0, 40, 10)}px`);
57
- if (options.tint !== void 0) root.style.setProperty("--bx-glass-opacity", String(finiteClamp(options.tint, 0, 1, .4)));
54
+ if (options.glassiness !== void 0) root.style.setProperty("--bx-glassiness", String(finiteClamp(options.glassiness, 0, 1, .5)));
58
55
  notify();
59
56
  };
60
57
  paint();
@@ -125,10 +122,27 @@ function subscribeFrame(callback) {
125
122
  }
126
123
  };
127
124
  }
125
+ const lightingOrigins = /* @__PURE__ */ new WeakMap();
126
+ function inheritGlassLighting(overlay, origin) {
127
+ if (overlay.contains(origin)) throw new Error("A lighting origin must be outside its overlay.");
128
+ lightingOrigins.set(overlay, origin);
129
+ return () => lightingOrigins.delete(overlay);
130
+ }
131
+ function lightingOrigin(element) {
132
+ let current = element;
133
+ while (current) {
134
+ const origin = lightingOrigins.get(current);
135
+ if (origin) return origin;
136
+ current = current.parentElement;
137
+ }
138
+ return element;
139
+ }
128
140
  function lightingVisibility(doc, view) {
129
141
  const styles = /* @__PURE__ */ new Map();
130
142
  const boundaries = /* @__PURE__ */ new Map();
131
143
  const layers = /* @__PURE__ */ new Map();
144
+ const ambientBoundaries = /* @__PURE__ */ new Map();
145
+ const hitStacks = /* @__PURE__ */ new Map();
132
146
  const style = (element) => {
133
147
  let value = styles.get(element);
134
148
  if (!value) {
@@ -137,9 +151,55 @@ function lightingVisibility(doc, view) {
137
151
  }
138
152
  return value;
139
153
  };
154
+ const elementsAt = (x, y) => {
155
+ const key = `${Math.round(x)},${Math.round(y)}`;
156
+ const cached = hitStacks.get(key);
157
+ if (cached) return cached;
158
+ const elements = [...doc.elementsFromPoint(x, y)];
159
+ for (let index = 0; index < elements.length; index++) {
160
+ const root = elements[index].shadowRoot;
161
+ if (!root) continue;
162
+ const shadowElements = root.elementsFromPoint(x, y);
163
+ if (!shadowElements.length) continue;
164
+ elements.splice(index, 0, ...shadowElements);
165
+ index += shadowElements.length;
166
+ }
167
+ hitStacks.set(key, elements);
168
+ return elements;
169
+ };
170
+ const ambientBoundary = (element) => {
171
+ const cached = ambientBoundaries.get(element);
172
+ if (cached) return cached;
173
+ const origin = lightingOrigin(element);
174
+ if (origin !== element) {
175
+ const result = ambientBoundary(origin);
176
+ ambientBoundaries.set(element, result);
177
+ return result;
178
+ }
179
+ let current = element;
180
+ const visited = [];
181
+ while (current) {
182
+ visited.push(current);
183
+ const boundary = ambientBoundaries.get(current) ?? current.closest("[data-bx-light-scope], dialog, [role=\"dialog\"], [role=\"alertdialog\"], [popover], .cdk-overlay-pane, .toast-container, bx-modal");
184
+ if (boundary) {
185
+ for (const ancestor of visited) ambientBoundaries.set(ancestor, boundary);
186
+ return boundary;
187
+ }
188
+ const root = current.getRootNode();
189
+ current = current.parentElement ?? root.host ?? null;
190
+ }
191
+ for (const ancestor of visited) ambientBoundaries.set(ancestor, doc.documentElement);
192
+ return doc.documentElement;
193
+ };
140
194
  const boundary = (element) => {
141
195
  const cached = boundaries.get(element);
142
196
  if (cached) return cached;
197
+ const origin = lightingOrigin(element);
198
+ if (origin !== element) {
199
+ const result = boundary(origin);
200
+ boundaries.set(element, result);
201
+ return result;
202
+ }
143
203
  const css = style(element);
144
204
  const result = element.matches("[data-bx-light-scope], dialog, [role=\"dialog\"], [role=\"alertdialog\"], [popover], .cdk-overlay-pane, .toast-container, bx-modal") || css.position === "fixed" || css.position === "sticky" ? element : element.parentElement ? boundary(element.parentElement) : element;
145
205
  boundaries.set(element, result);
@@ -167,17 +227,605 @@ function lightingVisibility(doc, view) {
167
227
  const px = Math.max(bounds.left + inset, Math.min(x, bounds.right - inset));
168
228
  const py = Math.max(bounds.top + inset, Math.min(y, bounds.bottom - inset));
169
229
  if (px < 0 || py < 0 || px >= view.innerWidth || py >= view.innerHeight) return false;
230
+ if (css.pointerEvents === "none" && element.closest("bx-tooltip, [role=\"tooltip\"]")) {
231
+ if (css.visibility !== "visible" || css.display === "none" || Number(css.opacity) === 0) return false;
232
+ const origin = lightingOrigin(element);
233
+ if (origin !== element) {
234
+ const rect = origin.getBoundingClientRect();
235
+ return exposed(origin, rect.left + rect.width / 2, rect.top + rect.height / 2, rect);
236
+ }
237
+ const behind = doc.elementFromPoint(px, py);
238
+ return !!behind && ambientBoundary(behind) === ambientBoundary(element);
239
+ }
170
240
  const hit = doc.elementFromPoint(px, py);
171
241
  return !!hit && (hit === element || element.contains(hit));
172
242
  };
173
243
  return {
174
244
  boundary,
175
245
  layer,
176
- exposed
246
+ exposed,
247
+ style,
248
+ elementsAt,
249
+ ambientBoundary
177
250
  };
178
251
  }
252
+ const sampleInterval = 180;
253
+ const liveInterval = 500;
254
+ const rasterSize = 32;
255
+ var AmbientSampler = class {
256
+ doc;
257
+ view;
258
+ changed;
259
+ observeRoot;
260
+ samples = /* @__PURE__ */ new Map();
261
+ dirty = /* @__PURE__ */ new Set();
262
+ pending = /* @__PURE__ */ new Set();
263
+ sources = /* @__PURE__ */ new Set();
264
+ initialized = false;
265
+ media = /* @__PURE__ */ new WeakMap();
266
+ dependencies;
267
+ sampleCanvas;
268
+ sampleContext;
269
+ timer;
270
+ liveTimer;
271
+ stopFrame;
272
+ nextSample = 0;
273
+ liveDelay = liveInterval;
274
+ motion;
275
+ constructor(doc, view, changed, observeRoot) {
276
+ this.doc = doc;
277
+ this.view = view;
278
+ this.changed = changed;
279
+ this.observeRoot = observeRoot;
280
+ this.sampleCanvas = doc.createElement("canvas");
281
+ this.sampleCanvas.width = this.sampleCanvas.height = rasterSize;
282
+ this.sampleContext = this.sampleCanvas.getContext("2d", { willReadFrequently: true });
283
+ this.motion = view.matchMedia("(prefers-reduced-motion: reduce)");
284
+ }
285
+ add(entry) {
286
+ if (!this.initialized) {
287
+ this.scan(this.doc);
288
+ this.initialized = true;
289
+ }
290
+ const root = entry.element.getRootNode();
291
+ if ("host" in root) this.scan(root);
292
+ this.samples.set(entry, {
293
+ lights: [],
294
+ media: /* @__PURE__ */ new Set(),
295
+ rects: /* @__PURE__ */ new Map()
296
+ });
297
+ this.invalidate(entry);
298
+ }
299
+ remove(entry) {
300
+ this.samples.delete(entry);
301
+ this.dirty.delete(entry);
302
+ this.pending.delete(entry);
303
+ this.syncLive();
304
+ }
305
+ lights(entry) {
306
+ return this.samples.get(entry)?.lights ?? [];
307
+ }
308
+ invalidate(entry) {
309
+ for (const receiver of entry ? [entry] : this.samples.keys()) if (receiver.visible && receiver.element.isConnected) this.dirty.add(receiver);
310
+ this.resume();
311
+ }
312
+ assetChanged(element) {
313
+ this.media.delete(element);
314
+ this.invalidate();
315
+ }
316
+ sourcesChanged(record) {
317
+ for (const node of record.addedNodes) if (node.nodeType === 1) this.scan(node);
318
+ for (const source of this.sources) if (!source.isConnected) this.sources.delete(source);
319
+ }
320
+ resume() {
321
+ if (this.doc.hidden) {
322
+ if (this.timer !== void 0) this.view.clearTimeout(this.timer);
323
+ if (this.liveTimer !== void 0) this.view.clearTimeout(this.liveTimer);
324
+ this.timer = this.liveTimer = void 0;
325
+ this.stopFrame?.();
326
+ this.stopFrame = void 0;
327
+ for (const entry of this.pending) this.dirty.add(entry);
328
+ this.pending.clear();
329
+ return;
330
+ }
331
+ if (this.dirty.size && this.timer === void 0 && !this.stopFrame) this.timer = this.view.setTimeout(() => {
332
+ this.timer = void 0;
333
+ this.nextSample = this.view.performance.now() + sampleInterval;
334
+ this.pending = this.dirty;
335
+ this.dirty = /* @__PURE__ */ new Set();
336
+ this.stopFrame = subscribeFrame(() => this.sampleBatch());
337
+ }, Math.max(0, this.nextSample - this.view.performance.now()));
338
+ this.syncLive();
339
+ }
340
+ destroy() {
341
+ if (this.timer !== void 0) this.view.clearTimeout(this.timer);
342
+ if (this.liveTimer !== void 0) this.view.clearTimeout(this.liveTimer);
343
+ this.stopFrame?.();
344
+ this.stopFrame = void 0;
345
+ this.timer = this.liveTimer = void 0;
346
+ this.samples.clear();
347
+ this.dirty.clear();
348
+ this.pending.clear();
349
+ this.sources.clear();
350
+ this.media = /* @__PURE__ */ new WeakMap();
351
+ this.initialized = false;
352
+ this.nextSample = 0;
353
+ this.liveDelay = liveInterval;
354
+ }
355
+ scan(root) {
356
+ if ("host" in root) this.observeRoot(root);
357
+ if ("tagName" in root && /^(IMG|VIDEO|CANVAS)$/.test(String(root.tagName))) this.sources.add(root);
358
+ const shadow = "shadowRoot" in root ? root.shadowRoot : null;
359
+ if (shadow) this.scan(shadow);
360
+ for (const element of root.querySelectorAll("*")) {
361
+ if (/^(IMG|VIDEO|CANVAS)$/.test(element.tagName)) this.sources.add(element);
362
+ if (element.shadowRoot) this.scan(element.shadowRoot);
363
+ }
364
+ }
365
+ sampleBatch() {
366
+ const start = this.view.performance.now();
367
+ const visibility = lightingVisibility(this.doc, this.view);
368
+ const rects = /* @__PURE__ */ new Map();
369
+ const rasterSamples = /* @__PURE__ */ new Map();
370
+ const sources = [...this.sources];
371
+ const completed = [];
372
+ for (const entry of this.pending) {
373
+ this.pending.delete(entry);
374
+ if (!entry.visible || !entry.element.isConnected || !this.samples.has(entry)) continue;
375
+ entry.bounds = entry.element.getBoundingClientRect();
376
+ this.dependencies = /* @__PURE__ */ new Set();
377
+ const lights = this.environmentLights(entry, sources, rasterSamples, rects, visibility);
378
+ const mediaRects = /* @__PURE__ */ new Map();
379
+ for (const source of this.dependencies) mediaRects.set(source, this.rectFor(source, rects));
380
+ this.samples.set(entry, {
381
+ lights,
382
+ media: this.dependencies,
383
+ rects: mediaRects
384
+ });
385
+ completed.push(entry);
386
+ if (completed.length >= 4 || this.view.performance.now() - start >= 4) break;
387
+ }
388
+ this.dependencies = void 0;
389
+ for (const entry of completed) this.changed(entry);
390
+ if (this.pending.size) return true;
391
+ this.stopFrame = void 0;
392
+ this.resume();
393
+ return false;
394
+ }
395
+ liveSources() {
396
+ const sources = /* @__PURE__ */ new Map();
397
+ for (const [entry, sample] of this.samples) {
398
+ const motion = inheritedAttribute(entry.element, "data-bx-reduced-motion");
399
+ if (!entry.visible || !entry.element.isConnected || (motion === null ? this.motion.matches : motion === "true")) continue;
400
+ for (const source of sample.media) {
401
+ if (!source.isConnected || !/^(CANVAS|VIDEO)$/.test(source.tagName) || this.media.get(source)?.data === null) continue;
402
+ if (source.tagName === "VIDEO" && source.paused) continue;
403
+ const entries = sources.get(source) ?? [];
404
+ entries.push(entry);
405
+ sources.set(source, entries);
406
+ }
407
+ }
408
+ return sources;
409
+ }
410
+ syncLive() {
411
+ const active = !this.doc.hidden && this.liveSources().size > 0;
412
+ if (!active && this.liveTimer !== void 0) {
413
+ this.view.clearTimeout(this.liveTimer);
414
+ this.liveTimer = void 0;
415
+ }
416
+ if (!active || this.liveTimer !== void 0) return;
417
+ this.liveTimer = this.view.setTimeout(() => {
418
+ this.liveTimer = void 0;
419
+ let changed = false;
420
+ const recolored = /* @__PURE__ */ new Set();
421
+ const visibility = lightingVisibility(this.doc, this.view);
422
+ for (const [source, entries] of this.liveSources()) {
423
+ const previous = this.media.get(source);
424
+ const next = this.readRaster(source, true);
425
+ if (previous?.data && next?.data && this.samePixels(previous.data, next.data)) continue;
426
+ if (previous?.data === next?.data) continue;
427
+ changed = true;
428
+ const coverageChanged = !previous?.data || !next?.data || !this.samePixels(previous.data, next.data, true);
429
+ for (const entry of entries) {
430
+ const sample = this.samples.get(entry);
431
+ if (!sample || coverageChanged || this.dirty.has(entry) || this.pending.has(entry)) {
432
+ this.dirty.add(entry);
433
+ continue;
434
+ }
435
+ const pixels = /* @__PURE__ */ new Map([[source, next?.data ?? null]]);
436
+ sample.lights = sample.lights.map((light) => {
437
+ if (light.source !== source) return light;
438
+ const color = this.rasterColorAt(source, light.x, light.y, pixels, sample.rects, visibility);
439
+ if (!color || color === light.color) return light;
440
+ recolored.add(entry);
441
+ return {
442
+ ...light,
443
+ color
444
+ };
445
+ });
446
+ }
447
+ }
448
+ for (const entry of recolored) this.changed(entry);
449
+ this.liveDelay = changed ? liveInterval : Math.min(2e3, this.liveDelay * 2);
450
+ this.resume();
451
+ }, this.liveDelay);
452
+ }
453
+ samePixels(first, second, alphaOnly = false) {
454
+ for (let index = alphaOnly ? 3 : 0; index < first.data.length; index += alphaOnly ? 4 : 1) if (first.data[index] >> 3 !== second.data[index] >> 3) return false;
455
+ return true;
456
+ }
457
+ readRaster(element, refresh = false) {
458
+ const cached = this.media.get(element);
459
+ if (cached && !refresh) return cached;
460
+ const tag = element.tagName;
461
+ let source;
462
+ let stamp;
463
+ if (tag === "IMG") {
464
+ const image = element;
465
+ if (!image.complete || !image.naturalWidth) return void 0;
466
+ source = image;
467
+ } else if (tag === "VIDEO") {
468
+ const video = element;
469
+ if (video.readyState < 2 || !video.videoWidth) return void 0;
470
+ stamp = video.currentTime;
471
+ if (cached?.stamp === stamp) return cached;
472
+ source = video;
473
+ } else {
474
+ const canvas = element;
475
+ if (!canvas.width || !canvas.height) return void 0;
476
+ source = canvas;
477
+ }
478
+ if (!this.sampleContext) return void 0;
479
+ let data = null;
480
+ try {
481
+ this.sampleContext.clearRect(0, 0, rasterSize, rasterSize);
482
+ this.sampleContext.drawImage(source, 0, 0, rasterSize, rasterSize);
483
+ data = this.sampleContext.getImageData(0, 0, rasterSize, rasterSize);
484
+ } catch {
485
+ this.sampleCanvas.width = rasterSize;
486
+ this.sampleContext = this.sampleCanvas.getContext("2d", { willReadFrequently: true });
487
+ }
488
+ const result = {
489
+ data,
490
+ stamp
491
+ };
492
+ this.media.set(element, result);
493
+ return result;
494
+ }
495
+ environmentLights(entry, rasterSources, rasterSamples, rects, visibility) {
496
+ const bounds = entry.bounds;
497
+ if (!bounds || bounds.width <= 0 || bounds.height <= 0 || entry.element.getAttribute("data-bx-surface") === "solid" || entry.element.getAttribute("data-bx-reduced-transparency") === "true") return [];
498
+ const insetX = Math.min(3, bounds.width * .1);
499
+ const insetY = Math.min(3, bounds.height * .1);
500
+ const points = [
501
+ {
502
+ x: bounds.left + bounds.width * .24,
503
+ y: bounds.top + insetY,
504
+ side: "inside",
505
+ edge: 0
506
+ },
507
+ {
508
+ x: bounds.left + bounds.width * .76,
509
+ y: bounds.top + insetY,
510
+ side: "inside",
511
+ edge: 0
512
+ },
513
+ {
514
+ x: bounds.right - insetX,
515
+ y: bounds.top + bounds.height * .24,
516
+ side: "inside",
517
+ edge: 1
518
+ },
519
+ {
520
+ x: bounds.right - insetX,
521
+ y: bounds.top + bounds.height * .76,
522
+ side: "inside",
523
+ edge: 1
524
+ },
525
+ {
526
+ x: bounds.left + bounds.width * .76,
527
+ y: bounds.bottom - insetY,
528
+ side: "inside",
529
+ edge: 2
530
+ },
531
+ {
532
+ x: bounds.left + bounds.width * .24,
533
+ y: bounds.bottom - insetY,
534
+ side: "inside",
535
+ edge: 2
536
+ },
537
+ {
538
+ x: bounds.left + insetX,
539
+ y: bounds.top + bounds.height * .76,
540
+ side: "inside",
541
+ edge: 3
542
+ },
543
+ {
544
+ x: bounds.left + insetX,
545
+ y: bounds.top + bounds.height * .24,
546
+ side: "inside",
547
+ edge: 3
548
+ },
549
+ ...[24, 96].flatMap((gap) => [
550
+ {
551
+ x: bounds.left + bounds.width / 2,
552
+ y: bounds.top - gap,
553
+ side: "outside",
554
+ edge: 0
555
+ },
556
+ {
557
+ x: bounds.right + gap,
558
+ y: bounds.top + bounds.height / 2,
559
+ side: "outside",
560
+ edge: 1
561
+ },
562
+ {
563
+ x: bounds.left + bounds.width / 2,
564
+ y: bounds.bottom + gap,
565
+ side: "outside",
566
+ edge: 2
567
+ },
568
+ {
569
+ x: bounds.left - gap,
570
+ y: bounds.top + bounds.height / 2,
571
+ side: "outside",
572
+ edge: 3
573
+ }
574
+ ])
575
+ ];
576
+ const visibleX = Math.max(1, Math.min(this.view.innerWidth - 1, bounds.left + bounds.width / 2));
577
+ const visibleY = Math.max(1, Math.min(this.view.innerHeight - 1, bounds.top + bounds.height / 2));
578
+ if (!visibility.exposed(entry.element, visibleX, visibleY, bounds)) return [];
579
+ const targetBoundary = visibility.ambientBoundary(entry.element);
580
+ const passiveTooltip = visibility.style(entry.element).pointerEvents === "none" && !!entry.element.closest("bx-tooltip, [role=\"tooltip\"]");
581
+ rasterSources = rasterSources.filter((source) => {
582
+ const rect = this.rectFor(source, rects);
583
+ return rect.width > 0 && rect.height > 0 && rect.right >= bounds.left - 96 && rect.left <= bounds.right + 96 && rect.bottom >= bounds.top - 96 && rect.top <= bounds.bottom + 96 && visibility.ambientBoundary(source) === targetBoundary;
584
+ });
585
+ const lights = [];
586
+ const sourcesByEdge = /* @__PURE__ */ new Map();
587
+ const colorsByEdge = /* @__PURE__ */ new Map();
588
+ points.forEach((point, index) => {
589
+ if (point.x < 0 || point.y < 0 || point.x >= this.view.innerWidth || point.y >= this.view.innerHeight) return;
590
+ const stack = visibility.elementsAt(point.x, point.y);
591
+ const receiverIndex = stack.findIndex((element) => this.isInside(element, entry.element));
592
+ let source;
593
+ let foreground = false;
594
+ if (point.side === "inside" && receiverIndex >= 0) {
595
+ source = this.findVisualSource(stack.slice(0, receiverIndex), entry, point.x, point.y, visibility, rasterSamples, rects, {
596
+ skipAncestors: true,
597
+ skipRoot: true
598
+ });
599
+ if (source) foreground = true;
600
+ source ??= this.findRasterSource(rasterSources, entry, point.x, point.y, visibility, rasterSamples, rects, stack, receiverIndex, true, targetBoundary);
601
+ source ??= this.findVisualSource(stack.slice(receiverIndex + 1), entry, point.x, point.y, visibility, rasterSamples, rects, {
602
+ skipAncestors: true,
603
+ skipRoot: true
604
+ });
605
+ source ??= this.findRasterSource(rasterSources, entry, point.x, point.y, visibility, rasterSamples, rects, stack, receiverIndex, false, targetBoundary);
606
+ source ??= this.findVisualSource(stack.slice(receiverIndex + 1), entry, point.x, point.y, visibility, rasterSamples, rects, {
607
+ skipAncestors: false,
608
+ skipRoot: true
609
+ });
610
+ source ??= this.findVisualSource(stack.slice(receiverIndex + 1), entry, point.x, point.y, visibility, rasterSamples, rects, {
611
+ skipAncestors: false,
612
+ skipRoot: false
613
+ });
614
+ } else if (point.side === "outside" || receiverIndex < 0 && passiveTooltip) {
615
+ source = this.findVisualSource(stack, entry, point.x, point.y, visibility, rasterSamples, rects, {
616
+ skipAncestors: true,
617
+ skipRoot: true
618
+ });
619
+ source ??= this.findRasterSource(rasterSources, entry, point.x, point.y, visibility, rasterSamples, rects, stack, -1, void 0, targetBoundary);
620
+ source ??= this.findVisualSource(stack, entry, point.x, point.y, visibility, rasterSamples, rects, {
621
+ skipAncestors: false,
622
+ skipRoot: true
623
+ });
624
+ source ??= this.findVisualSource(stack, entry, point.x, point.y, visibility, rasterSamples, rects, {
625
+ skipAncestors: false,
626
+ skipRoot: false
627
+ });
628
+ }
629
+ if (!source || visibility.ambientBoundary(source.element) !== targetBoundary) return;
630
+ const sourceRect = this.rectFor(source.element, rects);
631
+ const gapX = Math.max(bounds.left - sourceRect.right, sourceRect.left - bounds.right, 0);
632
+ const gapY = Math.max(bounds.top - sourceRect.bottom, sourceRect.top - bounds.bottom, 0);
633
+ if (point.side === "outside" && Math.hypot(gapX, gapY) > 160) return;
634
+ if (source.element === entry.element || this.isInside(source.element, entry.element)) return;
635
+ const edgeSources = sourcesByEdge.get(point.edge) ?? /* @__PURE__ */ new Set();
636
+ if (point.side === "outside" && edgeSources.has(source.element)) return;
637
+ const edgeColors = colorsByEdge.get(point.edge) ?? /* @__PURE__ */ new Map();
638
+ const sourceColors = edgeColors.get(source.element) ?? /* @__PURE__ */ new Set();
639
+ if (point.side === "outside" && sourceColors.has(source.color)) return;
640
+ sourceColors.add(source.color);
641
+ edgeColors.set(source.element, sourceColors);
642
+ colorsByEdge.set(point.edge, edgeColors);
643
+ if (point.side === "inside") {
644
+ edgeSources.add(source.element);
645
+ sourcesByEdge.set(point.edge, edgeSources);
646
+ }
647
+ if (point.side === "outside") foreground = this.isAmbientForeground(source.element, entry.element, stack, -1, visibility);
648
+ const depth = foreground ? 24 : 52;
649
+ const gap = Math.hypot(gapX, gapY);
650
+ const edgeLength = point.edge % 2 === 0 ? bounds.width : bounds.height;
651
+ lights.push({
652
+ key: `environment:${entry.id}:${index}`,
653
+ source: source.element.namespaceURI === "http://www.w3.org/1999/xhtml" ? source.element : void 0,
654
+ ambient: true,
655
+ x: point.x,
656
+ y: point.y,
657
+ z: depth,
658
+ color: source.color,
659
+ intensity: (point.side === "inside" ? .38 : .48) * Math.pow(Math.max(0, 1 - gap / 160), 2),
660
+ radius: 240,
661
+ spread: Math.max(48, Math.min(240, edgeLength * .65))
662
+ });
663
+ });
664
+ return lights;
665
+ }
666
+ findVisualSource(elements, entry, x, y, visibility, rasterSamples, rects, options) {
667
+ for (const element of elements) {
668
+ if (element === entry.element || this.isInside(element, entry.element)) continue;
669
+ if (options.skipAncestors && this.isInside(entry.element, element)) continue;
670
+ if (options.skipRoot && (element === this.doc.documentElement || element === this.doc.body)) continue;
671
+ if (visibility.ambientBoundary(element) !== visibility.ambientBoundary(entry.element)) continue;
672
+ const color = this.visualColorAt(element, x, y, visibility, rasterSamples, rects);
673
+ if (color) return {
674
+ element,
675
+ color
676
+ };
677
+ }
678
+ }
679
+ findRasterSource(sources, entry, x, y, visibility, rasterSamples, rects, stack, receiverIndex, foreground, targetBoundary) {
680
+ const matches = sources.filter((element) => {
681
+ if (this.isInside(element, entry.element) || visibility.ambientBoundary(element) !== targetBoundary) return false;
682
+ const bounds = this.rectFor(element, rects);
683
+ if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return false;
684
+ return foreground === void 0 || this.isAmbientForeground(element, entry.element, stack, receiverIndex, visibility) === foreground;
685
+ }).sort((first, second) => this.rasterOrder(second, stack, entry.element, visibility) - this.rasterOrder(first, stack, entry.element, visibility));
686
+ for (const element of matches) {
687
+ const color = this.rasterColorAt(element, x, y, rasterSamples, rects, visibility);
688
+ if (color) return {
689
+ element,
690
+ color
691
+ };
692
+ }
693
+ }
694
+ visualColorAt(element, x, y, visibility, rasterSamples, rects) {
695
+ const raster = this.rasterColorAt(element, x, y, rasterSamples, rects, visibility);
696
+ if (raster) return raster;
697
+ const style = visibility.style(element);
698
+ const gradient = this.gradientColorAt(style.backgroundImage, this.rectFor(element, rects), x, y);
699
+ if (gradient) return gradient;
700
+ if (!this.isTransparent(style.backgroundColor)) return style.backgroundColor;
701
+ for (const side of [
702
+ "top",
703
+ "right",
704
+ "bottom",
705
+ "left"
706
+ ]) {
707
+ const color = style.getPropertyValue(`border-${side}-color`);
708
+ if (parseFloat(style.getPropertyValue(`border-${side}-width`)) > 0 && !this.isTransparent(color)) return color;
709
+ }
710
+ }
711
+ rasterColorAt(element, x, y, cache, rects, visibility) {
712
+ const tag = element.tagName;
713
+ if (!/^(IMG|CANVAS|VIDEO)$/.test(tag)) return void 0;
714
+ const bounds = this.rectFor(element, rects);
715
+ if (bounds.width <= 0 || bounds.height <= 0) return void 0;
716
+ const css = visibility.style(element);
717
+ if (css.visibility !== "visible" || Number(css.opacity) === 0) return void 0;
718
+ this.dependencies?.add(element);
719
+ let data = cache.get(element);
720
+ if (data === void 0) {
721
+ data = this.readRaster(element)?.data ?? null;
722
+ cache.set(element, data);
723
+ }
724
+ if (!data) return void 0;
725
+ let u = Math.max(0, Math.min(1, (x - bounds.left) / bounds.width));
726
+ let v = Math.max(0, Math.min(1, (y - bounds.top) / bounds.height));
727
+ if ((tag === "IMG" || tag === "VIDEO") && css.objectFit !== "fill") {
728
+ const width = tag === "IMG" ? element.naturalWidth : element.videoWidth;
729
+ const height = tag === "IMG" ? element.naturalHeight : element.videoHeight;
730
+ if (!width || !height) return void 0;
731
+ const contain = Math.min(bounds.width / width, bounds.height / height);
732
+ const scale = css.objectFit === "cover" ? Math.max(bounds.width / width, bounds.height / height) : css.objectFit === "none" ? 1 : css.objectFit === "scale-down" ? Math.min(1, contain) : contain;
733
+ const [positionX = "50%", positionY = "50%"] = css.objectPosition.split(" ");
734
+ const offset = (position, space) => {
735
+ if (position.endsWith("%")) return parseFloat(position) / 100 * space;
736
+ if (position.endsWith("px")) return parseFloat(position);
737
+ return position === "left" || position === "top" ? 0 : position === "right" || position === "bottom" ? space : space / 2;
738
+ };
739
+ u = (x - bounds.left - offset(positionX, bounds.width - width * scale)) / (width * scale);
740
+ v = (y - bounds.top - offset(positionY, bounds.height - height * scale)) / (height * scale);
741
+ if (u < 0 || u > 1 || v < 0 || v > 1) return void 0;
742
+ }
743
+ const px = Math.max(0, Math.min(data.width - 1, Math.floor(u * data.width)));
744
+ const offset = (Math.max(0, Math.min(data.height - 1, Math.floor(v * data.height))) * data.width + px) * 4;
745
+ const alpha = data.data[offset + 3] / 255;
746
+ if (alpha < .08) return void 0;
747
+ return `rgb(${data.data[offset]} ${data.data[offset + 1]} ${data.data[offset + 2]} / ${alpha.toFixed(2)})`;
748
+ }
749
+ gradientColorAt(image, bounds, x, y) {
750
+ if (!image.includes("gradient(")) return void 0;
751
+ const colors = (image.match(/#[\da-f]{3,8}\b|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^)]*\)|\b(?:transparent|black|white|red|green|blue|orange|yellow|purple|gray|grey|teal|cyan|magenta)\b/gi) ?? []).filter((color) => !this.isTransparent(color));
752
+ if (!colors.length) return void 0;
753
+ let amount = .5;
754
+ if (image.includes("linear-gradient(")) {
755
+ const prelude = image.match(/linear-gradient\(\s*([^,]+)/i)?.[1]?.trim().toLowerCase() ?? "";
756
+ let dx = 0, dy = 1;
757
+ const angle = prelude.match(/^(-?[\d.]+)deg$/);
758
+ if (angle) {
759
+ const radians = Number(angle[1]) * Math.PI / 180;
760
+ dx = Math.sin(radians);
761
+ dy = -Math.cos(radians);
762
+ } else if (prelude.startsWith("to ")) {
763
+ dx = prelude.includes("right") ? 1 : prelude.includes("left") ? -1 : 0;
764
+ dy = prelude.includes("bottom") ? 1 : prelude.includes("top") ? -1 : 0;
765
+ }
766
+ const range = Math.max(.01, Math.abs(dx) + Math.abs(dy));
767
+ const u = (x - bounds.left) / Math.max(1, bounds.width) - .5;
768
+ const v = (y - bounds.top) / Math.max(1, bounds.height) - .5;
769
+ amount = Math.max(0, Math.min(1, .5 + (u * dx + v * dy) / range));
770
+ } else {
771
+ const u = (x - (bounds.left + bounds.width / 2)) / Math.max(1, bounds.width / 2);
772
+ const v = (y - (bounds.top + bounds.height / 2)) / Math.max(1, bounds.height / 2);
773
+ amount = Math.max(0, Math.min(1, Math.hypot(u, v) / Math.SQRT2));
774
+ }
775
+ return colors[Math.min(colors.length - 1, Math.round(amount * (colors.length - 1)))];
776
+ }
777
+ isTransparent(color) {
778
+ const value = color.trim().toLowerCase();
779
+ if (!value || value === "transparent") return true;
780
+ const alpha = value.match(/\/\s*([\d.]+)%?\s*\)$/) ?? (/^rgba\(|^hsla\(/.test(value) ? value.match(/,\s*([\d.]+)\s*\)$/) : null);
781
+ return !!alpha && Number(alpha[1]) <= .02;
782
+ }
783
+ isInside(element, ancestor) {
784
+ let current = element;
785
+ while (current) {
786
+ if (current === ancestor) return true;
787
+ const root = current.getRootNode();
788
+ current = current.parentElement ?? root.host ?? null;
789
+ }
790
+ return false;
791
+ }
792
+ layerElement(element, visibility) {
793
+ let current = element;
794
+ while (current && current.namespaceURI !== "http://www.w3.org/1999/xhtml") {
795
+ const root = current.getRootNode();
796
+ current = current.parentElement ?? root.host ?? null;
797
+ }
798
+ return current ? visibility.layer(current) : this.doc.documentElement;
799
+ }
800
+ zIndex(element, visibility) {
801
+ const value = Number.parseFloat(visibility.style(element).zIndex);
802
+ return Number.isFinite(value) ? value : 0;
803
+ }
804
+ isAmbientForeground(source, receiver, stack, receiverIndex, visibility) {
805
+ const sourceIndex = stack.indexOf(source);
806
+ if (sourceIndex >= 0 && receiverIndex >= 0) return sourceIndex < receiverIndex;
807
+ const sourceLayer = this.layerElement(source, visibility);
808
+ const receiverLayer = visibility.layer(receiver);
809
+ const depth = this.zIndex(sourceLayer, visibility) - this.zIndex(receiverLayer, visibility);
810
+ if (depth !== 0) return depth > 0;
811
+ return !!(source.compareDocumentPosition(receiver) & 2);
812
+ }
813
+ rasterOrder(element, stack, receiver, visibility) {
814
+ const index = stack.indexOf(element);
815
+ if (index >= 0) return 1e4 - index;
816
+ return this.zIndex(this.layerElement(element, visibility), visibility) - this.zIndex(visibility.layer(receiver), visibility);
817
+ }
818
+ rectFor(element, rects) {
819
+ let rect = rects.get(element);
820
+ if (!rect) {
821
+ rect = element.getBoundingClientRect();
822
+ rects.set(element, rect);
823
+ }
824
+ return rect;
825
+ }
826
+ };
179
827
  const runtimes = /* @__PURE__ */ new WeakMap();
180
- const activeStateSelector = "[aria-pressed=\"true\"], [aria-selected=\"true\"], [aria-expanded=\"true\"], [aria-current]:not([aria-current=\"false\"]):not([aria-current=\"\"])";
828
+ const activeStateSelector = ":checked, [aria-checked=\"true\"], [aria-pressed=\"true\"], [aria-selected=\"true\"], [aria-expanded=\"true\"], [aria-current]:not([aria-current=\"false\"]):not([aria-current=\"\"])";
181
829
  const disabledSelector = ":disabled, [aria-disabled=\"true\"], .is-disabled, [inert]";
182
830
  function runtimeFor(doc) {
183
831
  let runtime = runtimes.get(doc);
@@ -226,6 +874,16 @@ var SceneRuntime = class {
226
874
  motion;
227
875
  transparency;
228
876
  view;
877
+ ambient;
878
+ renderedStyles = /* @__PURE__ */ new WeakMap();
879
+ assetLoad = (event) => {
880
+ const target = event.target;
881
+ if (target && /^(IMG|VIDEO)$/.test(target.tagName)) this.ambient.assetChanged(target);
882
+ };
883
+ visibilityChange = () => {
884
+ if (!this.doc.hidden) this.refresh();
885
+ this.ambient.resume();
886
+ };
229
887
  down = (event) => {
230
888
  this.pressed = this.find(event);
231
889
  this.pressed?.hooks.press();
@@ -256,7 +914,8 @@ var SceneRuntime = class {
256
914
  }
257
915
  };
258
916
  out = (event) => {
259
- if (this.hovered && !this.hovered.element.contains(event.relatedTarget)) {
917
+ const target = this.hovered?.element.closest("label") ?? this.hovered?.element;
918
+ if (target && !target.contains(event.relatedTarget)) {
260
919
  this.hovered = void 0;
261
920
  this.updateEmitter();
262
921
  }
@@ -273,6 +932,10 @@ var SceneRuntime = class {
273
932
  this.focusedTarget = void 0;
274
933
  this.updateEmitter();
275
934
  };
935
+ change = () => {
936
+ for (const entry of this.entries.values()) this.updateSelected(entry);
937
+ this.updateEmitter();
938
+ };
276
939
  sync = () => {
277
940
  for (const entry of this.entries.values()) {
278
941
  entry.hooks.sync();
@@ -282,8 +945,10 @@ var SceneRuntime = class {
282
945
  this.refresh();
283
946
  };
284
947
  refresh = () => {
948
+ this.ambient.invalidate();
285
949
  for (const entry of this.entries.values()) {
286
950
  entry.bounds = void 0;
951
+ entry.lastLighting = void 0;
287
952
  if (entry.visible) this.queue.add(entry);
288
953
  }
289
954
  this.schedule();
@@ -293,6 +958,11 @@ var SceneRuntime = class {
293
958
  const view = doc.defaultView;
294
959
  if (!view) throw new Error("Glass scenes require a browser document.");
295
960
  this.view = view;
961
+ this.ambient = new AmbientSampler(doc, view, (entry) => {
962
+ if (this.entries.get(entry.element) !== entry) return;
963
+ this.queue.add(entry);
964
+ this.schedule();
965
+ }, (root) => this.observeState(root));
296
966
  this.motion = view.matchMedia("(prefers-reduced-motion: reduce)");
297
967
  this.transparency = view.matchMedia("(prefers-reduced-transparency: reduce)");
298
968
  if (typeof view.IntersectionObserver === "function") this.observer = new view.IntersectionObserver((records) => {
@@ -300,8 +970,10 @@ var SceneRuntime = class {
300
970
  const entry = this.entries.get(record.target);
301
971
  if (!entry) continue;
302
972
  entry.visible = record.isIntersecting;
973
+ this.ambient.invalidate(entry);
303
974
  if (entry.visible) {
304
975
  entry.bounds = void 0;
976
+ entry.lastLighting = void 0;
305
977
  this.queue.add(entry);
306
978
  }
307
979
  }
@@ -309,15 +981,45 @@ var SceneRuntime = class {
309
981
  }, { rootMargin: "64px" });
310
982
  if (typeof view.MutationObserver === "function") this.stateObserver = new view.MutationObserver((records) => {
311
983
  const changed = /* @__PURE__ */ new Set();
984
+ let visualChanged = false;
312
985
  for (const record of records) {
313
- const entry = this.entries.get(record.target);
986
+ if (record.type === "childList") {
987
+ if (record.target.nodeType === 1 && this.isLightingDecoration(record.target)) continue;
988
+ if ([...record.addedNodes, ...record.removedNodes].some((node) => !this.isLightingDecoration(node))) {
989
+ visualChanged = true;
990
+ this.ambient.sourcesChanged(record);
991
+ }
992
+ continue;
993
+ }
994
+ const target = record.target;
995
+ if (record.attributeName === "style" && this.renderedStyles.get(target) === target.getAttribute("style")) continue;
996
+ if (record.attributeName === "class") {
997
+ const entry = this.entries.get(target);
998
+ if (entry) changed.add(entry);
999
+ }
1000
+ if (record.attributeName === "class" || record.attributeName === "style" || record.attributeName === "hidden" || /^(role|data-bx-light-scope|data-bx-surface|data-bx-reduced-transparency|data-bx-reduced-motion|src|srcset|width|height)$/.test(record.attributeName ?? "")) {
1001
+ if (!this.isLightingDecoration(target)) {
1002
+ visualChanged = true;
1003
+ if (/^(src|srcset|width|height)$/.test(record.attributeName ?? "")) this.ambient.assetChanged(target);
1004
+ }
1005
+ continue;
1006
+ }
1007
+ const visual = target.matches("input[type=\"checkbox\"], input[type=\"radio\"]") ? target.closest("label")?.querySelector("[data-bx-surface]") : void 0;
1008
+ const entry = this.entries.get(target) ?? (visual ? this.entries.get(visual) : void 0);
314
1009
  if (entry) changed.add(entry);
315
- if (record.attributeName === "data-bx-field-invalid") for (const surface of record.target.querySelectorAll("[data-bx-surface]")) {
1010
+ if (record.attributeName === "data-bx-field-invalid") for (const surface of target.querySelectorAll("[data-bx-surface]")) {
316
1011
  const child = this.entries.get(surface);
317
1012
  if (child) changed.add(child);
318
1013
  }
319
1014
  }
320
- if (!changed.size) return;
1015
+ if (visualChanged) {
1016
+ for (const entry of this.entries.values()) {
1017
+ entry.bounds = void 0;
1018
+ entry.lastLighting = void 0;
1019
+ }
1020
+ this.ambient.invalidate();
1021
+ }
1022
+ if (!changed.size && !visualChanged) return;
321
1023
  for (const entry of changed) this.updateSelected(entry);
322
1024
  this.updateEmitter();
323
1025
  });
@@ -332,6 +1034,7 @@ var SceneRuntime = class {
332
1034
  };
333
1035
  const overlay = lightingVisibility(this.doc, this.view).boundary(element) !== this.doc.documentElement;
334
1036
  this.entries.set(element, entry);
1037
+ this.ambient.add(entry);
335
1038
  this.observer?.observe(element);
336
1039
  this.updateSelected(entry);
337
1040
  this.updateEmitter();
@@ -341,6 +1044,7 @@ var SceneRuntime = class {
341
1044
  return () => {
342
1045
  this.observer?.unobserve(element);
343
1046
  this.entries.delete(element);
1047
+ this.ambient.remove(entry);
344
1048
  this.queue.delete(entry);
345
1049
  this.selected.delete(entry);
346
1050
  if (this.pressed === entry) this.pressed = void 0;
@@ -358,7 +1062,9 @@ var SceneRuntime = class {
358
1062
  if (entry) this.updateSelected(entry);
359
1063
  this.updateEmitter();
360
1064
  if (entry) {
1065
+ this.ambient.invalidate(entry);
361
1066
  entry.bounds = void 0;
1067
+ entry.lastLighting = void 0;
362
1068
  this.queue.add(entry);
363
1069
  this.schedule();
364
1070
  }
@@ -377,11 +1083,14 @@ var SceneRuntime = class {
377
1083
  update: (next) => {
378
1084
  if (!disposed) {
379
1085
  entry.options = next;
380
- this.refreshRoot(root);
1086
+ this.refreshRoot(root, next.reuseGeometry !== true);
381
1087
  }
382
1088
  },
383
1089
  refresh: () => {
384
- if (!disposed) this.refreshRoot(root);
1090
+ if (!disposed) {
1091
+ this.ambient.invalidate();
1092
+ this.refreshRoot(root);
1093
+ }
385
1094
  },
386
1095
  destroy: () => {
387
1096
  if (disposed) return;
@@ -392,6 +1101,11 @@ var SceneRuntime = class {
392
1101
  }
393
1102
  };
394
1103
  }
1104
+ isLightingDecoration(node) {
1105
+ if (node.nodeType !== node.ELEMENT_NODE) return true;
1106
+ const element = node;
1107
+ return element.matches(".bx-glass-decoration, .bx-glass-rim") || !!element.closest(".bx-glass-decoration, .bx-glass-rim");
1108
+ }
395
1109
  find(event) {
396
1110
  for (const node of event.composedPath()) {
397
1111
  const found = this.entries.get(node);
@@ -399,13 +1113,17 @@ var SceneRuntime = class {
399
1113
  if (found.element.closest(disabledSelector)) return void 0;
400
1114
  if (found.hooks.interactive()) return found;
401
1115
  }
1116
+ const label = event.target.closest("label");
1117
+ const visual = label?.control?.matches("input[type=\"checkbox\"], input[type=\"radio\"]") ? label.querySelector("[data-bx-surface]") : void 0;
1118
+ const found = visual ? this.entries.get(visual) : void 0;
1119
+ if (found && !found.element.closest(disabledSelector) && !label?.control?.matches(disabledSelector) && found.hooks.interactive()) return found;
402
1120
  }
403
1121
  scope(element) {
404
- return element.closest(`[data-bx-light-scope], dialog, [role="dialog"], [role="alertdialog"], [popover], .cdk-overlay-pane, .toast-container, bx-modal, main, aside, nav`) ?? this.doc.body;
1122
+ return lightingOrigin(element).closest(`[data-bx-light-scope], dialog, [role="dialog"], [role="alertdialog"], [popover], .cdk-overlay-pane, .toast-container, bx-modal, main, aside, nav`) ?? this.doc.body;
405
1123
  }
406
1124
  updateEmitter() {
407
1125
  const next = /* @__PURE__ */ new Map();
408
- const enabled = (entry) => entry.hooks.interactive() && !entry.element.closest(disabledSelector);
1126
+ const enabled = (entry) => entry.hooks.interactive() && !entry.element.closest(disabledSelector) && !entry.element.closest("label")?.control?.matches(disabledSelector);
409
1127
  for (const entry of [
410
1128
  ...this.selected,
411
1129
  this.hovered,
@@ -426,45 +1144,69 @@ var SceneRuntime = class {
426
1144
  if (!entry.visible) continue;
427
1145
  if (sources.some((source) => this.scope(source.element) === this.scope(entry.element))) {
428
1146
  entry.bounds = void 0;
1147
+ entry.lastLighting = void 0;
429
1148
  this.queue.add(entry);
430
1149
  }
431
1150
  }
432
1151
  this.schedule();
433
1152
  }
434
1153
  updateSelected(entry) {
435
- if (entry.hooks.emission?.() && entry.element.matches(activeStateSelector)) this.selected.add(entry);
1154
+ const checked = (entry.element.closest("label")?.control)?.matches("input[type=\"checkbox\"]:checked, input[type=\"radio\"]:checked");
1155
+ if (entry.hooks.emission?.() && (entry.element.matches(activeStateSelector) || checked)) this.selected.add(entry);
436
1156
  else this.selected.delete(entry);
437
1157
  }
438
- refreshRoot(root) {
1158
+ refreshRoot(root, measure = true) {
439
1159
  for (const entry of this.entries.values()) {
440
- if (!root.contains(entry.element)) continue;
441
- entry.bounds = void 0;
1160
+ if (!root.contains(lightingOrigin(entry.element))) continue;
1161
+ if (measure) {
1162
+ entry.bounds = void 0;
1163
+ entry.lastLighting = void 0;
1164
+ }
442
1165
  if (entry.visible) this.queue.add(entry);
443
1166
  }
444
1167
  this.schedule();
445
1168
  }
446
- connect() {
447
- if (this.connected) return;
448
- this.connected = true;
449
- this.stateObserver?.observe(this.doc.documentElement, {
1169
+ observeState(root) {
1170
+ this.stateObserver?.observe(root, {
450
1171
  subtree: true,
451
1172
  attributes: true,
1173
+ childList: true,
452
1174
  attributeFilter: [
453
1175
  "aria-pressed",
1176
+ "aria-checked",
1177
+ "checked",
454
1178
  "aria-selected",
455
1179
  "aria-expanded",
456
1180
  "aria-current",
457
1181
  "disabled",
458
1182
  "aria-disabled",
459
1183
  "aria-invalid",
460
- "data-bx-field-invalid"
1184
+ "data-bx-field-invalid",
1185
+ "class",
1186
+ "style",
1187
+ "hidden",
1188
+ "role",
1189
+ "data-bx-light-scope",
1190
+ "data-bx-surface",
1191
+ "data-bx-reduced-transparency",
1192
+ "data-bx-reduced-motion",
1193
+ "src",
1194
+ "srcset",
1195
+ "width",
1196
+ "height"
461
1197
  ]
462
1198
  });
1199
+ }
1200
+ connect() {
1201
+ if (this.connected) return;
1202
+ this.connected = true;
1203
+ this.observeState(this.doc.documentElement);
463
1204
  this.doc.addEventListener("pointerdown", this.down, { passive: true });
464
1205
  this.doc.addEventListener("pointerover", this.over, { passive: true });
465
1206
  this.doc.addEventListener("pointerout", this.out, { passive: true });
466
1207
  this.doc.addEventListener("focusin", this.focus);
467
1208
  this.doc.addEventListener("focusout", this.unfocus);
1209
+ this.doc.addEventListener("change", this.change);
468
1210
  this.view.addEventListener("pointerup", this.up, { passive: true });
469
1211
  this.view.addEventListener("pointercancel", this.up, { passive: true });
470
1212
  this.doc.addEventListener("pointerleave", this.leave, { passive: true });
@@ -474,12 +1216,19 @@ var SceneRuntime = class {
474
1216
  passive: true,
475
1217
  capture: true
476
1218
  });
1219
+ this.doc.addEventListener("load", this.assetLoad, true);
1220
+ this.doc.addEventListener("loadeddata", this.assetLoad, true);
1221
+ this.doc.addEventListener("play", this.assetLoad, true);
1222
+ this.doc.addEventListener("pause", this.assetLoad, true);
1223
+ this.doc.addEventListener("seeked", this.assetLoad, true);
1224
+ this.doc.addEventListener("visibilitychange", this.visibilityChange);
477
1225
  this.doc.addEventListener(configurationEvent, this.sync);
478
1226
  this.motion.addEventListener("change", this.sync);
479
1227
  this.transparency.addEventListener("change", this.sync);
480
1228
  }
481
1229
  disconnectIfEmpty() {
482
1230
  if (this.entries.size || this.scenes.size) return;
1231
+ this.ambient.destroy();
483
1232
  this.stopFrame?.();
484
1233
  this.stopFrame = void 0;
485
1234
  this.queue.clear();
@@ -492,12 +1241,19 @@ var SceneRuntime = class {
492
1241
  this.doc.removeEventListener("pointerout", this.out);
493
1242
  this.doc.removeEventListener("focusin", this.focus);
494
1243
  this.doc.removeEventListener("focusout", this.unfocus);
1244
+ this.doc.removeEventListener("change", this.change);
495
1245
  this.view.removeEventListener("pointerup", this.up);
496
1246
  this.view.removeEventListener("pointercancel", this.up);
497
1247
  this.doc.removeEventListener("pointerleave", this.leave);
498
1248
  this.view.removeEventListener("blur", this.blur);
499
1249
  this.view.removeEventListener("resize", this.refresh);
500
1250
  this.doc.removeEventListener("scroll", this.refresh, true);
1251
+ this.doc.removeEventListener("load", this.assetLoad, true);
1252
+ this.doc.removeEventListener("loadeddata", this.assetLoad, true);
1253
+ this.doc.removeEventListener("play", this.assetLoad, true);
1254
+ this.doc.removeEventListener("pause", this.assetLoad, true);
1255
+ this.doc.removeEventListener("seeked", this.assetLoad, true);
1256
+ this.doc.removeEventListener("visibilitychange", this.visibilityChange);
501
1257
  this.doc.removeEventListener(configurationEvent, this.sync);
502
1258
  this.motion.removeEventListener("change", this.sync);
503
1259
  this.transparency.removeEventListener("change", this.sync);
@@ -529,26 +1285,46 @@ var SceneRuntime = class {
529
1285
  const sceneData = /* @__PURE__ */ new Map();
530
1286
  const projections = batch.filter((entry) => entry.visible && entry.element.isConnected).map((entry) => {
531
1287
  entry.bounds ??= entry.element.getBoundingClientRect();
1288
+ const origin = lightingOrigin(entry.element);
532
1289
  let nearest;
533
- for (const candidate of this.scenes) if (candidate.root.contains(entry.element) && (!nearest || nearest.root.contains(candidate.root))) nearest = candidate;
534
- if (nearest && !sceneData.has(nearest)) sceneData.set(nearest, {
535
- lights: (nearest.options.lights ?? []).slice(0, 4).map((light, index) => this.lightPoint(light, `${nearest.id}:${index}`)),
536
- reflections: (nearest.options.reflectors ?? []).slice(0, 4).map((reflection) => this.reflectionPoint(reflection))
537
- });
1290
+ for (const candidate of this.scenes) if (candidate.root.contains(origin) && (!nearest || nearest.root.contains(candidate.root))) nearest = candidate;
1291
+ if (nearest && !sceneData.has(nearest)) {
1292
+ const scene = nearest;
1293
+ sceneData.set(scene, {
1294
+ lights: (scene.options.lights ?? []).slice(0, 4).map((light, index) => this.lightPoint(light, `${scene.id}:${index}`)),
1295
+ reflections: (scene.options.reflectors ?? []).slice(0, 4).map((reflection) => this.reflectionPoint(reflection))
1296
+ });
1297
+ }
538
1298
  const data = nearest ? sceneData.get(nearest) : void 0;
1299
+ const sceneRoot = nearest?.root ?? entry.element;
539
1300
  const boundary = visibility.boundary(entry.element);
540
- const lights = (data?.lights ?? []).filter((light) => visibility.boundary(light.source ?? nearest.root) === boundary);
541
- for (const { emitter, light } of activeLights) if (this.scope(emitter.element) === this.scope(entry.element) && visibility.layer(emitter.element) === visibility.layer(entry.element) && !emitter.element.contains(entry.element) && !entry.element.contains(emitter.element)) lights.push(light);
1301
+ const lights = (data?.lights ?? []).filter((light) => visibility.boundary(light.source ?? sceneRoot) === boundary);
1302
+ for (const { emitter, light } of activeLights) if (this.scope(emitter.element) === this.scope(entry.element) && visibility.layer(lightingOrigin(emitter.element)) === visibility.layer(origin) && !emitter.element.contains(entry.element) && (!entry.element.contains(emitter.element) || entry.element.matches("[role=\"menu\"]"))) lights.push(light);
1303
+ lights.push(...this.ambient.lights(entry));
542
1304
  const reflections = (data?.reflections ?? []).filter((reflection) => visibility.boundary(reflection.source) === boundary);
543
1305
  return [entry, this.project(entry, lights, reflections, visibility)];
544
1306
  });
545
1307
  for (const entry of batch) this.queue.delete(entry);
546
1308
  for (const { emitter, color, exposed } of emissions) emitter.hooks.emit?.(exposed, color);
547
- for (const [entry, lighting] of projections) entry.hooks.light(lighting);
1309
+ for (const [entry, lighting] of projections) {
1310
+ if (this.sameLighting(entry.lastLighting, lighting)) continue;
1311
+ entry.lastLighting = lighting;
1312
+ const before = entry.element.getAttribute("style");
1313
+ entry.hooks.light(lighting);
1314
+ const after = entry.element.getAttribute("style");
1315
+ if (before !== after) this.renderedStyles.set(entry.element, after);
1316
+ }
548
1317
  if (!this.queue.size) this.stopFrame = void 0;
549
1318
  return this.queue.size > 0;
550
1319
  });
551
1320
  }
1321
+ sameLighting(previous, next) {
1322
+ if (!previous || previous.x !== next.x || previous.y !== next.y || previous.color !== next.color || previous.strength !== next.strength || previous.rim !== next.rim || previous.reflection !== next.reflection || previous.reflectionStrength !== next.reflectionStrength || previous.reflectionX !== next.reflectionX || previous.reflectionY !== next.reflectionY || previous.contributions.length !== next.contributions.length) return false;
1323
+ return previous.contributions.every((light, index) => {
1324
+ const other = next.contributions[index];
1325
+ return light.key === other.key && light.x === other.x && light.y === other.y && light.color === other.color && light.strength === other.strength && light.edgeStrength === other.edgeStrength && light.ambient === other.ambient && light.spread === other.spread;
1326
+ });
1327
+ }
552
1328
  lightPoint(light, key, border = false) {
553
1329
  const rect = light.element?.getBoundingClientRect();
554
1330
  if (light.element && !border) {
@@ -582,7 +1358,7 @@ var SceneRuntime = class {
582
1358
  };
583
1359
  }
584
1360
  project(entry, lights, reflectors, visibility) {
585
- const b = entry.bounds;
1361
+ const b = entry.bounds ??= entry.element.getBoundingClientRect();
586
1362
  const cx = b.left + b.width / 2, cy = b.top + b.height / 2;
587
1363
  let x = 25, y = 0, strength = .04, color = "white";
588
1364
  let strongest = 0, weightedX = 0, weightedY = 0, totalWeight = 0;
@@ -600,26 +1376,30 @@ var SceneRuntime = class {
600
1376
  const edgeDistance = Math.hypot(Math.max(b.left - lx, 0, lx - b.right), Math.max(b.top - ly, 0, ly - b.bottom), light.z);
601
1377
  const edgeWeight = Math.min(.85, light.intensity * Math.pow(Math.max(0, 1 - edgeDistance / light.radius), 2));
602
1378
  if (edgeWeight <= .01 && weight <= .001) continue;
603
- if (!visibility.exposed(entry.hooks.hitTarget ?? entry.element, lx, ly, b)) continue;
604
- if (light.source) {
1379
+ if (!light.ambient && !visibility.exposed(entry.hooks.hitTarget ?? entry.element, lx, ly, b)) continue;
1380
+ if (!light.ambient && light.source) {
605
1381
  const source = light.source.closest("[data-bx-surface]") ?? light.source;
606
1382
  if (!visibility.exposed(source, lx, ly, source.getBoundingClientRect())) continue;
607
1383
  }
608
- weightedX += localX * weight;
609
- weightedY += localY * weight;
610
- if (edgeWeight > .01) rims.push(`radial-gradient(ellipse farthest-side at ${localX.toFixed(2)}% ${localY.toFixed(2)}%, color-mix(in srgb, ${light.color} ${(edgeWeight * 100).toFixed(2)}%, transparent), transparent 75%)`);
1384
+ if (edgeWeight > .01) rims.push(`radial-gradient(${light.spread ? `circle ${light.spread}px` : "ellipse farthest-side"} at ${localX.toFixed(2)}% ${localY.toFixed(2)}%, color-mix(in srgb, ${light.color} ${(edgeWeight * 100).toFixed(2)}%, transparent), transparent 75%)`);
611
1385
  if (edgeWeight > .01 || weight > .001) contributions.push({
612
1386
  key: light.key,
613
1387
  x: localX,
614
1388
  y: localY,
615
1389
  color: light.color,
616
- strength: Math.min(.25, weight * .4),
617
- edgeStrength: edgeWeight
1390
+ strength: light.ambient ? 0 : Math.min(.25, weight * .4),
1391
+ edgeStrength: edgeWeight,
1392
+ ambient: light.ambient,
1393
+ spread: light.spread
618
1394
  });
619
- totalWeight += weight;
620
- if (weight > strongest) {
621
- strongest = weight;
622
- color = light.color;
1395
+ if (!light.ambient) {
1396
+ weightedX += localX * weight;
1397
+ weightedY += localY * weight;
1398
+ totalWeight += weight;
1399
+ if (weight > strongest) {
1400
+ strongest = weight;
1401
+ color = light.color;
1402
+ }
623
1403
  }
624
1404
  }
625
1405
  if (totalWeight > .001) {
@@ -726,8 +1506,9 @@ var Spring = class {
726
1506
  };
727
1507
  const mounted = /* @__PURE__ */ new WeakMap();
728
1508
  function isActionSurface(element) {
729
- if (element.matches("button, bx-button, [bx-button], a[href], summary, input:not([type=\"hidden\"]), select, textarea, [role=\"button\"], [role=\"link\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"combobox\"], [onclick]") || typeof element.onclick === "function") return true;
1509
+ if (element.matches("button, bx-button, [bx-button], a[href], summary, input:not([type=\"hidden\"]), select, textarea, [role=\"button\"], [role=\"link\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"combobox\"], [role=\"textbox\"], [role=\"searchbox\"], [role=\"slider\"], [role=\"spinbutton\"], [onclick]") || typeof element.onclick === "function") return true;
730
1510
  if (element.closest("label")?.control) return true;
1511
+ if (!element.closest("bx-form-field")) return false;
731
1512
  const controls = element.querySelectorAll("input:not([type=\"hidden\"]), select, textarea");
732
1513
  return controls.length === 1 && controls[0].closest("[data-bx-surface]") === element;
733
1514
  }
@@ -738,8 +1519,12 @@ function mountSurface(element, initial = {}) {
738
1519
  if (!view) throw new Error("Glass surfaces must be mounted in a browser document.");
739
1520
  const ownProperties = [
740
1521
  "position",
741
- "--bx-glass-blur",
742
- "--bx-glass-opacity"
1522
+ "--bx-glassiness",
1523
+ "--bx-glass-lit-edge",
1524
+ "--bx-glass-lit-edge-top",
1525
+ "--bx-glass-lit-edge-right",
1526
+ "--bx-glass-lit-edge-bottom",
1527
+ "--bx-glass-lit-edge-left"
743
1528
  ];
744
1529
  const restoreStyles = rememberStyles(element, ownProperties);
745
1530
  const attributes = [
@@ -763,6 +1548,14 @@ function mountSurface(element, initial = {}) {
763
1548
  rim.className = "bx-glass-rim";
764
1549
  rim.setAttribute("aria-hidden", "true");
765
1550
  element.append(rim);
1551
+ const environment = [doc.createElement("span"), doc.createElement("span")];
1552
+ for (const node of environment) {
1553
+ node.className = "bx-glass-contribution bx-glass-environment";
1554
+ node.style.opacity = "0";
1555
+ rim.append(node);
1556
+ }
1557
+ let environmentImage = "none";
1558
+ let environmentIndex = 0;
766
1559
  const ambient = doc.createElement("span");
767
1560
  ambient.className = "bx-glass-ambient";
768
1561
  decoration.append(light);
@@ -774,6 +1567,7 @@ function mountSurface(element, initial = {}) {
774
1567
  let disposed = false;
775
1568
  let unsubscribe;
776
1569
  let reducedMotion = false;
1570
+ let unifiedLighting = false;
777
1571
  const interactive = () => options.interactive ?? isActionSurface(element);
778
1572
  let emitting = false;
779
1573
  let emittingColor;
@@ -896,8 +1690,36 @@ function mountSurface(element, initial = {}) {
896
1690
  x.snap(value.x);
897
1691
  y.snap(value.y);
898
1692
  strength.target = value.strength;
899
- const active = new Set(value.contributions.map((contribution) => contribution.key));
900
- for (const contribution of value.contributions) setLayer(contribution);
1693
+ if (unifiedLighting) {
1694
+ const sources = [...value.contributions].sort((a, b) => a.edgeStrength - b.edgeStrength).slice(-4);
1695
+ const edgeAt = (x, y) => {
1696
+ let edge = "var(--bx-border-default, GrayText)";
1697
+ for (const source of sources) {
1698
+ const distance = Math.hypot((x - source.x) / Math.max(1, source.x, 100 - source.x), (y - source.y) / Math.max(1, source.y, 100 - source.y));
1699
+ const strength = finiteClamp(source.edgeStrength, 0, .85, 0) * .35 * Math.max(0, 1 - distance / .75);
1700
+ if (strength > .001) edge = `color-mix(in srgb, ${source.color} ${(strength * 100).toFixed(2)}%, ${edge})`;
1701
+ }
1702
+ return edge;
1703
+ };
1704
+ element.style.setProperty("--bx-glass-lit-edge", edgeAt(50, 50));
1705
+ element.style.setProperty("--bx-glass-lit-edge-top", edgeAt(50, 0));
1706
+ element.style.setProperty("--bx-glass-lit-edge-right", edgeAt(100, 50));
1707
+ element.style.setProperty("--bx-glass-lit-edge-bottom", edgeAt(50, 100));
1708
+ element.style.setProperty("--bx-glass-lit-edge-left", edgeAt(0, 50));
1709
+ }
1710
+ const ambientImage = value.contributions.filter((contribution) => contribution.ambient).map((contribution) => `radial-gradient(circle ${contribution.spread}px at ${contribution.x.toFixed(2)}% ${contribution.y.toFixed(2)}%, color-mix(in srgb, ${contribution.color} ${(contribution.edgeStrength * 100).toFixed(2)}%, transparent), transparent 75%)`).join(", ") || "none";
1711
+ if (ambientImage !== environmentImage) {
1712
+ environmentImage = ambientImage;
1713
+ const previous = environment[environmentIndex];
1714
+ environmentIndex = 1 - environmentIndex;
1715
+ const next = environment[environmentIndex];
1716
+ next.style.backgroundImage = ambientImage;
1717
+ next.style.opacity = "1";
1718
+ previous.style.opacity = "0";
1719
+ }
1720
+ const contributions = value.contributions.filter((contribution) => !contribution.ambient);
1721
+ const active = new Set(contributions.map((contribution) => contribution.key));
1722
+ for (const contribution of contributions) setLayer(contribution);
901
1723
  for (const [key, layer] of layers) if (key !== "emitter" && !active.has(key) && !layer.leaving) fadeLayer(key, layer, false);
902
1724
  decoration.style.setProperty("--bx-glass-light-color", value.color);
903
1725
  decoration.style.setProperty("--bx-glass-reflection-color", value.reflection);
@@ -907,9 +1729,7 @@ function mountSurface(element, initial = {}) {
907
1729
  animate();
908
1730
  };
909
1731
  const sync = () => {
910
- const inheritedMaterial = inheritedAttribute(element, "data-bx-glass-material");
911
- const material = options.material ?? (inheritedMaterial === "frosted" || inheritedMaterial === "solid" ? inheritedMaterial : "glass");
912
- element.setAttribute("data-bx-surface", material);
1732
+ element.setAttribute("data-bx-surface", "glass");
913
1733
  const inheritedMotion = globalValue("data-bx-reduced-motion") ?? originalAttributes.get("data-bx-reduced-motion");
914
1734
  const inheritedTransparency = globalValue("data-bx-reduced-transparency") ?? originalAttributes.get("data-bx-reduced-transparency");
915
1735
  reducedMotion = options.reducedMotion ?? (inheritedMotion === null || inheritedMotion === void 0 ? motion.matches : inheritedMotion === "true");
@@ -921,11 +1741,10 @@ function mountSurface(element, initial = {}) {
921
1741
  const economical = view.navigator.connection?.saveData || view.navigator.hardwareConcurrency > 0 && view.navigator.hardwareConcurrency <= 4 || view.matchMedia("(pointer: coarse)").matches;
922
1742
  const enhanced = quality !== "baseline" && supported && (quality === "enhanced" || !economical);
923
1743
  element.setAttribute("data-bx-glass-renderer", enhanced ? "enhanced" : "baseline");
924
- if (options.blur !== void 0) element.style.setProperty("--bx-glass-blur", `${finiteClamp(options.blur, 0, 40, 10)}px`);
925
- else restoreProperty("--bx-glass-blur");
926
- if (options.tint !== void 0) element.style.setProperty("--bx-glass-opacity", String(finiteClamp(options.tint, 0, 1, .4)));
927
- else restoreProperty("--bx-glass-opacity");
1744
+ if (options.glassiness !== void 0) element.style.setProperty("--bx-glassiness", String(finiteClamp(options.glassiness, 0, 1, .5)));
1745
+ else restoreProperty("--bx-glassiness");
928
1746
  const style = view.getComputedStyle(element);
1747
+ unifiedLighting = style.getPropertyValue("--bx-glass-unified-lighting").trim() === "1";
929
1748
  for (const side of [
930
1749
  "top",
931
1750
  "right",
@@ -1003,10 +1822,10 @@ function createGlassTransition(origin, target, options = {}) {
1003
1822
  const view = doc.defaultView;
1004
1823
  if (!view) throw new Error("Glass transitions must be mounted in a browser document.");
1005
1824
  if (origin.ownerDocument !== doc) throw new Error("Glass transition endpoints must share a document.");
1006
- const restoreStyles = rememberStyles(target, ["opacity", "visibility"]);
1007
1825
  const skin = target.matches("[data-bx-surface]") ? target : target.querySelector("[data-bx-surface]") ?? target;
1008
- const restoreMaterial = rememberAttributes(skin, ["data-bx-motion-material"]);
1009
- skin.setAttribute("data-bx-motion-material", "solid");
1826
+ const restoreOpacity = rememberStyles(skin, ["opacity"]);
1827
+ const restoreVisibility = rememberStyles(target, ["visibility"]);
1828
+ const opacity = Number(view.getComputedStyle(skin).opacity);
1010
1829
  const preference = view.matchMedia("(prefers-reduced-motion: reduce)");
1011
1830
  let disposed = false;
1012
1831
  let active = false;
@@ -1018,7 +1837,7 @@ function createGlassTransition(origin, target, options = {}) {
1018
1837
  return options.reducedMotion ?? (inherited === null ? preference.matches : inherited === "true");
1019
1838
  };
1020
1839
  const paint = () => {
1021
- target.style.opacity = String(value);
1840
+ skin.style.opacity = String(value * opacity);
1022
1841
  options.onUpdate?.();
1023
1842
  };
1024
1843
  function settle() {
@@ -1026,7 +1845,8 @@ function createGlassTransition(origin, target, options = {}) {
1026
1845
  value = destination;
1027
1846
  unsubscribe?.();
1028
1847
  unsubscribe = void 0;
1029
- restoreStyles();
1848
+ restoreOpacity();
1849
+ restoreVisibility();
1030
1850
  if (destination === 0) target.style.visibility = "hidden";
1031
1851
  const resolve = resolvePending;
1032
1852
  resolvePending = void 0;
@@ -1041,7 +1861,7 @@ function createGlassTransition(origin, target, options = {}) {
1041
1861
  destination = open ? 1 : 0;
1042
1862
  elapsed = 0;
1043
1863
  active = true;
1044
- restoreStyles();
1864
+ restoreVisibility();
1045
1865
  const finished = new Promise((resolve) => {
1046
1866
  resolvePending = resolve;
1047
1867
  });
@@ -1082,12 +1902,12 @@ function createGlassTransition(origin, target, options = {}) {
1082
1902
  resolvePending = void 0;
1083
1903
  preference.removeEventListener("change", onPreference);
1084
1904
  doc.removeEventListener(configurationEvent, onPreference);
1085
- restoreStyles();
1086
- restoreMaterial();
1905
+ restoreOpacity();
1906
+ restoreVisibility();
1087
1907
  }
1088
1908
  };
1089
1909
  }
1090
1910
  const createLiquidTransition = createGlassTransition;
1091
- export { Spring, configureAppearance, createGlassTransition, createLiquidTransition, mountGlassScene, mountSurface, observeGlassLighting, refreshGlassLighting };
1911
+ export { Spring, configureAppearance, createGlassTransition, createLiquidTransition, inheritGlassLighting, mountGlassScene, mountSurface, observeGlassLighting, refreshGlassLighting };
1092
1912
 
1093
1913
  //# sourceMappingURL=filip.mazev-blocks-effects-runtime.mjs.map