@ash-qw/dsh-theme-prts 0.1.91

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 (50) hide show
  1. package/LICENSE +24 -0
  2. package/README.en.md +103 -0
  3. package/README.md +107 -0
  4. package/THIRD_PARTY_NOTICES.md +48 -0
  5. package/cordis.patch.yml +4 -0
  6. package/lib/client.js +7466 -0
  7. package/lib/index.js +3 -0
  8. package/package.json +64 -0
  9. package/src/assets/faction-emblems/lungmen.png +0 -0
  10. package/src/assets/faction-emblems/penguin-logistics.png +0 -0
  11. package/src/assets/faction-emblems/reunion.png +0 -0
  12. package/src/assets/faction-emblems/rhine-lab.png +0 -0
  13. package/src/assets/faction-emblems/rhodes-island.png +0 -0
  14. package/src/assets/prts-original-avatar.png +0 -0
  15. package/src/assets/rhodes-dsh.svg +6 -0
  16. package/src/assets/topographic-map.svg +72 -0
  17. package/src/assets/topographic-tech.svg +19 -0
  18. package/src/client/assistant-glass-adapter.js +231 -0
  19. package/src/client/composer-glass-adapter.js +210 -0
  20. package/src/client/conversation-control-adapter.js +109 -0
  21. package/src/client/conversation-scale-adapter.js +2243 -0
  22. package/src/client/facility-geometry.js +60 -0
  23. package/src/client/facility-vector.js +230 -0
  24. package/src/client/floating-glass-adapter.js +177 -0
  25. package/src/client/host-geometry-adapter.js +123 -0
  26. package/src/client/index.js +359 -0
  27. package/src/client/layout-stability-diagnostics.js +162 -0
  28. package/src/client/operations-shell-v2.js +248 -0
  29. package/src/client/particle-field-adapter.js +1840 -0
  30. package/src/client/preferences.js +239 -0
  31. package/src/client/rc7-adapter.js +83 -0
  32. package/src/client/resize-shield-adapter.js +64 -0
  33. package/src/client/settings-page.js +62 -0
  34. package/src/client/sidebar-control-adapter.js +317 -0
  35. package/src/client/startup-sequence.js +366 -0
  36. package/src/client/status-projection.js +79 -0
  37. package/src/client/terrain-motion-adapter.js +188 -0
  38. package/src/client/theme-controller-v2.js +134 -0
  39. package/src/client/theme-controller.js +119 -0
  40. package/src/client/theme-settings-workbench.js +583 -0
  41. package/src/client/to-bottom-adapter.js +85 -0
  42. package/src/client/ui-store.js +23 -0
  43. package/src/index.js +4 -0
  44. package/src/styles/conversation-scale.css +471 -0
  45. package/src/styles/facility-vector.css +292 -0
  46. package/src/styles/prts.css +1223 -0
  47. package/src/styles/refinement.css +429 -0
  48. package/src/styles/stabilization.css +161 -0
  49. package/src/styles/startup-sequence.css +496 -0
  50. package/src/styles/theme-settings-workbench.css +803 -0
