@rcarls/rc-virtual-canvas 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/custom-elements.json +75 -29
- package/dist/{rc-virtual-canvas-BJQbqNEu.js → rc-virtual-canvas-CUVahMP7.js} +114 -29
- package/dist/rc-virtual-canvas-CUVahMP7.js.map +1 -0
- package/dist/rc-virtual-canvas-define.js +1 -1
- package/dist/rc-virtual-canvas.js +1 -1
- package/dist/types/rc-virtual-canvas.d.ts +145 -10
- package/package.json +6 -8
- package/dist/default_tiles.png +0 -0
- package/dist/demo.css +0 -85
- package/dist/demo.js +0 -40
- package/dist/rc-virtual-canvas-BJQbqNEu.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# `@rcarls/rc-virtual-canvas`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scrollable virtual canvas for rendering large coordinate-space content.
|
|
4
|
+
|
|
5
|
+
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-virtual-canvas](https://richardcarls.github.io/rc-webcomponents/components/rc-virtual-canvas).
|
|
6
|
+
|
|
7
|
+
Overlays a transparent scrollable div on top of a slotted `<canvas>` element, dispatching
|
|
8
|
+
`rc-virtual-canvas-render` events with the current viewport and content rectangles.
|
|
9
|
+
Consumers draw only what is visible.
|
|
4
10
|
|
|
5
11
|
---
|
|
6
12
|
|
|
@@ -21,7 +27,9 @@ import { RCVirtualCanvas } from '@rcarls/rc-virtual-canvas'; // named class e
|
|
|
21
27
|
|
|
22
28
|
## Basic usage
|
|
23
29
|
|
|
24
|
-
Place a `<canvas>` inside the component. Set `contentWidth` and `contentHeight` to the
|
|
30
|
+
Place a `<canvas>` inside the component. Set `contentWidth` and `contentHeight` to the
|
|
31
|
+
total size of your virtual content. On each `rc-virtual-canvas-render` event, use
|
|
32
|
+
`detail.viewRect` to determine which portion of that content is visible and draw accordingly.
|
|
25
33
|
|
|
26
34
|
```html
|
|
27
35
|
<rc-virtual-canvas
|
|
@@ -47,7 +55,9 @@ Place a `<canvas>` inside the component. Set `contentWidth` and `contentHeight`
|
|
|
47
55
|
</script>
|
|
48
56
|
```
|
|
49
57
|
|
|
50
|
-
The canvas element should match the component's rendered size (set via CSS). The virtual
|
|
58
|
+
The canvas element should match the component's rendered size (set via CSS). The virtual
|
|
59
|
+
content is larger — scrolling is handled by an absolutely-positioned transparent `<div>`
|
|
60
|
+
that triggers native scroll events.
|
|
51
61
|
|
|
52
62
|
---
|
|
53
63
|
|
|
@@ -63,6 +73,13 @@ The canvas element should match the component's rendered size (set via CSS). The
|
|
|
63
73
|
| `renderMode` | `render-mode` | `'continuous' \| 'viewport-change' \| 'manual'` | `'continuous'` | Controls when render events are dispatched |
|
|
64
74
|
| `imageRendering` | `image-rendering` | `'auto' \| 'crisp-edges' \| 'pixelated'` | `'auto'` | Convenience value applied to the slotted canvas via `image-rendering` |
|
|
65
75
|
|
|
76
|
+
### Read-only properties
|
|
77
|
+
|
|
78
|
+
| Property | Type | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `canvasScaleX` | `number` | Ratio of canvas backing-store pixels to CSS pixels along the x-axis (≈ `devicePixelRatio` when `autoResizeCanvas` is `true`) |
|
|
81
|
+
| `canvasScaleY` | `number` | Ratio of canvas backing-store pixels to CSS pixels along the y-axis |
|
|
82
|
+
|
|
66
83
|
### Methods
|
|
67
84
|
|
|
68
85
|
| Method | Description |
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"declarations": [
|
|
47
47
|
{
|
|
48
48
|
"kind": "class",
|
|
49
|
-
"description": "
|
|
49
|
+
"description": "A scrollable virtual canvas component.",
|
|
50
50
|
"name": "RCVirtualCanvas",
|
|
51
51
|
"slots": [
|
|
52
52
|
{
|
|
@@ -161,7 +161,8 @@
|
|
|
161
161
|
"type": {
|
|
162
162
|
"text": "HTMLDivElement"
|
|
163
163
|
},
|
|
164
|
-
"privacy": "protected"
|
|
164
|
+
"privacy": "protected",
|
|
165
|
+
"description": "Shadow DOM scroll container that owns pointer interaction and native scrolling"
|
|
165
166
|
},
|
|
166
167
|
{
|
|
167
168
|
"kind": "field",
|
|
@@ -169,7 +170,8 @@
|
|
|
169
170
|
"type": {
|
|
170
171
|
"text": "HTMLDivElement"
|
|
171
172
|
},
|
|
172
|
-
"privacy": "protected"
|
|
173
|
+
"privacy": "protected",
|
|
174
|
+
"description": "Absolutely-positioned div that establishes the virtual scroll range"
|
|
173
175
|
},
|
|
174
176
|
{
|
|
175
177
|
"kind": "field",
|
|
@@ -178,7 +180,8 @@
|
|
|
178
180
|
"text": "HTMLCanvasElement | null"
|
|
179
181
|
},
|
|
180
182
|
"privacy": "protected",
|
|
181
|
-
"default": "null"
|
|
183
|
+
"default": "null",
|
|
184
|
+
"description": "Currently-tracked slotted canvas element; `null` when no canvas is slotted"
|
|
182
185
|
},
|
|
183
186
|
{
|
|
184
187
|
"kind": "field",
|
|
@@ -187,7 +190,8 @@
|
|
|
187
190
|
"text": "object"
|
|
188
191
|
},
|
|
189
192
|
"privacy": "protected",
|
|
190
|
-
"default": "{ x: 0, y: 0, width: 0, height: 0, }"
|
|
193
|
+
"default": "{ x: 0, y: 0, width: 0, height: 0, }",
|
|
194
|
+
"description": "Mutable current-frame viewport geometry; call `getViewRect()` for a frozen snapshot"
|
|
191
195
|
},
|
|
192
196
|
{
|
|
193
197
|
"kind": "field",
|
|
@@ -196,18 +200,21 @@
|
|
|
196
200
|
"text": "object"
|
|
197
201
|
},
|
|
198
202
|
"privacy": "protected",
|
|
199
|
-
"default": "{ x: 0, y: 0, width: 0, height: 0, }"
|
|
203
|
+
"default": "{ x: 0, y: 0, width: 0, height: 0, }",
|
|
204
|
+
"description": "Mutable content bounds; updated when `contentWidth` or `contentHeight` change"
|
|
200
205
|
},
|
|
201
206
|
{
|
|
202
207
|
"kind": "field",
|
|
203
208
|
"name": "_resizeObserver",
|
|
204
209
|
"privacy": "protected",
|
|
205
|
-
"default": "new ResizeObserver(
|
|
210
|
+
"default": "new ResizeObserver((entries: ResizeObserverEntry[]) => { for (const entry of entries) { const devicePixelBoxSize = Array.isArray(entry.devicePixelContentBoxSize) ? entry.devicePixelContentBoxSize[0] : entry.devicePixelContentBoxSize; this._viewRect.width = this._getMeasuredCanvasWidth( devicePixelBoxSize?.inlineSize ?? Math.round(entry.contentRect.width * window.devicePixelRatio), ); this._viewRect.height = this._getMeasuredCanvasHeight( devicePixelBoxSize?.blockSize ?? Math.round(entry.contentRect.height * window.devicePixelRatio), ); this._syncCanvasBackingStore(); this._scheduleRender('viewport-change'); } })",
|
|
211
|
+
"description": "Watches the slotted canvas for DPR-accurate dimension changes"
|
|
206
212
|
},
|
|
207
213
|
{
|
|
208
214
|
"kind": "method",
|
|
209
215
|
"name": "_onScroll",
|
|
210
|
-
"privacy": "protected"
|
|
216
|
+
"privacy": "protected",
|
|
217
|
+
"description": "Reads scroll position from the root container and schedules a render"
|
|
211
218
|
},
|
|
212
219
|
{
|
|
213
220
|
"kind": "method",
|
|
@@ -220,11 +227,13 @@
|
|
|
220
227
|
"text": "Event"
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
|
-
]
|
|
230
|
+
],
|
|
231
|
+
"description": "Replaces canvas tracking and re-registers the resize observer when slot content changes"
|
|
224
232
|
},
|
|
225
233
|
{
|
|
226
234
|
"kind": "method",
|
|
227
|
-
"name": "getViewRect"
|
|
235
|
+
"name": "getViewRect",
|
|
236
|
+
"description": "Returns a frozen snapshot of the current viewport rectangle.\n\nSafe to keep between frames — the returned object will not be mutated\nby future scroll or resize work."
|
|
228
237
|
},
|
|
229
238
|
{
|
|
230
239
|
"kind": "method",
|
|
@@ -234,22 +243,26 @@
|
|
|
234
243
|
"name": "x",
|
|
235
244
|
"type": {
|
|
236
245
|
"text": "number"
|
|
237
|
-
}
|
|
246
|
+
},
|
|
247
|
+
"description": "Content x coordinate in CSS pixels"
|
|
238
248
|
},
|
|
239
249
|
{
|
|
240
250
|
"name": "y",
|
|
241
251
|
"type": {
|
|
242
252
|
"text": "number"
|
|
243
|
-
}
|
|
253
|
+
},
|
|
254
|
+
"description": "Content y coordinate in CSS pixels"
|
|
244
255
|
},
|
|
245
256
|
{
|
|
246
257
|
"name": "options",
|
|
247
258
|
"default": "{}",
|
|
248
259
|
"type": {
|
|
249
260
|
"text": "ScrollOptions"
|
|
250
|
-
}
|
|
261
|
+
},
|
|
262
|
+
"description": "Native scroll options passed to scrollTo"
|
|
251
263
|
}
|
|
252
|
-
]
|
|
264
|
+
],
|
|
265
|
+
"description": "Scrolls so the content coordinate is at the viewport origin."
|
|
253
266
|
},
|
|
254
267
|
{
|
|
255
268
|
"kind": "method",
|
|
@@ -259,22 +272,26 @@
|
|
|
259
272
|
"name": "x",
|
|
260
273
|
"type": {
|
|
261
274
|
"text": "number"
|
|
262
|
-
}
|
|
275
|
+
},
|
|
276
|
+
"description": "Content x coordinate in CSS pixels"
|
|
263
277
|
},
|
|
264
278
|
{
|
|
265
279
|
"name": "y",
|
|
266
280
|
"type": {
|
|
267
281
|
"text": "number"
|
|
268
|
-
}
|
|
282
|
+
},
|
|
283
|
+
"description": "Content y coordinate in CSS pixels"
|
|
269
284
|
},
|
|
270
285
|
{
|
|
271
286
|
"name": "options",
|
|
272
287
|
"default": "{}",
|
|
273
288
|
"type": {
|
|
274
289
|
"text": "ScrollOptions"
|
|
275
|
-
}
|
|
290
|
+
},
|
|
291
|
+
"description": "Native scroll options passed to scrollTo"
|
|
276
292
|
}
|
|
277
|
-
]
|
|
293
|
+
],
|
|
294
|
+
"description": "Scrolls so the content coordinate is centered in the viewport."
|
|
278
295
|
},
|
|
279
296
|
{
|
|
280
297
|
"kind": "method",
|
|
@@ -284,15 +301,18 @@
|
|
|
284
301
|
"name": "clientX",
|
|
285
302
|
"type": {
|
|
286
303
|
"text": "number"
|
|
287
|
-
}
|
|
304
|
+
},
|
|
305
|
+
"description": "Browser client x coordinate (e.g. from a pointer event)"
|
|
288
306
|
},
|
|
289
307
|
{
|
|
290
308
|
"name": "clientY",
|
|
291
309
|
"type": {
|
|
292
310
|
"text": "number"
|
|
293
|
-
}
|
|
311
|
+
},
|
|
312
|
+
"description": "Browser client y coordinate"
|
|
294
313
|
}
|
|
295
|
-
]
|
|
314
|
+
],
|
|
315
|
+
"description": "Converts browser client coordinates to content coordinates using the current\nbacking-store scale."
|
|
296
316
|
},
|
|
297
317
|
{
|
|
298
318
|
"kind": "method",
|
|
@@ -302,15 +322,18 @@
|
|
|
302
322
|
"name": "x",
|
|
303
323
|
"type": {
|
|
304
324
|
"text": "number"
|
|
305
|
-
}
|
|
325
|
+
},
|
|
326
|
+
"description": "Content x coordinate in CSS pixels"
|
|
306
327
|
},
|
|
307
328
|
{
|
|
308
329
|
"name": "y",
|
|
309
330
|
"type": {
|
|
310
331
|
"text": "number"
|
|
311
|
-
}
|
|
332
|
+
},
|
|
333
|
+
"description": "Content y coordinate in CSS pixels"
|
|
312
334
|
}
|
|
313
|
-
]
|
|
335
|
+
],
|
|
336
|
+
"description": "Converts content coordinates back to browser client coordinates."
|
|
314
337
|
},
|
|
315
338
|
{
|
|
316
339
|
"kind": "method",
|
|
@@ -321,9 +344,29 @@
|
|
|
321
344
|
"default": "'manual'",
|
|
322
345
|
"type": {
|
|
323
346
|
"text": "RCVirtualCanvasRenderReason"
|
|
324
|
-
}
|
|
347
|
+
},
|
|
348
|
+
"description": "Render reason reported in the event detail"
|
|
325
349
|
}
|
|
326
|
-
]
|
|
350
|
+
],
|
|
351
|
+
"description": "Queues a render event on the next animation frame.\n\nRequired when `renderMode` is `'manual'`. In other modes the component\nschedules renders automatically; calling this with reason `'animation-frame'`\nis a no-op when `renderMode` is `'viewport-change'`."
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"kind": "field",
|
|
355
|
+
"name": "canvasScaleX",
|
|
356
|
+
"type": {
|
|
357
|
+
"text": "number"
|
|
358
|
+
},
|
|
359
|
+
"description": "Ratio of canvas backing-store pixels to CSS pixels along the x-axis.\n\nEquals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the x-scale\nfactor in `ctx.scale()` to draw at CSS pixel resolution.",
|
|
360
|
+
"readonly": true
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"kind": "field",
|
|
364
|
+
"name": "canvasScaleY",
|
|
365
|
+
"type": {
|
|
366
|
+
"text": "number"
|
|
367
|
+
},
|
|
368
|
+
"description": "Ratio of canvas backing-store pixels to CSS pixels along the y-axis.\n\nEquals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the y-scale\nfactor in `ctx.scale()` to draw at CSS pixel resolution.",
|
|
369
|
+
"readonly": true
|
|
327
370
|
},
|
|
328
371
|
{
|
|
329
372
|
"kind": "method",
|
|
@@ -336,7 +379,8 @@
|
|
|
336
379
|
"text": "PointerEvent | MouseEvent"
|
|
337
380
|
}
|
|
338
381
|
}
|
|
339
|
-
]
|
|
382
|
+
],
|
|
383
|
+
"description": "Maps pointer and mouse events to content coordinates and dispatches `rc-virtual-canvas-pointer`"
|
|
340
384
|
},
|
|
341
385
|
{
|
|
342
386
|
"kind": "method",
|
|
@@ -365,9 +409,11 @@
|
|
|
365
409
|
"name": "time",
|
|
366
410
|
"type": {
|
|
367
411
|
"text": "DOMHighResTimeStamp"
|
|
368
|
-
}
|
|
412
|
+
},
|
|
413
|
+
"description": "High-resolution timestamp from `requestAnimationFrame`"
|
|
369
414
|
}
|
|
370
|
-
]
|
|
415
|
+
],
|
|
416
|
+
"description": "RAF callback. Dispatches `rc-virtual-canvas-render` and re-schedules in continuous mode."
|
|
371
417
|
},
|
|
372
418
|
{
|
|
373
419
|
"kind": "method",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { css as _, LitElement as p, html as g } from "lit";
|
|
2
|
-
import { property as h, query as u, state as
|
|
2
|
+
import { property as h, query as u, state as d } from "lit/decorators.js";
|
|
3
3
|
import { styleMap as b } from "lit/directives/style-map.js";
|
|
4
4
|
const w = _`
|
|
5
5
|
*,
|
|
@@ -70,9 +70,9 @@ const w = _`
|
|
|
70
70
|
image-rendering: var(--rc-virtual-canvas-image-rendering, auto);
|
|
71
71
|
}
|
|
72
72
|
`;
|
|
73
|
-
var m = Object.defineProperty,
|
|
74
|
-
for (var s = i > 1 ? void 0 : i ?
|
|
75
|
-
(
|
|
73
|
+
var m = Object.defineProperty, R = Object.getOwnPropertyDescriptor, o = (r, t, e, i) => {
|
|
74
|
+
for (var s = i > 1 ? void 0 : i ? R(t, e) : t, a = r.length - 1, l; a >= 0; a--)
|
|
75
|
+
(l = r[a]) && (s = (i ? l(t, e, s) : l(s)) || s);
|
|
76
76
|
return i && s && m(t, e, s), s;
|
|
77
77
|
};
|
|
78
78
|
function c(r) {
|
|
@@ -95,20 +95,16 @@ const v = class v extends p {
|
|
|
95
95
|
y: 0,
|
|
96
96
|
width: 0,
|
|
97
97
|
height: 0
|
|
98
|
-
}, this._resizeObserver = new ResizeObserver(
|
|
99
|
-
(t)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
), this._viewRect.height = this._getMeasuredCanvasHeight(
|
|
107
|
-
i?.blockSize ?? Math.round(e.contentRect.height * window.devicePixelRatio)
|
|
108
|
-
), this._syncCanvasBackingStore(), this._scheduleRender("viewport-change");
|
|
109
|
-
}
|
|
98
|
+
}, this._resizeObserver = new ResizeObserver((t) => {
|
|
99
|
+
for (const e of t) {
|
|
100
|
+
const i = Array.isArray(e.devicePixelContentBoxSize) ? e.devicePixelContentBoxSize[0] : e.devicePixelContentBoxSize;
|
|
101
|
+
this._viewRect.width = this._getMeasuredCanvasWidth(
|
|
102
|
+
i?.inlineSize ?? Math.round(e.contentRect.width * window.devicePixelRatio)
|
|
103
|
+
), this._viewRect.height = this._getMeasuredCanvasHeight(
|
|
104
|
+
i?.blockSize ?? Math.round(e.contentRect.height * window.devicePixelRatio)
|
|
105
|
+
), this._syncCanvasBackingStore(), this._scheduleRender("viewport-change");
|
|
110
106
|
}
|
|
111
|
-
);
|
|
107
|
+
});
|
|
112
108
|
}
|
|
113
109
|
set contentWidth(t) {
|
|
114
110
|
const e = this._contentWidth;
|
|
@@ -124,9 +120,11 @@ const v = class v extends p {
|
|
|
124
120
|
get contentHeight() {
|
|
125
121
|
return this._contentHeight;
|
|
126
122
|
}
|
|
123
|
+
/** Reads scroll position from the root container and schedules a render */
|
|
127
124
|
_onScroll() {
|
|
128
125
|
this._viewRect.x = this._$root.scrollLeft, this._viewRect.y = this._$root.scrollTop, this._scheduleRender("viewport-change");
|
|
129
126
|
}
|
|
127
|
+
/** Replaces canvas tracking and re-registers the resize observer when slot content changes */
|
|
130
128
|
_onSlotChange(t) {
|
|
131
129
|
this._resizeObserver.disconnect(), this._$canvas = t.currentTarget.assignedElements().filter((e) => e instanceof HTMLCanvasElement).at(0) ?? null, this._$canvas != null && (this._viewRect = {
|
|
132
130
|
x: this._$root.scrollLeft ?? 0,
|
|
@@ -137,9 +135,22 @@ const v = class v extends p {
|
|
|
137
135
|
box: "device-pixel-content-box"
|
|
138
136
|
}), this._scheduleRender("viewport-change"));
|
|
139
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns a frozen snapshot of the current viewport rectangle.
|
|
140
|
+
*
|
|
141
|
+
* Safe to keep between frames — the returned object will not be mutated
|
|
142
|
+
* by future scroll or resize work.
|
|
143
|
+
*/
|
|
140
144
|
getViewRect() {
|
|
141
145
|
return c(this._viewRect);
|
|
142
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Scrolls so the content coordinate is at the viewport origin.
|
|
149
|
+
*
|
|
150
|
+
* @param x - Content x coordinate in CSS pixels
|
|
151
|
+
* @param y - Content y coordinate in CSS pixels
|
|
152
|
+
* @param options - Native scroll options passed to scrollTo
|
|
153
|
+
*/
|
|
143
154
|
scrollToContent(t, e, i = {}) {
|
|
144
155
|
this._$root.scrollTo({
|
|
145
156
|
...i,
|
|
@@ -147,6 +158,13 @@ const v = class v extends p {
|
|
|
147
158
|
top: e
|
|
148
159
|
}), this._viewRect.x = this._$root.scrollLeft, this._viewRect.y = this._$root.scrollTop, this._scheduleRender("viewport-change");
|
|
149
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Scrolls so the content coordinate is centered in the viewport.
|
|
163
|
+
*
|
|
164
|
+
* @param x - Content x coordinate in CSS pixels
|
|
165
|
+
* @param y - Content y coordinate in CSS pixels
|
|
166
|
+
* @param options - Native scroll options passed to scrollTo
|
|
167
|
+
*/
|
|
150
168
|
centerOnContent(t, e, i = {}) {
|
|
151
169
|
this.scrollToContent(
|
|
152
170
|
t - this._$root.clientWidth * 0.5,
|
|
@@ -154,6 +172,19 @@ const v = class v extends p {
|
|
|
154
172
|
i
|
|
155
173
|
);
|
|
156
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Converts browser client coordinates to content coordinates using the current
|
|
177
|
+
* backing-store scale.
|
|
178
|
+
*
|
|
179
|
+
* @param clientX - Browser client x coordinate (e.g. from a pointer event)
|
|
180
|
+
* @param clientY - Browser client y coordinate
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* vc.addEventListener('pointerdown', (e) => {
|
|
184
|
+
* const { x, y } = vc.clientToContent(e.clientX, e.clientY);
|
|
185
|
+
* placeMarkerAt(x, y);
|
|
186
|
+
* });
|
|
187
|
+
*/
|
|
157
188
|
clientToContent(t, e) {
|
|
158
189
|
const i = this._getCanvasClientRect(), s = this._getViewportScaleX(i), a = this._getViewportScaleY(i);
|
|
159
190
|
return Object.freeze({
|
|
@@ -161,6 +192,17 @@ const v = class v extends p {
|
|
|
161
192
|
y: this._viewRect.y + (e - i.top) * a
|
|
162
193
|
});
|
|
163
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Converts content coordinates back to browser client coordinates.
|
|
197
|
+
*
|
|
198
|
+
* @param x - Content x coordinate in CSS pixels
|
|
199
|
+
* @param y - Content y coordinate in CSS pixels
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const { x, y } = vc.contentToClient(contentX, contentY);
|
|
203
|
+
* tooltip.style.left = `${x}px`;
|
|
204
|
+
* tooltip.style.top = `${y}px`;
|
|
205
|
+
*/
|
|
164
206
|
contentToClient(t, e) {
|
|
165
207
|
const i = this._getCanvasClientRect(), s = this._getViewportScaleX(i), a = this._getViewportScaleY(i);
|
|
166
208
|
return Object.freeze({
|
|
@@ -168,11 +210,50 @@ const v = class v extends p {
|
|
|
168
210
|
y: i.top + (e - this._viewRect.y) / a
|
|
169
211
|
});
|
|
170
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Queues a render event on the next animation frame.
|
|
215
|
+
*
|
|
216
|
+
* Required when `renderMode` is `'manual'`. In other modes the component
|
|
217
|
+
* schedules renders automatically; calling this with reason `'animation-frame'`
|
|
218
|
+
* is a no-op when `renderMode` is `'viewport-change'`.
|
|
219
|
+
*
|
|
220
|
+
* @param reason - Render reason reported in the event detail
|
|
221
|
+
*/
|
|
171
222
|
requestRender(t = "manual") {
|
|
172
223
|
this._scheduleRender(t);
|
|
173
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Ratio of canvas backing-store pixels to CSS pixels along the x-axis.
|
|
227
|
+
*
|
|
228
|
+
* Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the x-scale
|
|
229
|
+
* factor in `ctx.scale()` to draw at CSS pixel resolution.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* vc.addEventListener('rc-virtual-canvas-render', ({ detail: { viewRect } }) => {
|
|
233
|
+
* ctx.save();
|
|
234
|
+
* ctx.scale(vc.canvasScaleX, vc.canvasScaleY);
|
|
235
|
+
* drawScene(ctx, viewRect);
|
|
236
|
+
* ctx.restore();
|
|
237
|
+
* });
|
|
238
|
+
*/
|
|
239
|
+
get canvasScaleX() {
|
|
240
|
+
const t = this._getCanvasClientRect();
|
|
241
|
+
return this._getViewportScaleX(t);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Ratio of canvas backing-store pixels to CSS pixels along the y-axis.
|
|
245
|
+
*
|
|
246
|
+
* Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the y-scale
|
|
247
|
+
* factor in `ctx.scale()` to draw at CSS pixel resolution.
|
|
248
|
+
*/
|
|
249
|
+
get canvasScaleY() {
|
|
250
|
+
const t = this._getCanvasClientRect();
|
|
251
|
+
return this._getViewportScaleY(t);
|
|
252
|
+
}
|
|
253
|
+
/** Maps pointer and mouse events to content coordinates and dispatches `rc-virtual-canvas-pointer` */
|
|
174
254
|
_onPointerEvent(t) {
|
|
175
|
-
if (this._handledPointerEvents.has(t) || this._isOverlayEvent(t))
|
|
255
|
+
if (this._handledPointerEvents.has(t) || this._isOverlayEvent(t))
|
|
256
|
+
return;
|
|
176
257
|
this._handledPointerEvents.add(t);
|
|
177
258
|
const e = this.clientToContent(t.clientX, t.clientY);
|
|
178
259
|
this.dispatchEvent(
|
|
@@ -199,10 +280,13 @@ const v = class v extends p {
|
|
|
199
280
|
) || t.preventDefault();
|
|
200
281
|
}
|
|
201
282
|
_isOverlayEvent(t) {
|
|
202
|
-
return t.composedPath().some(
|
|
203
|
-
(e) => e instanceof HTMLSlotElement && e.name === "overlay"
|
|
204
|
-
);
|
|
283
|
+
return t.composedPath().some((e) => e instanceof HTMLSlotElement && e.name === "overlay");
|
|
205
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* RAF callback. Dispatches `rc-virtual-canvas-render` and re-schedules in continuous mode.
|
|
287
|
+
*
|
|
288
|
+
* @param time - High-resolution timestamp from `requestAnimationFrame`
|
|
289
|
+
*/
|
|
206
290
|
_update(t) {
|
|
207
291
|
if (this._rafHandle = 0, !this.isConnected || this._$canvas == null)
|
|
208
292
|
return;
|
|
@@ -220,17 +304,17 @@ const v = class v extends p {
|
|
|
220
304
|
})
|
|
221
305
|
), this.renderMode === "continuous" && this._scheduleRender("animation-frame");
|
|
222
306
|
}
|
|
307
|
+
/** Registers pointer event listeners and starts rendering in continuous mode */
|
|
223
308
|
connectedCallback() {
|
|
224
309
|
super.connectedCallback(), this.addEventListener("pointerdown", this._boundPointerEvent), this.addEventListener("pointermove", this._boundPointerEvent), this.addEventListener("pointerup", this._boundPointerEvent), this.addEventListener("click", this._boundPointerEvent), this.addEventListener("dblclick", this._boundPointerEvent), this.addEventListener("contextmenu", this._boundPointerEvent), this._rafHandle && (cancelAnimationFrame(this._rafHandle), this._rafHandle = 0), this.renderMode === "continuous" && this._scheduleRender("animation-frame");
|
|
225
310
|
}
|
|
311
|
+
/** Cancels any pending animation frame, disconnects the resize observer, and removes pointer listeners */
|
|
226
312
|
disconnectedCallback() {
|
|
227
313
|
super.disconnectedCallback(), cancelAnimationFrame(this._rafHandle), this._rafHandle = 0, this._resizeObserver.disconnect(), this.removeEventListener("pointerdown", this._boundPointerEvent), this.removeEventListener("pointermove", this._boundPointerEvent), this.removeEventListener("pointerup", this._boundPointerEvent), this.removeEventListener("click", this._boundPointerEvent), this.removeEventListener("dblclick", this._boundPointerEvent), this.removeEventListener("contextmenu", this._boundPointerEvent);
|
|
228
314
|
}
|
|
315
|
+
/** Reacts to `renderMode`, `autoResizeCanvas`, and `imageRendering` property changes */
|
|
229
316
|
updated(t) {
|
|
230
|
-
t.has("renderMode") && (this.renderMode === "continuous" ? this._scheduleRender("animation-frame") : this._pendingRenderReason === "animation-frame" ? (this._rafHandle && (cancelAnimationFrame(this._rafHandle), this._rafHandle = 0), this._pendingRenderReason = void 0) : this.renderMode === "viewport-change" && this._scheduleRender("viewport-change")), t.has("autoResizeCanvas") && this._syncCanvasBackingStore(), t.has("imageRendering") && this.style.setProperty(
|
|
231
|
-
"--rc-virtual-canvas-image-rendering",
|
|
232
|
-
this.imageRendering
|
|
233
|
-
);
|
|
317
|
+
t.has("renderMode") && (this.renderMode === "continuous" ? this._scheduleRender("animation-frame") : this._pendingRenderReason === "animation-frame" ? (this._rafHandle && (cancelAnimationFrame(this._rafHandle), this._rafHandle = 0), this._pendingRenderReason = void 0) : this.renderMode === "viewport-change" && this._scheduleRender("viewport-change")), t.has("autoResizeCanvas") && this._syncCanvasBackingStore(), t.has("imageRendering") && this.style.setProperty("--rc-virtual-canvas-image-rendering", this.imageRendering);
|
|
234
318
|
}
|
|
235
319
|
_scheduleRender(t) {
|
|
236
320
|
this.renderMode === "manual" && t !== "manual" || this.renderMode === "viewport-change" && t === "animation-frame" || (this._pendingRenderReason = t, !(!this.isConnected || this._rafHandle) && (this._rafHandle = window.requestAnimationFrame(this._boundUpdate)));
|
|
@@ -272,6 +356,7 @@ const v = class v extends p {
|
|
|
272
356
|
<div id="overlay" part="overlay">
|
|
273
357
|
<slot name="overlay"></slot>
|
|
274
358
|
</div>
|
|
359
|
+
|
|
275
360
|
<div
|
|
276
361
|
id="placeholder"
|
|
277
362
|
style=${b({
|
|
@@ -309,15 +394,15 @@ o([
|
|
|
309
394
|
u("#placeholder", !0)
|
|
310
395
|
], n.prototype, "_$placeholder", 2);
|
|
311
396
|
o([
|
|
312
|
-
|
|
397
|
+
d()
|
|
313
398
|
], n.prototype, "_$canvas", 2);
|
|
314
399
|
o([
|
|
315
|
-
|
|
400
|
+
d()
|
|
316
401
|
], n.prototype, "_viewRect", 2);
|
|
317
402
|
o([
|
|
318
|
-
|
|
403
|
+
d()
|
|
319
404
|
], n.prototype, "_contentRect", 2);
|
|
320
405
|
export {
|
|
321
406
|
n as R
|
|
322
407
|
};
|
|
323
|
-
//# sourceMappingURL=rc-virtual-canvas-
|
|
408
|
+
//# sourceMappingURL=rc-virtual-canvas-CUVahMP7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-virtual-canvas-CUVahMP7.js","sources":["../src/rc-virtual-canvas.styles.ts","../src/rc-virtual-canvas.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const virtualCanvasStyles = css`\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n :host {\n position: relative;\n display: block;\n width: max-content;\n line-height: 0; /* remove extra space under slotted <canvas> element(s) */\n }\n\n :host([hidden]) {\n display: none;\n }\n\n #root {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n\n overflow: auto;\n scrollbar-width: var(--rc-virtual-canvas-scrollbar-width, thin);\n }\n\n #root::-webkit-scrollbar {\n width: var(--rc-virtual-canvas-scrollbar-size, auto);\n height: var(--rc-virtual-canvas-scrollbar-size, auto);\n }\n\n #root::-webkit-scrollbar-thumb {\n background: var(--rc-virtual-canvas-scrollbar-thumb-background, ButtonFace);\n }\n\n #root::-webkit-scrollbar-button {\n background: var(--rc-virtual-canvas-scrollbar-button-background, ButtonFace);\n }\n\n #root::-webkit-scrollbar-track {\n background: var(--rc-virtual-canvas-scrollbar-track-background, Canvas);\n }\n\n #placeholder {\n pointer-events: none;\n }\n\n #overlay {\n position: sticky;\n left: 0;\n top: 0;\n z-index: 1;\n display: block;\n width: 100%;\n height: 0;\n pointer-events: none;\n }\n\n ::slotted([slot='overlay']) {\n pointer-events: auto;\n }\n\n ::slotted(canvas) {\n image-rendering: var(--rc-virtual-canvas-image-rendering, auto);\n }\n`;\n\nexport default virtualCanvasStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { styleMap } from 'lit/directives/style-map.js';\n\nimport virtualCanvasStyles from './rc-virtual-canvas.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-virtual-canvas': RCVirtualCanvas;\n }\n\n interface HTMLElementEventMap {\n 'rc-virtual-canvas-render': CustomEvent<RCVirtualCanvasRenderInit>;\n 'rc-virtual-canvas-pointer': CustomEvent<RCVirtualCanvasPointerInit>;\n }\n}\n\n/**\n * Scrollable virtual canvas for rendering large coordinate-space content.\n *\n * `x` and `y` are scroll offsets in CSS pixels; `width` and `height` are the canvas\n * backing-store dimensions in device pixels (DPR-accurate when `autoResizeCanvas` is `true`).\n */\nexport type RCVirtualCanvasViewRect = Readonly<{\n /** Scroll offset x in content space (CSS px) */\n x: number;\n\n /** Scroll offset y in content space (CSS px) */\n y: number;\n\n /** Visible width in device pixels */\n width: number;\n\n /** Visible height in device pixels */\n height: number;\n}>;\n\n/** Controls when `rc-virtual-canvas-render` events are dispatched */\nexport type RCVirtualCanvasRenderMode = 'continuous' | 'viewport-change' | 'manual';\n\n/** Identifies what triggered a render event */\nexport type RCVirtualCanvasRenderReason = 'animation-frame' | 'viewport-change' | 'manual';\n\n/** Content-space coordinate pair returned by coordinate-conversion methods */\nexport type RCVirtualCanvasPoint = Readonly<{\n /** Content space x coordinate in CSS pixels */\n x: number;\n\n /** Content space y coordinate in CSS pixels */\n y: number;\n}>;\n\n/**\n * Detail shape for `rc-virtual-canvas-pointer` events.\n *\n * Carries the source event's coordinates in both browser-client and content space,\n * along with all modifier key state.\n */\nexport type RCVirtualCanvasPointerInit = {\n /** DOM event type (e.g. `'pointerdown'`, `'click'`) */\n type: string;\n\n /** Browser client x coordinate from the source event */\n clientX: number;\n\n /** Browser client y coordinate from the source event */\n clientY: number;\n\n /** Content x coordinate mapped through the current backing-store scale */\n contentX: number;\n\n /** Content y coordinate mapped through the current backing-store scale */\n contentY: number;\n\n /** Frozen viewport snapshot at the moment the pointer event fired */\n viewRect: RCVirtualCanvasViewRect;\n\n /** Mouse button index from the source event */\n button: number;\n\n /** Active button bitmask from the source event */\n buttons: number;\n\n /** Whether the Alt key was held */\n altKey: boolean;\n\n /** Whether the Ctrl key was held */\n ctrlKey: boolean;\n\n /** Whether the Shift key was held */\n shiftKey: boolean;\n\n /** Whether the Meta key was held */\n metaKey: boolean;\n\n /** The original DOM event that produced this detail */\n sourceEvent: PointerEvent | MouseEvent;\n};\n\n/** CSS `image-rendering` value applied to the slotted canvas */\nexport type RCVirtualCanvasImageRendering = 'auto' | 'crisp-edges' | 'pixelated';\n\n/** Detail shape for `rc-virtual-canvas-render` events */\nexport type RCVirtualCanvasRenderInit = {\n /** High-resolution timestamp from `requestAnimationFrame` */\n time: DOMHighResTimeStamp;\n\n /** What triggered this render */\n reason: RCVirtualCanvasRenderReason;\n\n /** Frozen snapshot of the viewport for this frame */\n viewRect: RCVirtualCanvasViewRect;\n\n /** Frozen snapshot of the full content bounds for this frame */\n contentRect: RCVirtualCanvasViewRect;\n};\n\nfunction createRectSnapshot(rect: RCVirtualCanvasViewRect) {\n return Object.freeze({\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n });\n}\n\n/**\n * A scrollable virtual canvas component.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-virtual-canvas rc-virtual-canvas docs}\n *\n * @slot - The HTMLCanvasElement\n * @slot overlay - Optional viewport-positioned content rendered inside the\n * scroll container above the canvas.\n *\n * @fires rc-virtual-canvas-render - Fires with viewport data when the canvas should redraw.\n * @fires rc-virtual-canvas-pointer - Fires pointer/mouse input mapped to virtual content coordinates.\n */\nexport class RCVirtualCanvas extends LitElement {\n static styles = [virtualCanvasStyles];\n\n private _rafHandle: number = 0;\n private _pendingRenderReason?: RCVirtualCanvasRenderReason;\n private readonly _handledPointerEvents = new WeakSet<Event>();\n\n // Stored bound reference so the same closure is used for scheduling and\n // cancellation — `bind()` returns a new function every call.\n private readonly _boundUpdate = (time: DOMHighResTimeStamp) => this._update(time);\n\n private readonly _boundPointerEvent = (event: PointerEvent | MouseEvent) =>\n this._onPointerEvent(event);\n\n /** Pixel width of the virtual content */\n @property({ type: Number })\n set contentWidth(val: number) {\n const oldValue = this._contentWidth;\n\n this._contentWidth = val;\n\n this._contentRect.width = this._contentWidth;\n this.requestUpdate('contentWidth', oldValue);\n this._scheduleRender('viewport-change');\n }\n get contentWidth() {\n return this._contentWidth;\n }\n protected _contentWidth: number = 0;\n\n /** Pixel height of the virtual content */\n @property({ type: Number })\n set contentHeight(val: number) {\n const oldValue = this._contentHeight;\n\n this._contentHeight = val;\n\n this._contentRect.height = this._contentHeight;\n this.requestUpdate('contentHeight', oldValue);\n this._scheduleRender('viewport-change');\n }\n get contentHeight() {\n return this._contentHeight;\n }\n protected _contentHeight: number = 0;\n\n /** When true, keep the slotted canvas backing store aligned to the viewport. */\n @property({ attribute: 'auto-resize-canvas', type: Boolean })\n autoResizeCanvas: boolean = true;\n\n /** Controls when render events are dispatched. */\n @property({ attribute: 'render-mode' })\n renderMode: RCVirtualCanvasRenderMode = 'continuous';\n\n /** Convenience image-rendering value applied to the slotted canvas. */\n @property({ attribute: 'image-rendering' })\n imageRendering: RCVirtualCanvasImageRendering = 'auto';\n\n /** Shadow DOM scroll container that owns pointer interaction and native scrolling */\n @query('#root', true)\n protected _$root!: HTMLDivElement;\n\n /** Absolutely-positioned div that establishes the virtual scroll range */\n @query('#placeholder', true)\n protected _$placeholder!: HTMLDivElement;\n\n /** Currently-tracked slotted canvas element; `null` when no canvas is slotted */\n @state()\n protected _$canvas: HTMLCanvasElement | null = null;\n\n /** Mutable current-frame viewport geometry; call `getViewRect()` for a frozen snapshot */\n @state()\n protected _viewRect = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n\n /** Mutable content bounds; updated when `contentWidth` or `contentHeight` change */\n @state()\n protected _contentRect = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n\n /** Watches the slotted canvas for DPR-accurate dimension changes */\n protected _resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {\n for (const entry of entries) {\n const devicePixelBoxSize = Array.isArray(entry.devicePixelContentBoxSize)\n ? entry.devicePixelContentBoxSize[0]\n : entry.devicePixelContentBoxSize;\n\n this._viewRect.width = this._getMeasuredCanvasWidth(\n devicePixelBoxSize?.inlineSize ??\n Math.round(entry.contentRect.width * window.devicePixelRatio),\n );\n\n this._viewRect.height = this._getMeasuredCanvasHeight(\n devicePixelBoxSize?.blockSize ??\n Math.round(entry.contentRect.height * window.devicePixelRatio),\n );\n\n this._syncCanvasBackingStore();\n this._scheduleRender('viewport-change');\n }\n });\n\n /** Reads scroll position from the root container and schedules a render */\n protected _onScroll() {\n this._viewRect.x = this._$root.scrollLeft;\n this._viewRect.y = this._$root.scrollTop;\n\n this._scheduleRender('viewport-change');\n }\n\n /** Replaces canvas tracking and re-registers the resize observer when slot content changes */\n protected _onSlotChange(e: Event) {\n this._resizeObserver.disconnect();\n\n this._$canvas =\n (e.currentTarget as HTMLSlotElement)\n .assignedElements()\n .filter((el) => el instanceof HTMLCanvasElement)\n .at(0) ?? null;\n\n if (this._$canvas != null) {\n this._viewRect = {\n x: this._$root.scrollLeft ?? 0,\n y: this._$root.scrollTop ?? 0,\n width: this._getMeasuredCanvasWidth(this._$canvas.clientWidth),\n height: this._getMeasuredCanvasHeight(this._$canvas.clientHeight),\n };\n\n this._syncCanvasBackingStore();\n this._resizeObserver.observe(this._$canvas, {\n box: 'device-pixel-content-box',\n });\n\n this._scheduleRender('viewport-change');\n }\n }\n\n /**\n * Returns a frozen snapshot of the current viewport rectangle.\n *\n * Safe to keep between frames — the returned object will not be mutated\n * by future scroll or resize work.\n */\n getViewRect() {\n return createRectSnapshot(this._viewRect);\n }\n\n /**\n * Scrolls so the content coordinate is at the viewport origin.\n *\n * @param x - Content x coordinate in CSS pixels\n * @param y - Content y coordinate in CSS pixels\n * @param options - Native scroll options passed to scrollTo\n */\n scrollToContent(x: number, y: number, options: ScrollOptions = {}) {\n this._$root.scrollTo({\n ...options,\n left: x,\n top: y,\n });\n\n this._viewRect.x = this._$root.scrollLeft;\n this._viewRect.y = this._$root.scrollTop;\n this._scheduleRender('viewport-change');\n }\n\n /**\n * Scrolls so the content coordinate is centered in the viewport.\n *\n * @param x - Content x coordinate in CSS pixels\n * @param y - Content y coordinate in CSS pixels\n * @param options - Native scroll options passed to scrollTo\n */\n centerOnContent(x: number, y: number, options: ScrollOptions = {}) {\n this.scrollToContent(\n x - this._$root.clientWidth * 0.5,\n y - this._$root.clientHeight * 0.5,\n options,\n );\n }\n\n /**\n * Converts browser client coordinates to content coordinates using the current\n * backing-store scale.\n *\n * @param clientX - Browser client x coordinate (e.g. from a pointer event)\n * @param clientY - Browser client y coordinate\n *\n * @example\n * vc.addEventListener('pointerdown', (e) => {\n * const { x, y } = vc.clientToContent(e.clientX, e.clientY);\n * placeMarkerAt(x, y);\n * });\n */\n clientToContent(clientX: number, clientY: number) {\n const canvasRect = this._getCanvasClientRect();\n const scaleX = this._getViewportScaleX(canvasRect);\n const scaleY = this._getViewportScaleY(canvasRect);\n\n return Object.freeze({\n x: this._viewRect.x + (clientX - canvasRect.left) * scaleX,\n y: this._viewRect.y + (clientY - canvasRect.top) * scaleY,\n });\n }\n\n /**\n * Converts content coordinates back to browser client coordinates.\n *\n * @param x - Content x coordinate in CSS pixels\n * @param y - Content y coordinate in CSS pixels\n *\n * @example\n * const { x, y } = vc.contentToClient(contentX, contentY);\n * tooltip.style.left = `${x}px`;\n * tooltip.style.top = `${y}px`;\n */\n contentToClient(x: number, y: number) {\n const canvasRect = this._getCanvasClientRect();\n const scaleX = this._getViewportScaleX(canvasRect);\n const scaleY = this._getViewportScaleY(canvasRect);\n\n return Object.freeze({\n x: canvasRect.left + (x - this._viewRect.x) / scaleX,\n y: canvasRect.top + (y - this._viewRect.y) / scaleY,\n });\n }\n\n /**\n * Queues a render event on the next animation frame.\n *\n * Required when `renderMode` is `'manual'`. In other modes the component\n * schedules renders automatically; calling this with reason `'animation-frame'`\n * is a no-op when `renderMode` is `'viewport-change'`.\n *\n * @param reason - Render reason reported in the event detail\n */\n requestRender(reason: RCVirtualCanvasRenderReason = 'manual') {\n this._scheduleRender(reason);\n }\n\n /**\n * Ratio of canvas backing-store pixels to CSS pixels along the x-axis.\n *\n * Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the x-scale\n * factor in `ctx.scale()` to draw at CSS pixel resolution.\n *\n * @example\n * vc.addEventListener('rc-virtual-canvas-render', ({ detail: { viewRect } }) => {\n * ctx.save();\n * ctx.scale(vc.canvasScaleX, vc.canvasScaleY);\n * drawScene(ctx, viewRect);\n * ctx.restore();\n * });\n */\n get canvasScaleX(): number {\n const canvasRect = this._getCanvasClientRect();\n\n return this._getViewportScaleX(canvasRect);\n }\n\n /**\n * Ratio of canvas backing-store pixels to CSS pixels along the y-axis.\n *\n * Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the y-scale\n * factor in `ctx.scale()` to draw at CSS pixel resolution.\n */\n get canvasScaleY(): number {\n const canvasRect = this._getCanvasClientRect();\n\n return this._getViewportScaleY(canvasRect);\n }\n\n /** Maps pointer and mouse events to content coordinates and dispatches `rc-virtual-canvas-pointer` */\n protected _onPointerEvent(event: PointerEvent | MouseEvent) {\n if (this._handledPointerEvents.has(event)) {\n return;\n }\n\n if (this._isOverlayEvent(event)) {\n return;\n }\n\n this._handledPointerEvents.add(event);\n\n const contentPoint = this.clientToContent(event.clientX, event.clientY);\n\n const shouldContinue = this.dispatchEvent(\n new CustomEvent<RCVirtualCanvasPointerInit>('rc-virtual-canvas-pointer', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail: {\n type: event.type,\n clientX: event.clientX,\n clientY: event.clientY,\n contentX: contentPoint.x,\n contentY: contentPoint.y,\n viewRect: createRectSnapshot(this._viewRect),\n button: event.button,\n buttons: event.buttons,\n altKey: event.altKey,\n ctrlKey: event.ctrlKey,\n shiftKey: event.shiftKey,\n metaKey: event.metaKey,\n sourceEvent: event,\n },\n }),\n );\n\n if (!shouldContinue) event.preventDefault();\n }\n\n private _isOverlayEvent(event: Event): boolean {\n return event\n .composedPath()\n .some((node) => node instanceof HTMLSlotElement && node.name === 'overlay');\n }\n\n /**\n * RAF callback. Dispatches `rc-virtual-canvas-render` and re-schedules in continuous mode.\n *\n * @param time - High-resolution timestamp from `requestAnimationFrame`\n */\n protected _update(time: DOMHighResTimeStamp) {\n this._rafHandle = 0;\n\n if (!this.isConnected || this._$canvas == null) {\n return;\n }\n\n const reason =\n this._pendingRenderReason ??\n (this.renderMode === 'continuous' ? 'animation-frame' : 'manual');\n\n this._pendingRenderReason = undefined;\n\n this.dispatchEvent(\n new CustomEvent<RCVirtualCanvasRenderInit>('rc-virtual-canvas-render', {\n bubbles: true,\n composed: true,\n detail: {\n time,\n reason,\n viewRect: createRectSnapshot(this._viewRect),\n contentRect: createRectSnapshot(this._contentRect),\n },\n }),\n );\n\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n }\n }\n\n /** Registers pointer event listeners and starts rendering in continuous mode */\n override connectedCallback() {\n super.connectedCallback();\n\n this.addEventListener('pointerdown', this._boundPointerEvent);\n this.addEventListener('pointermove', this._boundPointerEvent);\n this.addEventListener('pointerup', this._boundPointerEvent);\n this.addEventListener('click', this._boundPointerEvent);\n this.addEventListener('dblclick', this._boundPointerEvent);\n this.addEventListener('contextmenu', this._boundPointerEvent);\n\n if (this._rafHandle) {\n cancelAnimationFrame(this._rafHandle);\n this._rafHandle = 0;\n }\n\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n }\n }\n\n /** Cancels any pending animation frame, disconnects the resize observer, and removes pointer listeners */\n override disconnectedCallback() {\n super.disconnectedCallback();\n\n cancelAnimationFrame(this._rafHandle);\n this._rafHandle = 0;\n this._resizeObserver.disconnect();\n\n this.removeEventListener('pointerdown', this._boundPointerEvent);\n this.removeEventListener('pointermove', this._boundPointerEvent);\n this.removeEventListener('pointerup', this._boundPointerEvent);\n this.removeEventListener('click', this._boundPointerEvent);\n this.removeEventListener('dblclick', this._boundPointerEvent);\n this.removeEventListener('contextmenu', this._boundPointerEvent);\n }\n\n /** Reacts to `renderMode`, `autoResizeCanvas`, and `imageRendering` property changes */\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('renderMode')) {\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n } else if (this._pendingRenderReason === 'animation-frame') {\n if (this._rafHandle) {\n cancelAnimationFrame(this._rafHandle);\n\n this._rafHandle = 0;\n }\n\n this._pendingRenderReason = undefined;\n } else if (this.renderMode === 'viewport-change') {\n this._scheduleRender('viewport-change');\n }\n }\n\n if (changed.has('autoResizeCanvas')) {\n this._syncCanvasBackingStore();\n }\n\n if (changed.has('imageRendering')) {\n this.style.setProperty('--rc-virtual-canvas-image-rendering', this.imageRendering);\n }\n }\n\n private _scheduleRender(reason: RCVirtualCanvasRenderReason) {\n if (this.renderMode === 'manual' && reason !== 'manual') {\n return;\n }\n\n if (this.renderMode === 'viewport-change' && reason === 'animation-frame') {\n return;\n }\n\n this._pendingRenderReason = reason;\n\n if (!this.isConnected || this._rafHandle) {\n return;\n }\n\n this._rafHandle = window.requestAnimationFrame(this._boundUpdate);\n }\n\n private _syncCanvasBackingStore() {\n if (!this.autoResizeCanvas || this._$canvas == null) {\n return;\n }\n\n const width = Math.max(0, Math.round(this._viewRect.width));\n const height = Math.max(0, Math.round(this._viewRect.height));\n\n if (this._$canvas.width !== width) {\n this._$canvas.width = width;\n }\n\n if (this._$canvas.height !== height) {\n this._$canvas.height = height;\n }\n }\n\n private _getCanvasClientRect() {\n if (this._$canvas == null) {\n return this.getBoundingClientRect();\n }\n\n return this._$canvas.getBoundingClientRect();\n }\n\n private _getMeasuredCanvasWidth(fallbackWidth: number) {\n if (!this.autoResizeCanvas && this._$canvas && this._$canvas.width > 0) {\n return this._$canvas.width;\n }\n\n return fallbackWidth;\n }\n\n private _getMeasuredCanvasHeight(fallbackHeight: number) {\n if (!this.autoResizeCanvas && this._$canvas && this._$canvas.height > 0) {\n return this._$canvas.height;\n }\n\n return fallbackHeight;\n }\n\n private _getViewportScaleX(canvasRect: DOMRect) {\n return canvasRect.width > 0 ? this._viewRect.width / canvasRect.width : 1;\n }\n\n private _getViewportScaleY(canvasRect: DOMRect) {\n return canvasRect.height > 0 ? this._viewRect.height / canvasRect.height : 1;\n }\n\n render() {\n return html`\n <div\n id=\"root\"\n part=\"scroller\"\n @scroll=${this._onScroll}\n @pointerdown=${this._onPointerEvent}\n @pointermove=${this._onPointerEvent}\n @pointerup=${this._onPointerEvent}\n @click=${this._onPointerEvent}\n @dblclick=${this._onPointerEvent}\n @contextmenu=${this._onPointerEvent}\n >\n <div id=\"overlay\" part=\"overlay\">\n <slot name=\"overlay\"></slot>\n </div>\n\n <div\n id=\"placeholder\"\n style=${styleMap({\n width: `${this.contentWidth}px`,\n height: `${this.contentHeight}px`,\n })}\n ></div>\n </div>\n\n <slot @slotchange=${this._onSlotChange}></slot>\n `;\n }\n}\n\nexport default RCVirtualCanvas;\n"],"names":["virtualCanvasStyles","css","createRectSnapshot","rect","_RCVirtualCanvas","LitElement","time","event","entries","entry","devicePixelBoxSize","val","oldValue","e","el","x","y","options","clientX","clientY","canvasRect","scaleX","scaleY","reason","contentPoint","node","changed","width","height","fallbackWidth","fallbackHeight","html","styleMap","RCVirtualCanvas","__decorateClass","property","query","state"],"mappings":"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"}
|
|
@@ -1,53 +1,96 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
declare global {
|
|
3
|
+
interface HTMLElementTagNameMap {
|
|
4
|
+
'rc-virtual-canvas': RCVirtualCanvas;
|
|
5
|
+
}
|
|
6
|
+
interface HTMLElementEventMap {
|
|
7
|
+
'rc-virtual-canvas-render': CustomEvent<RCVirtualCanvasRenderInit>;
|
|
8
|
+
'rc-virtual-canvas-pointer': CustomEvent<RCVirtualCanvasPointerInit>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Scrollable virtual canvas for rendering large coordinate-space content.
|
|
13
|
+
*
|
|
14
|
+
* `x` and `y` are scroll offsets in CSS pixels; `width` and `height` are the canvas
|
|
15
|
+
* backing-store dimensions in device pixels (DPR-accurate when `autoResizeCanvas` is `true`).
|
|
16
|
+
*/
|
|
2
17
|
export type RCVirtualCanvasViewRect = Readonly<{
|
|
18
|
+
/** Scroll offset x in content space (CSS px) */
|
|
3
19
|
x: number;
|
|
20
|
+
/** Scroll offset y in content space (CSS px) */
|
|
4
21
|
y: number;
|
|
22
|
+
/** Visible width in device pixels */
|
|
5
23
|
width: number;
|
|
24
|
+
/** Visible height in device pixels */
|
|
6
25
|
height: number;
|
|
7
26
|
}>;
|
|
27
|
+
/** Controls when `rc-virtual-canvas-render` events are dispatched */
|
|
8
28
|
export type RCVirtualCanvasRenderMode = 'continuous' | 'viewport-change' | 'manual';
|
|
29
|
+
/** Identifies what triggered a render event */
|
|
9
30
|
export type RCVirtualCanvasRenderReason = 'animation-frame' | 'viewport-change' | 'manual';
|
|
31
|
+
/** Content-space coordinate pair returned by coordinate-conversion methods */
|
|
10
32
|
export type RCVirtualCanvasPoint = Readonly<{
|
|
33
|
+
/** Content space x coordinate in CSS pixels */
|
|
11
34
|
x: number;
|
|
35
|
+
/** Content space y coordinate in CSS pixels */
|
|
12
36
|
y: number;
|
|
13
37
|
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Detail shape for `rc-virtual-canvas-pointer` events.
|
|
40
|
+
*
|
|
41
|
+
* Carries the source event's coordinates in both browser-client and content space,
|
|
42
|
+
* along with all modifier key state.
|
|
43
|
+
*/
|
|
14
44
|
export type RCVirtualCanvasPointerInit = {
|
|
45
|
+
/** DOM event type (e.g. `'pointerdown'`, `'click'`) */
|
|
15
46
|
type: string;
|
|
47
|
+
/** Browser client x coordinate from the source event */
|
|
16
48
|
clientX: number;
|
|
49
|
+
/** Browser client y coordinate from the source event */
|
|
17
50
|
clientY: number;
|
|
51
|
+
/** Content x coordinate mapped through the current backing-store scale */
|
|
18
52
|
contentX: number;
|
|
53
|
+
/** Content y coordinate mapped through the current backing-store scale */
|
|
19
54
|
contentY: number;
|
|
55
|
+
/** Frozen viewport snapshot at the moment the pointer event fired */
|
|
20
56
|
viewRect: RCVirtualCanvasViewRect;
|
|
57
|
+
/** Mouse button index from the source event */
|
|
21
58
|
button: number;
|
|
59
|
+
/** Active button bitmask from the source event */
|
|
22
60
|
buttons: number;
|
|
61
|
+
/** Whether the Alt key was held */
|
|
23
62
|
altKey: boolean;
|
|
63
|
+
/** Whether the Ctrl key was held */
|
|
24
64
|
ctrlKey: boolean;
|
|
65
|
+
/** Whether the Shift key was held */
|
|
25
66
|
shiftKey: boolean;
|
|
67
|
+
/** Whether the Meta key was held */
|
|
26
68
|
metaKey: boolean;
|
|
69
|
+
/** The original DOM event that produced this detail */
|
|
27
70
|
sourceEvent: PointerEvent | MouseEvent;
|
|
28
71
|
};
|
|
72
|
+
/** CSS `image-rendering` value applied to the slotted canvas */
|
|
29
73
|
export type RCVirtualCanvasImageRendering = 'auto' | 'crisp-edges' | 'pixelated';
|
|
74
|
+
/** Detail shape for `rc-virtual-canvas-render` events */
|
|
30
75
|
export type RCVirtualCanvasRenderInit = {
|
|
76
|
+
/** High-resolution timestamp from `requestAnimationFrame` */
|
|
31
77
|
time: DOMHighResTimeStamp;
|
|
78
|
+
/** What triggered this render */
|
|
32
79
|
reason: RCVirtualCanvasRenderReason;
|
|
80
|
+
/** Frozen snapshot of the viewport for this frame */
|
|
33
81
|
viewRect: RCVirtualCanvasViewRect;
|
|
82
|
+
/** Frozen snapshot of the full content bounds for this frame */
|
|
34
83
|
contentRect: RCVirtualCanvasViewRect;
|
|
35
84
|
};
|
|
36
|
-
declare global {
|
|
37
|
-
interface HTMLElementTagNameMap {
|
|
38
|
-
'rc-virtual-canvas': RCVirtualCanvas;
|
|
39
|
-
}
|
|
40
|
-
interface HTMLElementEventMap {
|
|
41
|
-
'rc-virtual-canvas-render': CustomEvent<RCVirtualCanvasRenderInit>;
|
|
42
|
-
'rc-virtual-canvas-pointer': CustomEvent<RCVirtualCanvasPointerInit>;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
85
|
/**
|
|
46
|
-
*
|
|
86
|
+
* A scrollable virtual canvas component.
|
|
87
|
+
*
|
|
88
|
+
* @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-virtual-canvas rc-virtual-canvas docs}
|
|
47
89
|
*
|
|
48
90
|
* @slot - The HTMLCanvasElement
|
|
49
91
|
* @slot overlay - Optional viewport-positioned content rendered inside the
|
|
50
92
|
* scroll container above the canvas.
|
|
93
|
+
*
|
|
51
94
|
* @fires rc-virtual-canvas-render - Fires with viewport data when the canvas should redraw.
|
|
52
95
|
* @fires rc-virtual-canvas-pointer - Fires pointer/mouse input mapped to virtual content coordinates.
|
|
53
96
|
*/
|
|
@@ -72,46 +115,138 @@ export declare class RCVirtualCanvas extends LitElement {
|
|
|
72
115
|
renderMode: RCVirtualCanvasRenderMode;
|
|
73
116
|
/** Convenience image-rendering value applied to the slotted canvas. */
|
|
74
117
|
imageRendering: RCVirtualCanvasImageRendering;
|
|
118
|
+
/** Shadow DOM scroll container that owns pointer interaction and native scrolling */
|
|
75
119
|
protected _$root: HTMLDivElement;
|
|
120
|
+
/** Absolutely-positioned div that establishes the virtual scroll range */
|
|
76
121
|
protected _$placeholder: HTMLDivElement;
|
|
122
|
+
/** Currently-tracked slotted canvas element; `null` when no canvas is slotted */
|
|
77
123
|
protected _$canvas: HTMLCanvasElement | null;
|
|
124
|
+
/** Mutable current-frame viewport geometry; call `getViewRect()` for a frozen snapshot */
|
|
78
125
|
protected _viewRect: {
|
|
79
126
|
x: number;
|
|
80
127
|
y: number;
|
|
81
128
|
width: number;
|
|
82
129
|
height: number;
|
|
83
130
|
};
|
|
131
|
+
/** Mutable content bounds; updated when `contentWidth` or `contentHeight` change */
|
|
84
132
|
protected _contentRect: {
|
|
85
133
|
x: number;
|
|
86
134
|
y: number;
|
|
87
135
|
width: number;
|
|
88
136
|
height: number;
|
|
89
137
|
};
|
|
138
|
+
/** Watches the slotted canvas for DPR-accurate dimension changes */
|
|
90
139
|
protected _resizeObserver: ResizeObserver;
|
|
140
|
+
/** Reads scroll position from the root container and schedules a render */
|
|
91
141
|
protected _onScroll(): void;
|
|
142
|
+
/** Replaces canvas tracking and re-registers the resize observer when slot content changes */
|
|
92
143
|
protected _onSlotChange(e: Event): void;
|
|
144
|
+
/**
|
|
145
|
+
* Returns a frozen snapshot of the current viewport rectangle.
|
|
146
|
+
*
|
|
147
|
+
* Safe to keep between frames — the returned object will not be mutated
|
|
148
|
+
* by future scroll or resize work.
|
|
149
|
+
*/
|
|
93
150
|
getViewRect(): Readonly<{
|
|
94
151
|
x: number;
|
|
95
152
|
y: number;
|
|
96
153
|
width: number;
|
|
97
154
|
height: number;
|
|
98
155
|
}>;
|
|
156
|
+
/**
|
|
157
|
+
* Scrolls so the content coordinate is at the viewport origin.
|
|
158
|
+
*
|
|
159
|
+
* @param x - Content x coordinate in CSS pixels
|
|
160
|
+
* @param y - Content y coordinate in CSS pixels
|
|
161
|
+
* @param options - Native scroll options passed to scrollTo
|
|
162
|
+
*/
|
|
99
163
|
scrollToContent(x: number, y: number, options?: ScrollOptions): void;
|
|
164
|
+
/**
|
|
165
|
+
* Scrolls so the content coordinate is centered in the viewport.
|
|
166
|
+
*
|
|
167
|
+
* @param x - Content x coordinate in CSS pixels
|
|
168
|
+
* @param y - Content y coordinate in CSS pixels
|
|
169
|
+
* @param options - Native scroll options passed to scrollTo
|
|
170
|
+
*/
|
|
100
171
|
centerOnContent(x: number, y: number, options?: ScrollOptions): void;
|
|
172
|
+
/**
|
|
173
|
+
* Converts browser client coordinates to content coordinates using the current
|
|
174
|
+
* backing-store scale.
|
|
175
|
+
*
|
|
176
|
+
* @param clientX - Browser client x coordinate (e.g. from a pointer event)
|
|
177
|
+
* @param clientY - Browser client y coordinate
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* vc.addEventListener('pointerdown', (e) => {
|
|
181
|
+
* const { x, y } = vc.clientToContent(e.clientX, e.clientY);
|
|
182
|
+
* placeMarkerAt(x, y);
|
|
183
|
+
* });
|
|
184
|
+
*/
|
|
101
185
|
clientToContent(clientX: number, clientY: number): Readonly<{
|
|
102
186
|
x: number;
|
|
103
187
|
y: number;
|
|
104
188
|
}>;
|
|
189
|
+
/**
|
|
190
|
+
* Converts content coordinates back to browser client coordinates.
|
|
191
|
+
*
|
|
192
|
+
* @param x - Content x coordinate in CSS pixels
|
|
193
|
+
* @param y - Content y coordinate in CSS pixels
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* const { x, y } = vc.contentToClient(contentX, contentY);
|
|
197
|
+
* tooltip.style.left = `${x}px`;
|
|
198
|
+
* tooltip.style.top = `${y}px`;
|
|
199
|
+
*/
|
|
105
200
|
contentToClient(x: number, y: number): Readonly<{
|
|
106
201
|
x: number;
|
|
107
202
|
y: number;
|
|
108
203
|
}>;
|
|
204
|
+
/**
|
|
205
|
+
* Queues a render event on the next animation frame.
|
|
206
|
+
*
|
|
207
|
+
* Required when `renderMode` is `'manual'`. In other modes the component
|
|
208
|
+
* schedules renders automatically; calling this with reason `'animation-frame'`
|
|
209
|
+
* is a no-op when `renderMode` is `'viewport-change'`.
|
|
210
|
+
*
|
|
211
|
+
* @param reason - Render reason reported in the event detail
|
|
212
|
+
*/
|
|
109
213
|
requestRender(reason?: RCVirtualCanvasRenderReason): void;
|
|
214
|
+
/**
|
|
215
|
+
* Ratio of canvas backing-store pixels to CSS pixels along the x-axis.
|
|
216
|
+
*
|
|
217
|
+
* Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the x-scale
|
|
218
|
+
* factor in `ctx.scale()` to draw at CSS pixel resolution.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* vc.addEventListener('rc-virtual-canvas-render', ({ detail: { viewRect } }) => {
|
|
222
|
+
* ctx.save();
|
|
223
|
+
* ctx.scale(vc.canvasScaleX, vc.canvasScaleY);
|
|
224
|
+
* drawScene(ctx, viewRect);
|
|
225
|
+
* ctx.restore();
|
|
226
|
+
* });
|
|
227
|
+
*/
|
|
228
|
+
get canvasScaleX(): number;
|
|
229
|
+
/**
|
|
230
|
+
* Ratio of canvas backing-store pixels to CSS pixels along the y-axis.
|
|
231
|
+
*
|
|
232
|
+
* Equals `devicePixelRatio` when `autoResizeCanvas` is `true`. Use as the y-scale
|
|
233
|
+
* factor in `ctx.scale()` to draw at CSS pixel resolution.
|
|
234
|
+
*/
|
|
235
|
+
get canvasScaleY(): number;
|
|
236
|
+
/** Maps pointer and mouse events to content coordinates and dispatches `rc-virtual-canvas-pointer` */
|
|
110
237
|
protected _onPointerEvent(event: PointerEvent | MouseEvent): void;
|
|
111
238
|
private _isOverlayEvent;
|
|
239
|
+
/**
|
|
240
|
+
* RAF callback. Dispatches `rc-virtual-canvas-render` and re-schedules in continuous mode.
|
|
241
|
+
*
|
|
242
|
+
* @param time - High-resolution timestamp from `requestAnimationFrame`
|
|
243
|
+
*/
|
|
112
244
|
protected _update(time: DOMHighResTimeStamp): void;
|
|
245
|
+
/** Registers pointer event listeners and starts rendering in continuous mode */
|
|
113
246
|
connectedCallback(): void;
|
|
247
|
+
/** Cancels any pending animation frame, disconnects the resize observer, and removes pointer listeners */
|
|
114
248
|
disconnectedCallback(): void;
|
|
249
|
+
/** Reacts to `renderMode`, `autoResizeCanvas`, and `imageRendering` property changes */
|
|
115
250
|
protected updated(changed: Map<string, unknown>): void;
|
|
116
251
|
private _scheduleRender;
|
|
117
252
|
private _syncCanvasBackingStore;
|
package/package.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
7
|
-
"description": "
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"description": "Scrollable virtual canvas for rendering large coordinate-space content.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/richardcarls/rc-webcomponents.git",
|
|
11
11
|
"directory": "packages/rc-virtual-canvas"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-virtual-canvas",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"files": [
|
|
@@ -37,17 +37,15 @@
|
|
|
37
37
|
],
|
|
38
38
|
"customElements": "dist/custom-elements.json",
|
|
39
39
|
"scripts": {
|
|
40
|
-
"dev": "vite",
|
|
41
40
|
"build": "tsc && vite build && cem analyze",
|
|
42
41
|
"cem:analyze": "cem analyze",
|
|
43
42
|
"preview": "vite preview",
|
|
44
|
-
"test:browser": "vitest",
|
|
45
|
-
"test:browser:chrome": "vitest --project=chromium",
|
|
46
|
-
"test:browser:firefox": "vitest --project=firefox"
|
|
43
|
+
"test:browser": "vitest --run",
|
|
44
|
+
"test:browser:chrome": "vitest --run --project=chromium",
|
|
45
|
+
"test:browser:firefox": "vitest --run --project=firefox"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
49
48
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
50
|
-
"@guanghechen/rollup-plugin-copy": "^6.0.9",
|
|
51
49
|
"@types/node": "^25.6.0",
|
|
52
50
|
"@vitest/browser-playwright": "4.1.5",
|
|
53
51
|
"lit": "^3.0.0",
|
package/dist/default_tiles.png
DELETED
|
Binary file
|
package/dist/demo.css
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
*, *::before, *::after {
|
|
2
|
-
box-sizing: border-box;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
:root {
|
|
6
|
-
color-scheme: light dark;
|
|
7
|
-
--rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
@supports (color: AccentColor) {
|
|
11
|
-
:root {
|
|
12
|
-
--rc-accent: AccentColor;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
[data-theme="light"] { color-scheme: light; }
|
|
17
|
-
[data-theme="dark"] { color-scheme: dark; }
|
|
18
|
-
|
|
19
|
-
[data-theme="solarized-light"] {
|
|
20
|
-
color-scheme: light;
|
|
21
|
-
--rc-surface: #fdf6e3;
|
|
22
|
-
--rc-text: #657b83;
|
|
23
|
-
--rc-border: 1px solid #93a1a1;
|
|
24
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
[data-theme="solarized-dark"] {
|
|
28
|
-
color-scheme: dark;
|
|
29
|
-
--rc-surface: #002b36;
|
|
30
|
-
--rc-text: #839496;
|
|
31
|
-
--rc-border: 1px solid #586e75;
|
|
32
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
body {
|
|
36
|
-
font-family: system-ui, sans-serif;
|
|
37
|
-
margin: 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.demo-page {
|
|
41
|
-
max-width: 60rem;
|
|
42
|
-
margin: 0 auto;
|
|
43
|
-
padding: 2rem 1.5rem;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.demo-controls {
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
gap: 1rem;
|
|
50
|
-
margin-bottom: 2rem;
|
|
51
|
-
padding-bottom: 1rem;
|
|
52
|
-
border-bottom: 1px solid ButtonBorder;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.demo-controls .demo-title {
|
|
56
|
-
flex: 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.demo-controls h1 {
|
|
60
|
-
margin: 0 0 0.15rem;
|
|
61
|
-
font-size: 1.5rem;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.demo-controls a {
|
|
65
|
-
font-size: 0.8rem;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.demo-section {
|
|
69
|
-
margin-bottom: 2rem;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.demo-section h2 {
|
|
73
|
-
margin: 0 0 0.5rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.theme-picker {
|
|
77
|
-
padding: 0.35em 0.75em;
|
|
78
|
-
font-family: inherit;
|
|
79
|
-
font-size: 0.8rem;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
border: 1px solid ButtonBorder;
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
background: Canvas;
|
|
84
|
-
color: CanvasText;
|
|
85
|
-
}
|
package/dist/demo.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
|
|
2
|
-
const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
|
|
3
|
-
|
|
4
|
-
const stored = localStorage.getItem('rc-demo-theme') ?? '';
|
|
5
|
-
applyTheme(stored);
|
|
6
|
-
|
|
7
|
-
function applyTheme(theme) {
|
|
8
|
-
if (theme) {
|
|
9
|
-
document.documentElement.dataset.theme = theme;
|
|
10
|
-
} else {
|
|
11
|
-
delete document.documentElement.dataset.theme;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function currentIndex() {
|
|
16
|
-
const current = document.documentElement.dataset.theme ?? '';
|
|
17
|
-
const idx = THEMES.indexOf(current);
|
|
18
|
-
return idx === -1 ? 0 : idx;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function updateButtons() {
|
|
22
|
-
const label = LABELS[currentIndex()];
|
|
23
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
24
|
-
btn.textContent = label;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
window.cycleTheme = function () {
|
|
29
|
-
const next = THEMES[(currentIndex() + 1) % THEMES.length];
|
|
30
|
-
applyTheme(next);
|
|
31
|
-
localStorage.setItem('rc-demo-theme', next);
|
|
32
|
-
updateButtons();
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
36
|
-
updateButtons();
|
|
37
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
38
|
-
btn.addEventListener('click', window.cycleTheme);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rc-virtual-canvas-BJQbqNEu.js","sources":["../src/rc-virtual-canvas.styles.ts","../src/rc-virtual-canvas.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const virtualCanvasStyles = css`\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n :host {\n position: relative;\n display: block;\n width: max-content;\n line-height: 0; /* remove extra space under slotted <canvas> element(s) */\n }\n\n :host([hidden]) {\n display: none;\n }\n\n #root {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n\n overflow: auto;\n scrollbar-width: var(--rc-virtual-canvas-scrollbar-width, thin);\n }\n\n #root::-webkit-scrollbar {\n width: var(--rc-virtual-canvas-scrollbar-size, auto);\n height: var(--rc-virtual-canvas-scrollbar-size, auto);\n }\n\n #root::-webkit-scrollbar-thumb {\n background: var(--rc-virtual-canvas-scrollbar-thumb-background, ButtonFace);\n }\n\n #root::-webkit-scrollbar-button {\n background: var(--rc-virtual-canvas-scrollbar-button-background, ButtonFace);\n }\n\n #root::-webkit-scrollbar-track {\n background: var(--rc-virtual-canvas-scrollbar-track-background, Canvas);\n }\n\n #placeholder {\n pointer-events: none;\n }\n\n #overlay {\n position: sticky;\n left: 0;\n top: 0;\n z-index: 1;\n display: block;\n width: 100%;\n height: 0;\n pointer-events: none;\n }\n\n ::slotted([slot='overlay']) {\n pointer-events: auto;\n }\n\n ::slotted(canvas) {\n image-rendering: var(--rc-virtual-canvas-image-rendering, auto);\n }\n`;\n\nexport default virtualCanvasStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { styleMap } from 'lit/directives/style-map.js';\n\nimport virtualCanvasStyles from './rc-virtual-canvas.styles';\n\nexport type RCVirtualCanvasViewRect = Readonly<{\n x: number;\n y: number;\n width: number;\n height: number;\n}>;\n\nexport type RCVirtualCanvasRenderMode =\n | 'continuous'\n | 'viewport-change'\n | 'manual';\n\nexport type RCVirtualCanvasRenderReason =\n | 'animation-frame'\n | 'viewport-change'\n | 'manual';\n\nexport type RCVirtualCanvasPoint = Readonly<{\n x: number;\n y: number;\n}>;\n\nexport type RCVirtualCanvasPointerInit = {\n type: string;\n clientX: number;\n clientY: number;\n contentX: number;\n contentY: number;\n viewRect: RCVirtualCanvasViewRect;\n button: number;\n buttons: number;\n altKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n sourceEvent: PointerEvent | MouseEvent;\n};\n\nexport type RCVirtualCanvasImageRendering =\n | 'auto'\n | 'crisp-edges'\n | 'pixelated';\n\nexport type RCVirtualCanvasRenderInit = {\n time: DOMHighResTimeStamp;\n reason: RCVirtualCanvasRenderReason;\n viewRect: RCVirtualCanvasViewRect;\n contentRect: RCVirtualCanvasViewRect;\n};\n\nfunction createRectSnapshot(rect: RCVirtualCanvasViewRect) {\n return Object.freeze({\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n });\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-virtual-canvas': RCVirtualCanvas;\n }\n\n interface HTMLElementEventMap {\n 'rc-virtual-canvas-render': CustomEvent<RCVirtualCanvasRenderInit>;\n 'rc-virtual-canvas-pointer': CustomEvent<RCVirtualCanvasPointerInit>;\n }\n}\n\n/**\n * An accessible virtual scrollable canvas component.\n *\n * @slot - The HTMLCanvasElement\n * @slot overlay - Optional viewport-positioned content rendered inside the\n * scroll container above the canvas.\n * @fires rc-virtual-canvas-render - Fires with viewport data when the canvas should redraw.\n * @fires rc-virtual-canvas-pointer - Fires pointer/mouse input mapped to virtual content coordinates.\n */\nexport class RCVirtualCanvas extends LitElement {\n static styles = [virtualCanvasStyles];\n\n private _rafHandle: number = 0;\n private _pendingRenderReason?: RCVirtualCanvasRenderReason;\n private readonly _handledPointerEvents = new WeakSet<Event>();\n\n // Stored bound reference so the same closure is used for scheduling and\n // cancellation — `bind()` returns a new function every call.\n private readonly _boundUpdate = (time: DOMHighResTimeStamp) =>\n this._update(time);\n\n private readonly _boundPointerEvent = (event: PointerEvent | MouseEvent) =>\n this._onPointerEvent(event);\n\n /** Pixel width of the virtual content */\n @property({ type: Number })\n set contentWidth(val: number) {\n const oldValue = this._contentWidth;\n\n this._contentWidth = val;\n\n this._contentRect.width = this._contentWidth;\n this.requestUpdate('contentWidth', oldValue);\n this._scheduleRender('viewport-change');\n }\n get contentWidth() {\n return this._contentWidth;\n }\n protected _contentWidth: number = 0;\n\n /** Pixel height of the virtual content */\n @property({ type: Number })\n set contentHeight(val: number) {\n const oldValue = this._contentHeight;\n\n this._contentHeight = val;\n\n this._contentRect.height = this._contentHeight;\n this.requestUpdate('contentHeight', oldValue);\n this._scheduleRender('viewport-change');\n }\n get contentHeight() {\n return this._contentHeight;\n }\n protected _contentHeight: number = 0;\n\n /** When true, keep the slotted canvas backing store aligned to the viewport. */\n @property({ attribute: 'auto-resize-canvas', type: Boolean })\n autoResizeCanvas: boolean = true;\n\n /** Controls when render events are dispatched. */\n @property({ attribute: 'render-mode' })\n renderMode: RCVirtualCanvasRenderMode = 'continuous';\n\n /** Convenience image-rendering value applied to the slotted canvas. */\n @property({ attribute: 'image-rendering' })\n imageRendering: RCVirtualCanvasImageRendering = 'auto';\n\n @query('#root', true)\n protected _$root!: HTMLDivElement;\n\n @query('#placeholder', true)\n protected _$placeholder!: HTMLDivElement;\n\n @state()\n protected _$canvas: HTMLCanvasElement | null = null;\n\n @state()\n protected _viewRect = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n\n @state()\n protected _contentRect = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n\n protected _resizeObserver = new ResizeObserver(\n (entries: ResizeObserverEntry[]) => {\n for (const entry of entries) {\n const devicePixelBoxSize = Array.isArray(\n entry.devicePixelContentBoxSize,\n )\n ? entry.devicePixelContentBoxSize[0]\n : entry.devicePixelContentBoxSize;\n\n this._viewRect.width = this._getMeasuredCanvasWidth(\n devicePixelBoxSize?.inlineSize\n ?? Math.round(entry.contentRect.width * window.devicePixelRatio),\n );\n this._viewRect.height = this._getMeasuredCanvasHeight(\n devicePixelBoxSize?.blockSize\n ?? Math.round(entry.contentRect.height * window.devicePixelRatio),\n );\n\n this._syncCanvasBackingStore();\n this._scheduleRender('viewport-change');\n }\n },\n );\n\n protected _onScroll() {\n this._viewRect.x = this._$root.scrollLeft;\n this._viewRect.y = this._$root.scrollTop;\n\n this._scheduleRender('viewport-change');\n }\n\n protected _onSlotChange(e: Event) {\n this._resizeObserver.disconnect();\n\n this._$canvas =\n (e.currentTarget as HTMLSlotElement)\n .assignedElements()\n .filter((el) => el instanceof HTMLCanvasElement)\n .at(0) ?? null;\n\n if (this._$canvas != null) {\n this._viewRect = {\n x: this._$root.scrollLeft ?? 0,\n y: this._$root.scrollTop ?? 0,\n width: this._getMeasuredCanvasWidth(this._$canvas.clientWidth),\n height: this._getMeasuredCanvasHeight(this._$canvas.clientHeight),\n };\n\n this._syncCanvasBackingStore();\n this._resizeObserver.observe(this._$canvas, {\n box: 'device-pixel-content-box',\n });\n this._scheduleRender('viewport-change');\n }\n }\n\n getViewRect() {\n return createRectSnapshot(this._viewRect);\n }\n\n scrollToContent(x: number, y: number, options: ScrollOptions = {}) {\n this._$root.scrollTo({\n ...options,\n left: x,\n top: y,\n });\n\n this._viewRect.x = this._$root.scrollLeft;\n this._viewRect.y = this._$root.scrollTop;\n this._scheduleRender('viewport-change');\n }\n\n centerOnContent(x: number, y: number, options: ScrollOptions = {}) {\n this.scrollToContent(\n x - (this._$root.clientWidth * 0.5),\n y - (this._$root.clientHeight * 0.5),\n options,\n );\n }\n\n clientToContent(clientX: number, clientY: number) {\n const canvasRect = this._getCanvasClientRect();\n const scaleX = this._getViewportScaleX(canvasRect);\n const scaleY = this._getViewportScaleY(canvasRect);\n\n return Object.freeze({\n x: this._viewRect.x + ((clientX - canvasRect.left) * scaleX),\n y: this._viewRect.y + ((clientY - canvasRect.top) * scaleY),\n });\n }\n\n contentToClient(x: number, y: number) {\n const canvasRect = this._getCanvasClientRect();\n const scaleX = this._getViewportScaleX(canvasRect);\n const scaleY = this._getViewportScaleY(canvasRect);\n\n return Object.freeze({\n x: canvasRect.left + ((x - this._viewRect.x) / scaleX),\n y: canvasRect.top + ((y - this._viewRect.y) / scaleY),\n });\n }\n\n requestRender(reason: RCVirtualCanvasRenderReason = 'manual') {\n this._scheduleRender(reason);\n }\n\n protected _onPointerEvent(event: PointerEvent | MouseEvent) {\n if (this._handledPointerEvents.has(event)) return;\n if (this._isOverlayEvent(event)) return;\n\n this._handledPointerEvents.add(event);\n\n const contentPoint = this.clientToContent(event.clientX, event.clientY);\n\n const shouldContinue = this.dispatchEvent(\n new CustomEvent<RCVirtualCanvasPointerInit>('rc-virtual-canvas-pointer', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail: {\n type: event.type,\n clientX: event.clientX,\n clientY: event.clientY,\n contentX: contentPoint.x,\n contentY: contentPoint.y,\n viewRect: createRectSnapshot(this._viewRect),\n button: event.button,\n buttons: event.buttons,\n altKey: event.altKey,\n ctrlKey: event.ctrlKey,\n shiftKey: event.shiftKey,\n metaKey: event.metaKey,\n sourceEvent: event,\n },\n }),\n );\n\n if (!shouldContinue) event.preventDefault();\n }\n\n private _isOverlayEvent(event: Event): boolean {\n return event.composedPath().some((node) =>\n node instanceof HTMLSlotElement\n && node.name === 'overlay',\n );\n }\n\n protected _update(time: DOMHighResTimeStamp) {\n this._rafHandle = 0;\n\n if (!this.isConnected || this._$canvas == null) {\n return;\n }\n\n const reason = this._pendingRenderReason\n ?? (this.renderMode === 'continuous' ? 'animation-frame' : 'manual');\n\n this._pendingRenderReason = undefined;\n\n this.dispatchEvent(\n new CustomEvent<RCVirtualCanvasRenderInit>('rc-virtual-canvas-render', {\n bubbles: true,\n composed: true,\n detail: {\n time,\n reason,\n viewRect: createRectSnapshot(this._viewRect),\n contentRect: createRectSnapshot(this._contentRect),\n },\n }),\n );\n\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n }\n }\n\n override connectedCallback() {\n super.connectedCallback();\n\n this.addEventListener('pointerdown', this._boundPointerEvent);\n this.addEventListener('pointermove', this._boundPointerEvent);\n this.addEventListener('pointerup', this._boundPointerEvent);\n this.addEventListener('click', this._boundPointerEvent);\n this.addEventListener('dblclick', this._boundPointerEvent);\n this.addEventListener('contextmenu', this._boundPointerEvent);\n\n if (this._rafHandle) {\n cancelAnimationFrame(this._rafHandle);\n this._rafHandle = 0;\n }\n\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n\n cancelAnimationFrame(this._rafHandle);\n this._rafHandle = 0;\n this._resizeObserver.disconnect();\n\n this.removeEventListener('pointerdown', this._boundPointerEvent);\n this.removeEventListener('pointermove', this._boundPointerEvent);\n this.removeEventListener('pointerup', this._boundPointerEvent);\n this.removeEventListener('click', this._boundPointerEvent);\n this.removeEventListener('dblclick', this._boundPointerEvent);\n this.removeEventListener('contextmenu', this._boundPointerEvent);\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('renderMode')) {\n if (this.renderMode === 'continuous') {\n this._scheduleRender('animation-frame');\n } else if (this._pendingRenderReason === 'animation-frame') {\n if (this._rafHandle) {\n cancelAnimationFrame(this._rafHandle);\n this._rafHandle = 0;\n }\n\n this._pendingRenderReason = undefined;\n } else if (this.renderMode === 'viewport-change') {\n this._scheduleRender('viewport-change');\n }\n }\n\n if (changed.has('autoResizeCanvas')) {\n this._syncCanvasBackingStore();\n }\n\n if (changed.has('imageRendering')) {\n this.style.setProperty(\n '--rc-virtual-canvas-image-rendering',\n this.imageRendering,\n );\n }\n }\n\n private _scheduleRender(reason: RCVirtualCanvasRenderReason) {\n if (this.renderMode === 'manual' && reason !== 'manual') {\n return;\n }\n\n if (\n this.renderMode === 'viewport-change'\n && reason === 'animation-frame'\n ) {\n return;\n }\n\n this._pendingRenderReason = reason;\n\n if (!this.isConnected || this._rafHandle) {\n return;\n }\n\n this._rafHandle = window.requestAnimationFrame(this._boundUpdate);\n }\n\n private _syncCanvasBackingStore() {\n if (!this.autoResizeCanvas || this._$canvas == null) {\n return;\n }\n\n const width = Math.max(0, Math.round(this._viewRect.width));\n const height = Math.max(0, Math.round(this._viewRect.height));\n\n if (this._$canvas.width !== width) {\n this._$canvas.width = width;\n }\n\n if (this._$canvas.height !== height) {\n this._$canvas.height = height;\n }\n }\n\n private _getCanvasClientRect() {\n if (this._$canvas == null) {\n return this.getBoundingClientRect();\n }\n\n return this._$canvas.getBoundingClientRect();\n }\n\n private _getMeasuredCanvasWidth(fallbackWidth: number) {\n if (!this.autoResizeCanvas && this._$canvas && this._$canvas.width > 0) {\n return this._$canvas.width;\n }\n\n return fallbackWidth;\n }\n\n private _getMeasuredCanvasHeight(fallbackHeight: number) {\n if (!this.autoResizeCanvas && this._$canvas && this._$canvas.height > 0) {\n return this._$canvas.height;\n }\n\n return fallbackHeight;\n }\n\n private _getViewportScaleX(canvasRect: DOMRect) {\n return canvasRect.width > 0\n ? this._viewRect.width / canvasRect.width\n : 1;\n }\n\n private _getViewportScaleY(canvasRect: DOMRect) {\n return canvasRect.height > 0\n ? this._viewRect.height / canvasRect.height\n : 1;\n }\n\n render() {\n return html`\n <div\n id=\"root\"\n part=\"scroller\"\n @scroll=${this._onScroll}\n @pointerdown=${this._onPointerEvent}\n @pointermove=${this._onPointerEvent}\n @pointerup=${this._onPointerEvent}\n @click=${this._onPointerEvent}\n @dblclick=${this._onPointerEvent}\n @contextmenu=${this._onPointerEvent}\n >\n <div id=\"overlay\" part=\"overlay\">\n <slot name=\"overlay\"></slot>\n </div>\n <div\n id=\"placeholder\"\n style=${styleMap({\n width: `${this.contentWidth}px`,\n height: `${this.contentHeight}px`,\n })}\n ></div>\n </div>\n\n <slot @slotchange=${this._onSlotChange}></slot>\n `;\n }\n}\n\nexport default RCVirtualCanvas;\n"],"names":["virtualCanvasStyles","css","createRectSnapshot","rect","_RCVirtualCanvas","LitElement","time","event","entries","entry","devicePixelBoxSize","val","oldValue","e","el","x","y","options","clientX","clientY","canvasRect","scaleX","scaleY","reason","contentPoint","node","changed","width","height","fallbackWidth","fallbackHeight","html","styleMap","RCVirtualCanvas","__decorateClass","property","query","state"],"mappings":"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"}
|