vue-canvas-frame-headless 4.4.0 → 4.5.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 +12 -8
- package/dist/index.d.ts +15 -0
- package/dist/index.js +70 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[![NPM downloads][badge-npm-downloads]][url-npm]
|
|
6
6
|
[![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This package is a headless canvas frame with no styling of its own. `useCanvasFrame` draws content in an isolated document at a width, scales it down to fit a narrower pane, and adopts a stylesheet into that document.
|
|
9
9
|
|
|
10
10
|
Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs).
|
|
11
11
|
|
|
@@ -19,10 +19,14 @@ Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/
|
|
|
19
19
|
|
|
20
20
|
## <a name="getting-started">🚀 Getting Started</a>
|
|
21
21
|
|
|
22
|
+
1. Install the package.
|
|
23
|
+
|
|
22
24
|
```bash
|
|
23
25
|
pnpm i vue-canvas-frame-headless
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
2. Create a component from this code. The slot content appears inside the frame.
|
|
29
|
+
|
|
26
30
|
```vue
|
|
27
31
|
<script setup lang="ts">
|
|
28
32
|
import { CANVAS_FRAME_SRCDOC, useCanvasFrame } from "vue-canvas-frame-headless";
|
|
@@ -51,14 +55,14 @@ const { frameBody, frameStyle } = useCanvasFrame({
|
|
|
51
55
|
|
|
52
56
|
## <a name="key-exports">🧩 Key Exports</a>
|
|
53
57
|
|
|
54
|
-
| Export | Kind | Purpose
|
|
55
|
-
| --------------------- | ---------- |
|
|
56
|
-
| `CANVAS_FRAME_SRCDOC` | const | The document every frame starts from, before
|
|
57
|
-
| `getFrameScale` | function | The scale that fits a frame to a pane
|
|
58
|
-
| `getFrameWidth` | function | The frame's width: its least width, or the pane's width when it fills a wider pane
|
|
59
|
-
| `useCanvasFrame` | composable | Draws content in an isolated document at a width, scaled down to fit a narrower pane
|
|
58
|
+
| Export | Kind | Purpose |
|
|
59
|
+
| --------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `CANVAS_FRAME_SRCDOC` | const | The document that every frame starts from, before the page markup is teleported into it |
|
|
61
|
+
| `getFrameScale` | function | The scale that fits a frame to a pane. It is never above 1, and it is 1 while the pane is unmeasured. |
|
|
62
|
+
| `getFrameWidth` | function | The frame's width: its least width, or the pane's width when it fills a wider pane |
|
|
63
|
+
| `useCanvasFrame` | composable | Draws content in an isolated document at a width, scaled down to fit a narrower pane |
|
|
60
64
|
|
|
61
|
-
`getFrameWidth` and `getFrameScale` are the pure geometry
|
|
65
|
+
`getFrameWidth` and `getFrameScale` are the pure geometry that `useCanvasFrame` uses. They are exported on their own.
|
|
62
66
|
|
|
63
67
|
## <a name="license">⚖️ License</a>
|
|
64
68
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ComputedRef, MaybeRefOrGetter, Ref } from "vue";
|
|
2
|
+
export declare const useCanvasFrame: ({ css, fill, frame, pane, width }: {
|
|
3
|
+
css: MaybeRefOrGetter<string>;
|
|
4
|
+
fill?: MaybeRefOrGetter<boolean>;
|
|
5
|
+
frame: Ref<HTMLIFrameElement | null | undefined>;
|
|
6
|
+
pane: Ref<HTMLElement | null | undefined>;
|
|
7
|
+
width: MaybeRefOrGetter<number>;
|
|
8
|
+
}) => {
|
|
9
|
+
frameBody: ComputedRef<HTMLElement | undefined>;
|
|
10
|
+
frameStyle: ComputedRef<Record<string, string>>;
|
|
11
|
+
loaded: Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare const CANVAS_FRAME_SRCDOC = "<!doctype html><html><head></head><body></body></html>";
|
|
14
|
+
export declare const getFrameScale: (paneWidth: number, frameWidth: number) => number;
|
|
15
|
+
export declare const getFrameWidth: (width: number, fill: boolean, paneWidth: number) => number;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { computed, onMounted, onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
// src/services/getFrameScale.ts
|
|
3
|
+
const getFrameScale = (paneWidth, frameWidth) => paneWidth > 0 ? Math.min(1, paneWidth / frameWidth) : 1;
|
|
4
|
+
// src/services/getFrameWidth.ts
|
|
5
|
+
const getFrameWidth = (width, fill, paneWidth) => fill ? Math.max(paneWidth, width) : width;
|
|
6
|
+
// src/composables/useCanvasFrame.ts
|
|
7
|
+
const useCanvasFrame = ({ css, fill, frame, pane, width }) => {
|
|
8
|
+
const { promise: loaded, resolve: resolveLoaded } = Promise.withResolvers();
|
|
9
|
+
const frameDocument = shallowRef();
|
|
10
|
+
const frameStyleSheet = shallowRef();
|
|
11
|
+
const paneHeight = shallowRef(0);
|
|
12
|
+
const paneWidth = shallowRef(0);
|
|
13
|
+
const frameBody = computed(() => frameDocument.value?.body);
|
|
14
|
+
const frameWidth = computed(() => getFrameWidth(toValue(width), toValue(fill ?? false), paneWidth.value));
|
|
15
|
+
const frameScale = computed(() => getFrameScale(paneWidth.value, frameWidth.value));
|
|
16
|
+
const frameStyle = computed(() => ({
|
|
17
|
+
height: `${paneHeight.value / frameScale.value}px`,
|
|
18
|
+
transform: `scale(${frameScale.value})`,
|
|
19
|
+
transformOrigin: "0 0",
|
|
20
|
+
width: `${frameWidth.value}px`
|
|
21
|
+
}));
|
|
22
|
+
const updatePaneSize = (paneElement) => {
|
|
23
|
+
paneWidth.value = paneElement.clientWidth;
|
|
24
|
+
paneHeight.value = paneElement.clientHeight;
|
|
25
|
+
};
|
|
26
|
+
let resizeObserver;
|
|
27
|
+
onMounted(() => {
|
|
28
|
+
const paneElement = pane.value;
|
|
29
|
+
if (!paneElement) return;
|
|
30
|
+
updatePaneSize(paneElement);
|
|
31
|
+
resizeObserver = new ResizeObserver(() => {
|
|
32
|
+
updatePaneSize(paneElement);
|
|
33
|
+
});
|
|
34
|
+
resizeObserver.observe(paneElement);
|
|
35
|
+
});
|
|
36
|
+
onScopeDispose(() => resizeObserver?.disconnect());
|
|
37
|
+
const handleLoad = () => {
|
|
38
|
+
const contentWindow = frame.value?.contentWindow;
|
|
39
|
+
const contentDocument = frame.value?.contentDocument;
|
|
40
|
+
if (!contentWindow || !contentDocument) return;
|
|
41
|
+
const styleSheet = new contentWindow.CSSStyleSheet();
|
|
42
|
+
contentDocument.adoptedStyleSheets = [styleSheet];
|
|
43
|
+
frameStyleSheet.value = styleSheet;
|
|
44
|
+
frameDocument.value = contentDocument;
|
|
45
|
+
resolveLoaded();
|
|
46
|
+
};
|
|
47
|
+
watch(frame, (frameElement, _previousFrameElement, onCleanup) => {
|
|
48
|
+
frameElement?.addEventListener("load", handleLoad);
|
|
49
|
+
onCleanup(() => {
|
|
50
|
+
frameElement?.removeEventListener("load", handleLoad);
|
|
51
|
+
});
|
|
52
|
+
}, {
|
|
53
|
+
flush: "sync",
|
|
54
|
+
immediate: true
|
|
55
|
+
});
|
|
56
|
+
watch([frameStyleSheet, () => toValue(css)], ([styleSheet, cssText], _previous, onCleanup) => {
|
|
57
|
+
const styleFrame = requestAnimationFrame(() => styleSheet?.replaceSync(cssText));
|
|
58
|
+
onCleanup(() => {
|
|
59
|
+
cancelAnimationFrame(styleFrame);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
frameBody,
|
|
64
|
+
frameStyle,
|
|
65
|
+
loaded
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
// src/services/constants.ts
|
|
69
|
+
const CANVAS_FRAME_SRCDOC = "<!doctype html><html><head></head><body></body></html>";
|
|
70
|
+
export { CANVAS_FRAME_SRCDOC, getFrameScale, getFrameWidth, useCanvasFrame };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-canvas-frame-headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "A headless canvas frame: an isolated document at a width, scaled down to fit a narrower pane, with its styles adopted into it, and no styling of its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"headless",
|
|
@@ -43,13 +43,14 @@
|
|
|
43
43
|
"typecheck": "tsrs --fast"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@esposter/configuration": "4.
|
|
46
|
+
"@esposter/configuration": "4.5.0",
|
|
47
|
+
"@types/node": "^26.6.6",
|
|
47
48
|
"@vue/test-utils": "^2.5.1",
|
|
48
49
|
"happy-dom": "^21.0.4",
|
|
49
|
-
"tsdown": "0.23.
|
|
50
|
+
"tsdown": "^0.23.5"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"vue": "^3.5.44"
|
|
53
54
|
},
|
|
54
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "e30c001f3b4b15693e75fb53092d62a97ade4d2b"
|
|
55
56
|
}
|