@@ -0,0 +1,60 @@
1
+ export const FACILITY_GEOMETRY = Object.freeze({
2
+ stroke: 1,
3
+ corner: 1,
4
+ sideDepth: 2,
5
+ sideOpening: 10,
6
+ sideInner: 6,
7
+ topOpening: 50,
8
+ topInner: 40,
9
+ topDepth: 10 / 3,
10
+ })
11
+
12
+ function number(value) {
13
+ return Number(value.toFixed(3)).toString()
14
+ }
15
+
16
+ function point(x, y) {
17
+ return `${number(x)} ${number(y)}`
18
+ }
19
+
20
+ export function createFacilityPath({ width, height, topNotch = false } = {}) {
21
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 2 || height <= FACILITY_GEOMETRY.sideOpening + 2) return ''
22
+
23
+ const halfStroke = FACILITY_GEOMETRY.stroke / 2
24
+ const left = halfStroke
25
+ const right = width - halfStroke
26
+ const top = halfStroke
27
+ const bottom = height - halfStroke
28
+ const centerX = width / 2
29
+ const centerY = height / 2
30
+ const points = [point(left + FACILITY_GEOMETRY.corner, top)]
31
+
32
+ if (topNotch && width >= FACILITY_GEOMETRY.topOpening + FACILITY_GEOMETRY.corner * 2 + FACILITY_GEOMETRY.stroke) {
33
+ points.push(
34
+ point(centerX - FACILITY_GEOMETRY.topOpening / 2, top),
35
+ point(centerX - FACILITY_GEOMETRY.topInner / 2, top + FACILITY_GEOMETRY.topDepth),
36
+ point(centerX + FACILITY_GEOMETRY.topInner / 2, top + FACILITY_GEOMETRY.topDepth),
37
+ point(centerX + FACILITY_GEOMETRY.topOpening / 2, top),
38
+ )
39
+ }
40
+
41
+ points.push(
42
+ point(right - FACILITY_GEOMETRY.corner, top),
43
+ point(right, top + FACILITY_GEOMETRY.corner),
44
+ point(right, centerY - FACILITY_GEOMETRY.sideOpening / 2),
45
+ point(right - FACILITY_GEOMETRY.sideDepth, centerY - FACILITY_GEOMETRY.sideInner / 2),
46
+ point(right - FACILITY_GEOMETRY.sideDepth, centerY + FACILITY_GEOMETRY.sideInner / 2),
47
+ point(right, centerY + FACILITY_GEOMETRY.sideOpening / 2),
48
+ point(right, bottom - FACILITY_GEOMETRY.corner),
49
+ point(right - FACILITY_GEOMETRY.corner, bottom),
50
+ point(left + FACILITY_GEOMETRY.corner, bottom),
51
+ point(left, bottom - FACILITY_GEOMETRY.corner),
52
+ point(left, centerY + FACILITY_GEOMETRY.sideOpening / 2),
53
+ point(left + FACILITY_GEOMETRY.sideDepth, centerY + FACILITY_GEOMETRY.sideInner / 2),
54
+ point(left + FACILITY_GEOMETRY.sideDepth, centerY - FACILITY_GEOMETRY.sideInner / 2),
55
+ point(left, centerY - FACILITY_GEOMETRY.sideOpening / 2),
56
+ point(left, top + FACILITY_GEOMETRY.corner),
57
+ )
58
+
59
+ return `M ${points.join(' L ')} Z`
60
+ }
@@ -0,0 +1,230 @@
1
+ import { createFacilityPath } from './facility-geometry.js'
2
+
3
+ const SVG_NS = 'http://www.w3.org/2000/svg'
4
+ const SILHOUETTE_TRACK_WIDTH = 512
5
+ const SESSION_PICKUP_HEIGHTS = Object.freeze([5, 11, 7, 17, 11, 20, 13, 7, 15, 9, 5])
6
+
7
+ const SILHOUETTE_PATHS = Object.freeze({
8
+ far: [
9
+ 'M0 38V26H14V22H30V25H43V18H60V22H73V15H84V11H89V6H93V2H97V6H101V11H106V20H121V16H138V22H151V13H158V9H163V4H168V9H173V13H180V21H196V17H211V12H226V20H241V23H253V15H270V19H285V11H293V7H297V3H301V7H305V11H313V18H329V21H342V14H360V19H374V22H388V16H401V12H405V8H409V12H413V16H422V20H438V13H454V18H470V24H484V20H499V23H512V26H512V38Z',
10
+ 'M326 19V7H329V19ZM317 7H346V9H317ZM340 9L350 16H347L337 9Z',
11
+ 'M218 12V7H220V12ZM214 7H224V9H214Z',
12
+ ].join(' '),
13
+ near: [
14
+ 'M0 38V32H16V29H31V33H48V26H61V31H78V28H94V34H112V30H126V32H141V25H156V29H174V33H191V27H206V31H223V34H240V29H256V26H270V32H289V30H305V34H324V27H340V31H357V29H374V34H392V30H410V26H425V31H443V33H460V28H477V31H494V27H512V32H512V38Z',
15
+ 'M94 28V20H97V28ZM90 20H102V22H90Z',
16
+ 'M203 27V23H219V25H205V27Z',
17
+ 'M267 26V18H270V26ZM263 18H274V20H263Z',
18
+ 'M418 26V21H421V26ZM414 21H425V23H414Z',
19
+ ].join(' '),
20
+ })
21
+
22
+ let silhouetteId = 0
23
+
24
+ export const FACILITY_TEXTURES = Object.freeze(['none', 'grid', 'scanline', 'silhouette', 'pickup'])
25
+
26
+ function svgNode(document, name, attributes = {}) {
27
+ const node = document.createElementNS(SVG_NS, name)
28
+ for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value)
29
+ return node
30
+ }
31
+
32
+ function appendPattern(document, defs, { id, width, height, path }) {
33
+ const pattern = svgNode(document, 'pattern', {
34
+ id: `prts-facility-${id}-pattern`,
35
+ patternUnits: 'userSpaceOnUse',
36
+ width,
37
+ height,
38
+ })
39
+ pattern.append(svgNode(document, 'path', {
40
+ d: path,
41
+ fill: 'none',
42
+ stroke: '#fff',
43
+ 'stroke-width': '1',
44
+ opacity: '.72',
45
+ }))
46
+ defs.append(pattern)
47
+
48
+ const mask = svgNode(document, 'mask', {
49
+ id: `prts-facility-${id}-mask`,
50
+ maskUnits: 'userSpaceOnUse',
51
+ x: '0',
52
+ y: '0',
53
+ width: '4096',
54
+ height: '4096',
55
+ })
56
+ mask.append(svgNode(document, 'rect', {
57
+ x: '0',
58
+ y: '0',
59
+ width: '4096',
60
+ height: '4096',
61
+ fill: `url(#prts-facility-${id}-pattern)`,
62
+ }))
63
+ defs.append(mask)
64
+ }
65
+
66
+ export function ensureFacilityTextureDefs(document) {
67
+ let root = document?.querySelector?.('svg[data-prts-facility-defs]')
68
+ if (root) return root
69
+ if (!document?.body) return null
70
+
71
+ root = svgNode(document, 'svg', {
72
+ 'data-prts-facility-defs': '',
73
+ 'data-prts-owned-facility-defs': '',
74
+ 'aria-hidden': 'true',
75
+ focusable: 'false',
76
+ width: '0',
77
+ height: '0',
78
+ })
79
+ const defs = svgNode(document, 'defs')
80
+ appendPattern(document, defs, { id: 'grid', width: '8', height: '8', path: 'M 0 .5 H 8 M .5 0 V 8' })
81
+ appendPattern(document, defs, { id: 'scanline', width: '4', height: '4', path: 'M 0 .5 H 4' })
82
+ root.append(defs)
83
+ document.body.append(root)
84
+ return root
85
+ }
86
+
87
+ function appendSilhouetteScene(document, graphic) {
88
+ const clipId = `prts-facility-silhouette-clip-${++silhouetteId}`
89
+ const defs = svgNode(document, 'defs')
90
+ const clip = svgNode(document, 'clipPath', { id: clipId, clipPathUnits: 'userSpaceOnUse' })
91
+ clip.append(svgNode(document, 'path', { 'data-prts-facility-clip': '' }))
92
+ defs.append(clip)
93
+
94
+ const scene = svgNode(document, 'g', {
95
+ 'data-prts-facility-silhouette': '',
96
+ 'clip-path': `url(#${clipId})`,
97
+ })
98
+ for (const layer of ['far', 'near']) {
99
+ const track = svgNode(document, 'g', { 'data-prts-silhouette-layer': layer })
100
+ for (const offset of [0, SILHOUETTE_TRACK_WIDTH]) {
101
+ track.append(svgNode(document, 'path', {
102
+ d: SILHOUETTE_PATHS[layer],
103
+ transform: `translate(${offset} 0)`,
104
+ }))
105
+ }
106
+ scene.append(track)
107
+ }
108
+
109
+ graphic.insertBefore(defs, graphic.firstChild)
110
+ graphic.insertBefore(scene, graphic.querySelector('[data-prts-facility-layer="outline"]'))
111
+ }
112
+
113
+ function appendSessionPickup(document, graphic) {
114
+ const clipId = `prts-facility-pickup-clip-${++silhouetteId}`
115
+ const defs = svgNode(document, 'defs')
116
+ const clip = svgNode(document, 'clipPath', { id: clipId, clipPathUnits: 'userSpaceOnUse' })
117
+ clip.append(svgNode(document, 'path', { 'data-prts-facility-clip': '' }))
118
+ defs.append(clip)
119
+
120
+ const pickup = svgNode(document, 'g', {
121
+ 'data-prts-session-pickup': '',
122
+ 'clip-path': `url(#${clipId})`,
123
+ })
124
+ pickup.append(svgNode(document, 'line', {
125
+ 'data-prts-session-pickup-baseline': '',
126
+ x1: '60%',
127
+ x2: '98%',
128
+ y1: '19',
129
+ y2: '19',
130
+ }))
131
+ pickup.append(svgNode(document, 'circle', {
132
+ 'data-prts-session-pickup-indicator': '',
133
+ cx: '56%',
134
+ cy: '19',
135
+ r: '2.5',
136
+ }))
137
+ SESSION_PICKUP_HEIGHTS.forEach((height, index) => {
138
+ pickup.append(svgNode(document, 'rect', {
139
+ 'data-prts-session-pickup-bar': '',
140
+ x: `${76 + index * 2.15}%`,
141
+ y: String((38 - height) / 2),
142
+ width: '1.7',
143
+ height: String(height),
144
+ rx: '.4',
145
+ }))
146
+ })
147
+
148
+ graphic.insertBefore(defs, graphic.firstChild)
149
+ graphic.insertBefore(pickup, graphic.querySelector('[data-prts-facility-layer="outline"]'))
150
+ }
151
+
152
+ export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, texture = 'none' } = {}) {
153
+ if (!owner) return null
154
+ let graphic = owner.querySelector?.(':scope > svg[data-prts-facility-svg]')
155
+ if (!graphic) {
156
+ graphic = svgNode(document, 'svg', {
157
+ 'data-prts-facility-svg': kind,
158
+ 'data-prts-owned-facility-vector': '',
159
+ 'aria-hidden': 'true',
160
+ focusable: 'false',
161
+ preserveAspectRatio: 'none',
162
+ })
163
+ for (const layer of ['surface', 'texture', 'outline']) {
164
+ const path = svgNode(document, 'path', { 'data-prts-facility-layer': layer })
165
+ if (layer === 'outline') {
166
+ path.setAttribute('fill', 'none')
167
+ path.setAttribute('stroke-width', '1')
168
+ path.setAttribute('stroke-linejoin', 'miter')
169
+ path.setAttribute('stroke-miterlimit', '2')
170
+ path.setAttribute('vector-effect', 'non-scaling-stroke')
171
+ path.setAttribute('shape-rendering', 'geometricPrecision')
172
+ }
173
+ graphic.append(path)
174
+ }
175
+ owner.insertBefore(graphic, owner.firstChild)
176
+ }
177
+ if (texture === 'silhouette' && !graphic.querySelector('[data-prts-facility-silhouette]')) appendSilhouetteScene(document, graphic)
178
+ if (texture === 'pickup' && !graphic.querySelector('[data-prts-session-pickup]')) appendSessionPickup(document, graphic)
179
+ graphic.setAttribute('data-prts-facility-svg', kind)
180
+ graphic.toggleAttribute('data-prts-top-notch', Boolean(topNotch))
181
+ if (!owner.hasAttribute('data-prts-facility-texture')) owner.setAttribute('data-prts-facility-texture', texture)
182
+ return graphic
183
+ }
184
+
185
+ function setReady(owner, ready) {
186
+ owner.toggleAttribute('data-prts-facility-vector', ready)
187
+ if (owner.hasAttribute('data-prts-facility-spine')) {
188
+ owner.parentElement?.toggleAttribute('data-prts-spine-vector', ready)
189
+ }
190
+ }
191
+
192
+ export function renderFacilityGraphic(owner, { width, height } = {}) {
193
+ const graphic = owner?.querySelector?.(':scope > svg[data-prts-facility-svg]')
194
+ const topNotch = graphic?.hasAttribute('data-prts-top-notch')
195
+ const path = createFacilityPath({ width, height, topNotch })
196
+ if (!graphic || !path) {
197
+ owner?.removeAttribute?.('data-prts-facility-size')
198
+ setReady(owner, false)
199
+ return false
200
+ }
201
+
202
+ const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}`
203
+ if (owner.getAttribute('data-prts-facility-size') !== size) {
204
+ graphic.setAttribute('viewBox', `0 0 ${Number(width.toFixed(3))} ${Number(height.toFixed(3))}`)
205
+ for (const layer of graphic.querySelectorAll('[data-prts-facility-layer], [data-prts-facility-clip]')) layer.setAttribute('d', path)
206
+ const silhouette = graphic.querySelector('[data-prts-facility-silhouette]')
207
+ if (silhouette) {
208
+ const availableHeight = Math.max(1, height - 2)
209
+ silhouette.setAttribute('transform', `translate(0 1) scale(1 ${Number((availableHeight / 38).toFixed(5))})`)
210
+ }
211
+ const pickup = graphic.querySelector('[data-prts-session-pickup]')
212
+ if (pickup) {
213
+ const center = Number((height / 2).toFixed(3))
214
+ for (const baseline of pickup.querySelectorAll('[data-prts-session-pickup-baseline]')) {
215
+ baseline.setAttribute('y1', String(center))
216
+ baseline.setAttribute('y2', String(center))
217
+ }
218
+ for (const indicator of pickup.querySelectorAll('[data-prts-session-pickup-indicator]')) {
219
+ indicator.setAttribute('cy', String(center))
220
+ }
221
+ for (const bar of pickup.querySelectorAll('[data-prts-session-pickup-bar]')) {
222
+ const barHeight = Number(bar.getAttribute('height')) || 0
223
+ bar.setAttribute('y', String(Number((center - barHeight / 2).toFixed(3))))
224
+ }
225
+ }
226
+ owner.setAttribute('data-prts-facility-size', size)
227
+ }
228
+ setReady(owner, true)
229
+ return true
230
+ }
@@ -0,0 +1,177 @@
1
+ const FLOATING_GLASS_OWNED_ATTRIBUTES = [
2
+ 'data-prts-floating-glass',
3
+ 'data-prts-floating-kind',
4
+ 'data-prts-floating-item',
5
+ 'data-prts-floating-input',
6
+ 'data-prts-floating-danger',
7
+ 'data-prts-floating-scrim',
8
+ 'data-prts-floating-nested',
9
+ 'data-prts-floating-preserved',
10
+ ]
11
+ const FLOATING_GLASS_OWNED_SELECTOR = FLOATING_GLASS_OWNED_ATTRIBUTES.map(attribute => `[${attribute}]`).join(', ')
12
+
13
+ const FLOATING_GLASS_POPUP_SELECTOR = [
14
+ '[role="menu"]',
15
+ '[role="listbox"]',
16
+ '[role="dialog"][aria-modal="true"]',
17
+ '[popover]',
18
+ ].join(', ')
19
+
20
+ const FLOATING_GLASS_PORTAL_ROOT_SELECTOR = [
21
+ '[role="presentation"]',
22
+ '[class*="_portal_"]',
23
+ '[data-floating-ui-portal]',
24
+ '[data-radix-portal]',
25
+ ].join(', ')
26
+
27
+ function markFloatingGlass(node, attribute, value = '') {
28
+ if (!node?.setAttribute) return
29
+ node.setAttribute(attribute, value)
30
+ }
31
+
32
+ function floatingClassHas(node, fragment) {
33
+ return [...(node?.classList ?? [])].some(token => token.includes(fragment))
34
+ }
35
+
36
+ function floatingPopupKind(node) {
37
+ if (node.matches('[role="dialog"]')) return 'dialog'
38
+ if (node.matches('[role="listbox"]')) return 'listbox'
39
+ if (node.matches('[role="menu"]')) return 'menu'
40
+ return 'popover'
41
+ }
42
+
43
+ function floatingVisible(node) {
44
+ return !node.hidden
45
+ && node.getAttribute('aria-hidden') !== 'true'
46
+ && node.style?.display !== 'none'
47
+ }
48
+
49
+ function floatingForeignPluginOwner(node) {
50
+ const owner = node.closest?.('[data-plugin]')
51
+ if (!owner) return null
52
+ const id = owner.getAttribute('data-plugin')
53
+ return id && id !== 'dsh-theme-prts' ? owner : null
54
+ }
55
+
56
+ function floatingHasCompleteCustomMaterial(window, node) {
57
+ let style
58
+ try {
59
+ style = window.getComputedStyle(node)
60
+ } catch {
61
+ return false
62
+ }
63
+ const radius = Number.parseFloat(style.borderRadius) || 0
64
+ const hasDepth = style.boxShadow !== 'none'
65
+ || style.backdropFilter !== 'none'
66
+ || style.webkitBackdropFilter !== 'none'
67
+ || style.backgroundImage !== 'none'
68
+ return radius >= 10 && hasDepth
69
+ }
70
+
71
+ function floatingCandidatesForBodyChild(node, body) {
72
+ if (node?.nodeType !== 1 || node.parentElement !== body) return []
73
+ if (node.matches(FLOATING_GLASS_POPUP_SELECTOR)) return [node]
74
+ if (!node.matches(FLOATING_GLASS_PORTAL_ROOT_SELECTOR)) return []
75
+ return [...node.querySelectorAll(FLOATING_GLASS_POPUP_SELECTOR)]
76
+ }
77
+
78
+ export function createFloatingGlassAdapter({ document, window }) {
79
+ let observer
80
+ const owned = new Set()
81
+ const classified = new WeakSet()
82
+
83
+ function own(node, attribute, value = '') {
84
+ markFloatingGlass(node, attribute, value)
85
+ owned.add(node)
86
+ }
87
+
88
+ function preserveThirdParty(node) {
89
+ if (node.closest?.('[data-prts-preserve-popup-style]')) return true
90
+ if (!floatingForeignPluginOwner(node) || !floatingHasCompleteCustomMaterial(window, node)) return false
91
+ own(node, 'data-prts-floating-preserved', 'custom')
92
+ return true
93
+ }
94
+
95
+ function classify(node) {
96
+ if (!floatingVisible(node) || classified.has(node)) return
97
+ classified.add(node)
98
+ if (preserveThirdParty(node)) return
99
+
100
+ const kind = floatingPopupKind(node)
101
+ own(node, 'data-prts-floating-glass', kind)
102
+ own(node, 'data-prts-floating-kind', kind)
103
+ if (node.parentElement?.closest?.(FLOATING_GLASS_POPUP_SELECTOR)) own(node, 'data-prts-floating-nested')
104
+
105
+ for (const item of node.querySelectorAll('[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]')) {
106
+ own(item, 'data-prts-floating-item')
107
+ if (floatingClassHas(item, '_danger_') || item.matches('[data-variant="danger"], [data-danger="true"]')) {
108
+ own(item, 'data-prts-floating-danger')
109
+ }
110
+ }
111
+
112
+ if (kind === 'dialog') {
113
+ const scrim = node.parentElement?.matches?.('[role="presentation"]') ? node.parentElement : null
114
+ if (scrim) own(scrim, 'data-prts-floating-scrim')
115
+ for (const input of node.querySelectorAll('input, textarea, select, [contenteditable="true"]')) {
116
+ own(input, 'data-prts-floating-input')
117
+ }
118
+ for (const danger of node.querySelectorAll('[class*="_danger_"], [class*="_deleteAction"], [data-variant="danger"], [data-danger="true"]')) {
119
+ own(danger, 'data-prts-floating-danger')
120
+ }
121
+ } else {
122
+ for (const input of node.querySelectorAll('input, textarea, select, [contenteditable="true"]')) {
123
+ own(input, 'data-prts-floating-input')
124
+ }
125
+ }
126
+ }
127
+
128
+ function scanBodyChild(node) {
129
+ for (const candidate of floatingCandidatesForBodyChild(node, document.body)) classify(candidate)
130
+ }
131
+
132
+ function scan() {
133
+ for (const child of document?.body?.children ?? []) scanBodyChild(child)
134
+ }
135
+
136
+ function releaseTree(node) {
137
+ if (node?.nodeType !== 1) return
138
+ const candidates = [
139
+ node,
140
+ ...(node.querySelectorAll?.(FLOATING_GLASS_OWNED_SELECTOR) ?? []),
141
+ ]
142
+ for (const candidate of candidates) {
143
+ if (!owned.has(candidate)) continue
144
+ for (const attribute of FLOATING_GLASS_OWNED_ATTRIBUTES) candidate.removeAttribute?.(attribute)
145
+ owned.delete(candidate)
146
+ classified.delete(candidate)
147
+ }
148
+ }
149
+
150
+ function clear() {
151
+ for (const node of owned) {
152
+ for (const attribute of FLOATING_GLASS_OWNED_ATTRIBUTES) node.removeAttribute?.(attribute)
153
+ }
154
+ owned.clear()
155
+ }
156
+
157
+ return {
158
+ start() {
159
+ if (!document?.body || observer) return
160
+ scan()
161
+ const Observer = window?.MutationObserver
162
+ if (!Observer) return
163
+ observer = new Observer(records => {
164
+ for (const record of records) {
165
+ for (const node of record.removedNodes) releaseTree(node)
166
+ for (const node of record.addedNodes) scanBodyChild(node)
167
+ }
168
+ })
169
+ observer.observe(document.body, { childList: true })
170
+ },
171
+ dispose() {
172
+ observer?.disconnect()
173
+ observer = undefined
174
+ clear()
175
+ },
176
+ }
177
+ }
@@ -0,0 +1,123 @@
1
+ const TOP_INSET_PROPERTY = '--prts-host-top-inset'
2
+ const RESIZE_SETTLE_DELAY = 160
3
+
4
+ function finiteInset(value, viewportHeight) {
5
+ const next = Number(value)
6
+ if (!Number.isFinite(next)) return 0
7
+ const limit = Math.max(0, Number(viewportHeight) || 0)
8
+ return Math.min(Math.max(0, next), limit)
9
+ }
10
+
11
+ function cssPixels(value) {
12
+ const rounded = Math.round(value * 100) / 100
13
+ return `${Object.is(rounded, -0) ? 0 : rounded}px`
14
+ }
15
+
16
+ export function createHostGeometryAdapter({ document, window }) {
17
+ const root = document?.documentElement
18
+ let frame
19
+ let resizeObserver
20
+ let frameRequest
21
+ let resizeSettleTimer
22
+ let observedFrameHeight
23
+ let previousValue
24
+ let previousPriority
25
+ let started = false
26
+
27
+ function measure() {
28
+ if (!started || !root || !frame?.isConnected) return 0
29
+ const top = finiteInset(frame.getBoundingClientRect?.().top, window?.innerHeight)
30
+ const value = cssPixels(top)
31
+ if (root.style.getPropertyValue(TOP_INSET_PROPERTY) !== value) {
32
+ root.style.setProperty(TOP_INSET_PROPERTY, value)
33
+ }
34
+ return top
35
+ }
36
+
37
+ function scheduleMeasure() {
38
+ if (!started || frameRequest !== undefined) return
39
+ if (typeof window?.requestAnimationFrame !== 'function') {
40
+ measure()
41
+ return
42
+ }
43
+ frameRequest = window.requestAnimationFrame(() => {
44
+ frameRequest = undefined
45
+ measure()
46
+ })
47
+ }
48
+
49
+ function scheduleSettledMeasure() {
50
+ scheduleMeasure()
51
+ }
52
+
53
+ function onWindowResize() {
54
+ if (!started) return
55
+ if (resizeSettleTimer !== undefined) window?.clearTimeout?.(resizeSettleTimer)
56
+ resizeSettleTimer = window?.setTimeout?.(() => {
57
+ resizeSettleTimer = undefined
58
+ scheduleMeasure()
59
+ }, RESIZE_SETTLE_DELAY)
60
+ }
61
+
62
+ function onGeometryResize(entries = []) {
63
+ let shouldMeasure = false
64
+ for (const entry of entries) {
65
+ if (entry.target !== frame) continue
66
+ const height = Number(entry.contentRect?.height)
67
+ if (!Number.isFinite(height) || height <= 0) {
68
+ shouldMeasure = true
69
+ } else if (height !== observedFrameHeight) {
70
+ observedFrameHeight = height
71
+ shouldMeasure = true
72
+ }
73
+ }
74
+ if (shouldMeasure || !entries.length) scheduleMeasure()
75
+ }
76
+
77
+ function stopObservation() {
78
+ resizeObserver?.disconnect?.()
79
+ resizeObserver = undefined
80
+ window?.removeEventListener?.('resize', onWindowResize)
81
+ if (resizeSettleTimer !== undefined) window?.clearTimeout?.(resizeSettleTimer)
82
+ resizeSettleTimer = undefined
83
+ if (frameRequest !== undefined) window?.cancelAnimationFrame?.(frameRequest)
84
+ frameRequest = undefined
85
+ }
86
+
87
+ function dispose() {
88
+ if (!started) return
89
+ stopObservation()
90
+ if (previousValue) root.style.setProperty(TOP_INSET_PROPERTY, previousValue, previousPriority)
91
+ else root.style.removeProperty(TOP_INSET_PROPERTY)
92
+ previousValue = undefined
93
+ previousPriority = undefined
94
+ frame = undefined
95
+ observedFrameHeight = undefined
96
+ started = false
97
+ }
98
+
99
+ return {
100
+ start(nextFrame) {
101
+ if (!root || !nextFrame) return false
102
+ if (started && frame === nextFrame) {
103
+ scheduleSettledMeasure()
104
+ return true
105
+ }
106
+ if (started) dispose()
107
+ frame = nextFrame
108
+ previousValue = root.style.getPropertyValue(TOP_INSET_PROPERTY)
109
+ previousPriority = root.style.getPropertyPriority(TOP_INSET_PROPERTY)
110
+ started = true
111
+ measure()
112
+ if (typeof window?.ResizeObserver === 'function') {
113
+ resizeObserver = new window.ResizeObserver(onGeometryResize)
114
+ resizeObserver.observe(frame)
115
+ }
116
+ window?.addEventListener?.('resize', onWindowResize, { passive: true })
117
+ scheduleSettledMeasure()
118
+ return true
119
+ },
120
+ refresh: measure,
121
+ dispose,
122
+ }
123
+ }