@fitin/vue 0.1.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 ADDED
@@ -0,0 +1,5 @@
1
+ # Vue 3 + TypeScript + Vite
2
+
3
+ This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4
+
5
+ Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
@@ -0,0 +1,151 @@
1
+ import { computed as e, createElementBlock as t, defineComponent as n, inject as r, normalizeClass as i, normalizeStyle as a, openBlock as o, provide as s, reactive as c, ref as l, renderSlot as u, watchEffect as d } from "vue";
2
+ import { BehaviorSubject as f } from "rxjs";
3
+ import { Fitin as p } from "@fitin/core";
4
+ //#region src/components/keys.ts
5
+ var m = Symbol("fitin-canvas"), h = Symbol("fitin-container");
6
+ //#endregion
7
+ //#region src/components/logging.ts
8
+ function g(e) {
9
+ console.error("[@fitin/vue]", e);
10
+ }
11
+ function _(e) {
12
+ console.warn("[@fitin/vue]", e);
13
+ }
14
+ //#endregion
15
+ //#region src/components/fitin-canvas.vue
16
+ var v = /* @__PURE__ */ n({
17
+ inheritAttrs: !1,
18
+ __name: "fitin-canvas",
19
+ props: {
20
+ designWidth: {},
21
+ designHeight: {},
22
+ mode: {},
23
+ minDesignWidth: {},
24
+ minDesignHeight: {},
25
+ wait: {},
26
+ computeScaleXY: { type: Function },
27
+ viewport: {},
28
+ center: { type: Boolean }
29
+ },
30
+ setup(n) {
31
+ let i = n, c = l(), h = e(() => i.center ? {
32
+ position: "absolute",
33
+ top: "50%",
34
+ left: "50%",
35
+ transform: "translate(-50%, -50%)",
36
+ overflow: "hidden"
37
+ } : {
38
+ position: "relative",
39
+ overflow: "hidden"
40
+ }), _, v = new f(void 0);
41
+ s(m, v.asObservable());
42
+ let y = r(m);
43
+ return d((e) => {
44
+ if (y) {
45
+ g("Nested fitin-canvas is forbidden");
46
+ return;
47
+ }
48
+ _ = new p(i);
49
+ let t = (e) => {
50
+ e && c.value && (c.value.style.width = `${e.effectiveWidth}px`, c.value.style.height = `${e.effectiveHeight}px`), v.next(e);
51
+ }, n = i.viewport === "parent" ? c.value?.parentElement : i.viewport;
52
+ n ? _.subscribe(n, t) : _.subscribe(t), e(() => {
53
+ _.dispose();
54
+ });
55
+ }), (e, n) => (o(), t("section", {
56
+ ref_key: "canvasRef",
57
+ ref: c,
58
+ role: "fitin-canvas",
59
+ style: a(h.value)
60
+ }, [u(e.$slots, "default")], 4));
61
+ }
62
+ });
63
+ //#endregion
64
+ //#region src/components/hooks.ts
65
+ function y(e) {
66
+ let t = r(h) || [];
67
+ t.push(e), s(h, t);
68
+ }
69
+ function b(e) {
70
+ let t = r(m);
71
+ return t ? ((r(h) || []).find((e) => e === "affect-descendant") && _("There's an ancestor fitin-container resizing all descendants"), d((n) => {
72
+ let r = t.subscribe(e);
73
+ n(() => r.unsubscribe());
74
+ })) : g("A fitin-container must be used within a fitin-canvas context"), t;
75
+ }
76
+ function x() {
77
+ let e = c({
78
+ scaleX: 1,
79
+ scaleY: 1,
80
+ scaleXY: 1,
81
+ effectiveWidth: void 0,
82
+ effectiveHeight: void 0
83
+ });
84
+ return b((t) => {
85
+ t && (e.scaleX = t.scaleX, e.scaleY = t.scaleY, e.scaleXY = t.scaleXY, e.effectiveWidth = t.effectiveWidth, e.effectiveHeight = t.effectiveHeight);
86
+ }), e;
87
+ }
88
+ //#endregion
89
+ //#region src/components/fitin-container.vue?vue&type=script&setup=true&lang.ts
90
+ var S = ["data-scope"], C = /* @__PURE__ */ n({
91
+ inheritAttrs: !1,
92
+ __name: "fitin-container",
93
+ props: {
94
+ designWidth: {},
95
+ designHeight: {},
96
+ transformOriginX: {},
97
+ transformOriginY: {},
98
+ style: {},
99
+ class: {}
100
+ },
101
+ setup(n) {
102
+ let r = n, s = e(() => r.transformOriginX || r.transformOriginY ? "affect-descendant" : "affect-self");
103
+ y(s.value);
104
+ let c = l({});
105
+ function f(e, t) {
106
+ if (t.transformOriginX || t.transformOriginY) {
107
+ let n, r;
108
+ if (t.transformOriginX && t.transformOriginY) {
109
+ let i = e.transformXY(t.transformOriginX, t.transformOriginY);
110
+ n = i.transform, r = i.transformOrigin;
111
+ } else if (t.transformOriginX) {
112
+ let i = e.transformX(t.transformOriginX);
113
+ n = i.transform, r = i.transformOrigin;
114
+ } else {
115
+ let i = e.transformY(t.transformOriginY);
116
+ n = i.transform, r = i.transformOrigin;
117
+ }
118
+ c.value = {
119
+ position: "absolute",
120
+ width: `${t.designWidth}px`,
121
+ height: `${t.designHeight}px`,
122
+ transform: n,
123
+ transformOrigin: r
124
+ };
125
+ } else c.value = {
126
+ width: `${e.scaleX * t.designWidth}px`,
127
+ height: `${e.scaleY * t.designHeight}px`
128
+ };
129
+ }
130
+ let p = b();
131
+ return d((e) => {
132
+ if (p) {
133
+ let t = p.subscribe((e) => {
134
+ e && f(e, r);
135
+ });
136
+ e(() => t.unsubscribe());
137
+ }
138
+ }), (e, n) => (o(), t("section", {
139
+ role: "fitin-container",
140
+ "data-scope": s.value,
141
+ style: a([r.style, c.value]),
142
+ class: i(r.class)
143
+ }, [u(e.$slots, "default")], 14, S));
144
+ }
145
+ }), w = { install(e) {
146
+ e.component("FitinCanvas", v), e.component("FitinContainer", C);
147
+ } };
148
+ //#endregion
149
+ export { v as FitinCanvas, C as FitinContainer, w as plugin, b as useFitin, x as useFitinState };
150
+
151
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/components/keys.ts","../src/components/logging.ts","../src/components/fitin-canvas.vue","../src/components/fitin-canvas.vue","../src/components/hooks.ts","../src/components/fitin-container.vue","../src/components/fitin-container.vue","../src/index.ts"],"sourcesContent":["import { type InjectionKey } from \"vue\";\r\nimport type { Observable } from \"rxjs\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\n\r\nexport const FitinCanvasKey: InjectionKey<\r\n Observable<IResizeEvent | undefined>\r\n> = Symbol(\"fitin-canvas\");\r\n\r\nexport const FitinContainerKey: InjectionKey<\r\n (\"affect-self\" | \"affect-descendant\")[]\r\n> = Symbol(\"fitin-container\");\r\n","export function logE(message: string): void {\r\n console.error(\"[@fitin/vue]\", message);\r\n}\r\nexport function logW(message: string): void {\r\n console.warn(\"[@fitin/vue]\", message);\r\n}\r\n","<script setup lang=\"ts\">\r\nimport {\r\n computed,\r\n inject,\r\n provide,\r\n ref,\r\n watchEffect,\r\n type CSSProperties,\r\n} from \"vue\";\r\nimport { BehaviorSubject } from \"rxjs\";\r\nimport { Fitin, type Mode, type IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey } from \"./keys\";\r\nimport { logE } from \"./logging\";\r\n\r\n/**\r\n * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).\r\n * However, these are exactly the types that `FitinOptions` depends on.\r\n * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).\r\n */\r\ntype FitinCanvasProps = {\r\n designWidth: number; // physical design width of container\r\n designHeight: number; // physical design height of container\r\n mode?: Mode;\r\n minDesignWidth?: number; // minimum physical design width of container\r\n minDesignHeight?: number; // minimum physical design height of container\r\n wait?: number; // in ms\r\n computeScaleXY?: (\r\n designWidth: number,\r\n designHeight: number,\r\n scaleX: number,\r\n scaleY: number,\r\n ) => number;\r\n viewport?: HTMLElement | \"parent\";\r\n center?: boolean;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\nconst props = defineProps<FitinCanvasProps>();\r\n\r\nconst canvasRef = ref<HTMLElement>();\r\n\r\nconst canvasStyle = computed<CSSProperties>(() =>\r\n props.center\r\n ? {\r\n position: \"absolute\",\r\n top: \"50%\",\r\n left: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n overflow: \"hidden\"\r\n }\r\n : {\r\n position: \"relative\",\r\n overflow: \"hidden\"\r\n },\r\n);\r\n\r\nlet fitin: Fitin;\r\nlet subject = new BehaviorSubject<IResizeEvent | undefined>(undefined);\r\nprovide(FitinCanvasKey, subject.asObservable());\r\nconst resizeObservable = inject(FitinCanvasKey);\r\n\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n logE(\"Nested fitin-canvas is forbidden\");\r\n return;\r\n }\r\n\r\n fitin = new Fitin(props);\r\n\r\n const handleResize = (event: IResizeEvent) => {\r\n if (event && canvasRef.value) {\r\n canvasRef.value.style.width = `${event.effectiveWidth}px`;\r\n canvasRef.value.style.height = `${event.effectiveHeight}px`;\r\n }\r\n subject.next(event);\r\n };\r\n\r\n const viewport =\r\n props.viewport === \"parent\"\r\n ? canvasRef.value?.parentElement\r\n : props.viewport;\r\n if (viewport) {\r\n fitin.subscribe(viewport, handleResize);\r\n } else {\r\n fitin.subscribe(handleResize);\r\n }\r\n\r\n onCleanup(() => {\r\n fitin.dispose();\r\n });\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n ref=\"canvasRef\"\r\n role=\"fitin-canvas\"\r\n :style=\"canvasStyle\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport {\r\n computed,\r\n inject,\r\n provide,\r\n ref,\r\n watchEffect,\r\n type CSSProperties,\r\n} from \"vue\";\r\nimport { BehaviorSubject } from \"rxjs\";\r\nimport { Fitin, type Mode, type IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey } from \"./keys\";\r\nimport { logE } from \"./logging\";\r\n\r\n/**\r\n * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).\r\n * However, these are exactly the types that `FitinOptions` depends on.\r\n * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).\r\n */\r\ntype FitinCanvasProps = {\r\n designWidth: number; // physical design width of container\r\n designHeight: number; // physical design height of container\r\n mode?: Mode;\r\n minDesignWidth?: number; // minimum physical design width of container\r\n minDesignHeight?: number; // minimum physical design height of container\r\n wait?: number; // in ms\r\n computeScaleXY?: (\r\n designWidth: number,\r\n designHeight: number,\r\n scaleX: number,\r\n scaleY: number,\r\n ) => number;\r\n viewport?: HTMLElement | \"parent\";\r\n center?: boolean;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\nconst props = defineProps<FitinCanvasProps>();\r\n\r\nconst canvasRef = ref<HTMLElement>();\r\n\r\nconst canvasStyle = computed<CSSProperties>(() =>\r\n props.center\r\n ? {\r\n position: \"absolute\",\r\n top: \"50%\",\r\n left: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n overflow: \"hidden\"\r\n }\r\n : {\r\n position: \"relative\",\r\n overflow: \"hidden\"\r\n },\r\n);\r\n\r\nlet fitin: Fitin;\r\nlet subject = new BehaviorSubject<IResizeEvent | undefined>(undefined);\r\nprovide(FitinCanvasKey, subject.asObservable());\r\nconst resizeObservable = inject(FitinCanvasKey);\r\n\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n logE(\"Nested fitin-canvas is forbidden\");\r\n return;\r\n }\r\n\r\n fitin = new Fitin(props);\r\n\r\n const handleResize = (event: IResizeEvent) => {\r\n if (event && canvasRef.value) {\r\n canvasRef.value.style.width = `${event.effectiveWidth}px`;\r\n canvasRef.value.style.height = `${event.effectiveHeight}px`;\r\n }\r\n subject.next(event);\r\n };\r\n\r\n const viewport =\r\n props.viewport === \"parent\"\r\n ? canvasRef.value?.parentElement\r\n : props.viewport;\r\n if (viewport) {\r\n fitin.subscribe(viewport, handleResize);\r\n } else {\r\n fitin.subscribe(handleResize);\r\n }\r\n\r\n onCleanup(() => {\r\n fitin.dispose();\r\n });\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n ref=\"canvasRef\"\r\n role=\"fitin-canvas\"\r\n :style=\"canvasStyle\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","import { inject, provide, reactive, watchEffect } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey, FitinContainerKey } from \"./keys\";\r\nimport { logE, logW } from \"./logging\";\r\nimport type { Observable } from \"rxjs\";\r\n\r\nexport function provideFitinContainer(\r\n value: \"affect-self\" | \"affect-descendant\",\r\n) {\r\n const paths = inject(FitinContainerKey) || [];\r\n paths.push(value);\r\n provide(FitinContainerKey, paths);\r\n}\r\n\r\nexport function useFitin(\r\n resizeHandler?: (event: undefined | IResizeEvent) => void,\r\n): undefined | Observable<undefined | IResizeEvent> {\r\n const resizeObservable = inject(FitinCanvasKey);\r\n if (resizeObservable) {\r\n const paths = inject(FitinContainerKey) || [];\r\n if (paths.find((path) => path === \"affect-descendant\")) {\r\n logW(\"There's an ancestor fitin-container resizing all descendants\");\r\n }\r\n\r\n watchEffect((onCleanup) => {\r\n const subscription = resizeObservable.subscribe(resizeHandler);\r\n onCleanup(() => subscription.unsubscribe());\r\n });\r\n } else {\r\n logE(\"A fitin-container must be used within a fitin-canvas context\");\r\n }\r\n\r\n return resizeObservable;\r\n}\r\n\r\nexport function useFitinState() {\r\n const state = reactive<{\r\n scaleX: number;\r\n scaleY: number;\r\n scaleXY: number;\r\n effectiveWidth: undefined | number;\r\n effectiveHeight: undefined | number;\r\n }>({\r\n scaleX: 1,\r\n scaleY: 1,\r\n scaleXY: 1,\r\n effectiveWidth: undefined,\r\n effectiveHeight: undefined,\r\n });\r\n useFitin((event) => {\r\n if (event) {\r\n state.scaleX = event.scaleX;\r\n state.scaleY = event.scaleY;\r\n state.scaleXY = event.scaleXY;\r\n state.effectiveWidth = event.effectiveWidth;\r\n state.effectiveHeight = event.effectiveHeight;\r\n }\r\n });\r\n\r\n return state;\r\n}\r\n","<script setup lang=\"ts\">\r\nimport { ref, watchEffect, computed, type CSSProperties } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { provideFitinContainer, useFitin } from \"./hooks\";\r\n\r\ntype FitinContainerProps = {\r\n designWidth: number;\r\n designHeight: number;\r\n transformOriginX?: \"left\" | \"center\" | \"right\";\r\n transformOriginY?: \"top\" | \"center\" | \"bottom\";\r\n style?: CSSProperties;\r\n class?: string;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\n\r\nconst props = defineProps<FitinContainerProps>();\r\n\r\nconst affectScope = computed(() =>\r\n props.transformOriginX || props.transformOriginY\r\n ? \"affect-descendant\"\r\n : \"affect-self\",\r\n);\r\n\r\nprovideFitinContainer(affectScope.value);\r\n\r\nconst fitinStyle = ref<CSSProperties>({});\r\n\r\nfunction adjustContainer(event: IResizeEvent, props: FitinContainerProps) {\r\n if (props.transformOriginX || props.transformOriginY) {\r\n let transform: string;\r\n let transformOrigin: string;\r\n if (props.transformOriginX && props.transformOriginY) {\r\n const res = event.transformXY(\r\n props.transformOriginX,\r\n props.transformOriginY,\r\n );\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else if (props.transformOriginX) {\r\n const res = event.transformX(props.transformOriginX);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else {\r\n const res = event.transformY(props.transformOriginY!);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n }\r\n fitinStyle.value = {\r\n position: \"absolute\",\r\n width: `${props.designWidth}px`,\r\n height: `${props.designHeight}px`,\r\n transform: transform,\r\n transformOrigin: transformOrigin,\r\n };\r\n } else {\r\n fitinStyle.value = {\r\n width: `${event.scaleX * props.designWidth}px`,\r\n height: `${event.scaleY * props.designHeight}px`,\r\n };\r\n }\r\n}\r\n\r\nconst resizeObservable = useFitin();\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n const subscription = resizeObservable.subscribe(\r\n (event: IResizeEvent | undefined) => {\r\n if (event) {\r\n adjustContainer(event, props);\r\n }\r\n },\r\n );\r\n onCleanup(() => subscription.unsubscribe());\r\n }\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n role=\"fitin-container\"\r\n :data-scope=\"affectScope\"\r\n :style=\"[props.style, fitinStyle]\"\r\n :class=\"props.class\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { ref, watchEffect, computed, type CSSProperties } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { provideFitinContainer, useFitin } from \"./hooks\";\r\n\r\ntype FitinContainerProps = {\r\n designWidth: number;\r\n designHeight: number;\r\n transformOriginX?: \"left\" | \"center\" | \"right\";\r\n transformOriginY?: \"top\" | \"center\" | \"bottom\";\r\n style?: CSSProperties;\r\n class?: string;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\n\r\nconst props = defineProps<FitinContainerProps>();\r\n\r\nconst affectScope = computed(() =>\r\n props.transformOriginX || props.transformOriginY\r\n ? \"affect-descendant\"\r\n : \"affect-self\",\r\n);\r\n\r\nprovideFitinContainer(affectScope.value);\r\n\r\nconst fitinStyle = ref<CSSProperties>({});\r\n\r\nfunction adjustContainer(event: IResizeEvent, props: FitinContainerProps) {\r\n if (props.transformOriginX || props.transformOriginY) {\r\n let transform: string;\r\n let transformOrigin: string;\r\n if (props.transformOriginX && props.transformOriginY) {\r\n const res = event.transformXY(\r\n props.transformOriginX,\r\n props.transformOriginY,\r\n );\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else if (props.transformOriginX) {\r\n const res = event.transformX(props.transformOriginX);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else {\r\n const res = event.transformY(props.transformOriginY!);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n }\r\n fitinStyle.value = {\r\n position: \"absolute\",\r\n width: `${props.designWidth}px`,\r\n height: `${props.designHeight}px`,\r\n transform: transform,\r\n transformOrigin: transformOrigin,\r\n };\r\n } else {\r\n fitinStyle.value = {\r\n width: `${event.scaleX * props.designWidth}px`,\r\n height: `${event.scaleY * props.designHeight}px`,\r\n };\r\n }\r\n}\r\n\r\nconst resizeObservable = useFitin();\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n const subscription = resizeObservable.subscribe(\r\n (event: IResizeEvent | undefined) => {\r\n if (event) {\r\n adjustContainer(event, props);\r\n }\r\n },\r\n );\r\n onCleanup(() => subscription.unsubscribe());\r\n }\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n role=\"fitin-container\"\r\n :data-scope=\"affectScope\"\r\n :style=\"[props.style, fitinStyle]\"\r\n :class=\"props.class\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","import type { App, Plugin } from \"vue\";\r\nimport FitinCanvas from \"./components/fitin-canvas.vue\";\r\nimport FitinContainer from \"./components/fitin-container.vue\";\r\nexport { useFitin, useFitinState } from \"./components/hooks\";\r\n\r\nexport { FitinCanvas, FitinContainer };\r\nexport const plugin: Plugin = {\r\n install(app: App) {\r\n app.component(\"FitinCanvas\", FitinCanvas);\r\n app.component(\"FitinContainer\", FitinContainer);\r\n },\r\n};\r\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("vue"),require("rxjs"),require("@fitin/core")):typeof define==`function`&&define.amd?define([`exports`,`vue`,`rxjs`,`@fitin/core`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Fitin={},e.Vue,e.rxjs,e.FitinCore))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var i=Symbol(`fitin-canvas`),a=Symbol(`fitin-container`);function o(e){console.error(`[@fitin/vue]`,e)}function s(e){console.warn(`[@fitin/vue]`,e)}var c=(0,t.defineComponent)({inheritAttrs:!1,__name:`fitin-canvas`,props:{designWidth:{},designHeight:{},mode:{},minDesignWidth:{},minDesignHeight:{},wait:{},computeScaleXY:{type:Function},viewport:{},center:{type:Boolean}},setup(e){let a=e,s=(0,t.ref)(),c=(0,t.computed)(()=>a.center?{position:`absolute`,top:`50%`,left:`50%`,transform:`translate(-50%, -50%)`,overflow:`hidden`}:{position:`relative`,overflow:`hidden`}),l,u=new n.BehaviorSubject(void 0);(0,t.provide)(i,u.asObservable());let d=(0,t.inject)(i);return(0,t.watchEffect)(e=>{if(d){o(`Nested fitin-canvas is forbidden`);return}l=new r.Fitin(a);let t=e=>{e&&s.value&&(s.value.style.width=`${e.effectiveWidth}px`,s.value.style.height=`${e.effectiveHeight}px`),u.next(e)},n=a.viewport===`parent`?s.value?.parentElement:a.viewport;n?l.subscribe(n,t):l.subscribe(t),e(()=>{l.dispose()})}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`section`,{ref_key:`canvasRef`,ref:s,role:`fitin-canvas`,style:(0,t.normalizeStyle)(c.value)},[(0,t.renderSlot)(e.$slots,`default`)],4))}});function l(e){let n=(0,t.inject)(a)||[];n.push(e),(0,t.provide)(a,n)}function u(e){let n=(0,t.inject)(i);return n?(((0,t.inject)(a)||[]).find(e=>e===`affect-descendant`)&&s(`There's an ancestor fitin-container resizing all descendants`),(0,t.watchEffect)(t=>{let r=n.subscribe(e);t(()=>r.unsubscribe())})):o(`A fitin-container must be used within a fitin-canvas context`),n}function d(){let e=(0,t.reactive)({scaleX:1,scaleY:1,scaleXY:1,effectiveWidth:void 0,effectiveHeight:void 0});return u(t=>{t&&(e.scaleX=t.scaleX,e.scaleY=t.scaleY,e.scaleXY=t.scaleXY,e.effectiveWidth=t.effectiveWidth,e.effectiveHeight=t.effectiveHeight)}),e}var f=[`data-scope`],p=(0,t.defineComponent)({inheritAttrs:!1,__name:`fitin-container`,props:{designWidth:{},designHeight:{},transformOriginX:{},transformOriginY:{},style:{},class:{}},setup(e){let n=e,r=(0,t.computed)(()=>n.transformOriginX||n.transformOriginY?`affect-descendant`:`affect-self`);l(r.value);let i=(0,t.ref)({});function a(e,t){if(t.transformOriginX||t.transformOriginY){let n,r;if(t.transformOriginX&&t.transformOriginY){let i=e.transformXY(t.transformOriginX,t.transformOriginY);n=i.transform,r=i.transformOrigin}else if(t.transformOriginX){let i=e.transformX(t.transformOriginX);n=i.transform,r=i.transformOrigin}else{let i=e.transformY(t.transformOriginY);n=i.transform,r=i.transformOrigin}i.value={position:`absolute`,width:`${t.designWidth}px`,height:`${t.designHeight}px`,transform:n,transformOrigin:r}}else i.value={width:`${e.scaleX*t.designWidth}px`,height:`${e.scaleY*t.designHeight}px`}}let o=u();return(0,t.watchEffect)(e=>{if(o){let t=o.subscribe(e=>{e&&a(e,n)});e(()=>t.unsubscribe())}}),(e,a)=>((0,t.openBlock)(),(0,t.createElementBlock)(`section`,{role:`fitin-container`,"data-scope":r.value,style:(0,t.normalizeStyle)([n.style,i.value]),class:(0,t.normalizeClass)(n.class)},[(0,t.renderSlot)(e.$slots,`default`)],14,f))}});e.FitinCanvas=c,e.FitinContainer=p,e.plugin={install(e){e.component(`FitinCanvas`,c),e.component(`FitinContainer`,p)}},e.useFitin=u,e.useFitinState=d});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","names":[],"sources":["../src/components/keys.ts","../src/components/logging.ts","../src/components/fitin-canvas.vue","../src/components/fitin-canvas.vue","../src/components/hooks.ts","../src/components/fitin-container.vue","../src/components/fitin-container.vue","../src/index.ts"],"sourcesContent":["import { type InjectionKey } from \"vue\";\r\nimport type { Observable } from \"rxjs\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\n\r\nexport const FitinCanvasKey: InjectionKey<\r\n Observable<IResizeEvent | undefined>\r\n> = Symbol(\"fitin-canvas\");\r\n\r\nexport const FitinContainerKey: InjectionKey<\r\n (\"affect-self\" | \"affect-descendant\")[]\r\n> = Symbol(\"fitin-container\");\r\n","export function logE(message: string): void {\r\n console.error(\"[@fitin/vue]\", message);\r\n}\r\nexport function logW(message: string): void {\r\n console.warn(\"[@fitin/vue]\", message);\r\n}\r\n","<script setup lang=\"ts\">\r\nimport {\r\n computed,\r\n inject,\r\n provide,\r\n ref,\r\n watchEffect,\r\n type CSSProperties,\r\n} from \"vue\";\r\nimport { BehaviorSubject } from \"rxjs\";\r\nimport { Fitin, type Mode, type IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey } from \"./keys\";\r\nimport { logE } from \"./logging\";\r\n\r\n/**\r\n * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).\r\n * However, these are exactly the types that `FitinOptions` depends on.\r\n * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).\r\n */\r\ntype FitinCanvasProps = {\r\n designWidth: number; // physical design width of container\r\n designHeight: number; // physical design height of container\r\n mode?: Mode;\r\n minDesignWidth?: number; // minimum physical design width of container\r\n minDesignHeight?: number; // minimum physical design height of container\r\n wait?: number; // in ms\r\n computeScaleXY?: (\r\n designWidth: number,\r\n designHeight: number,\r\n scaleX: number,\r\n scaleY: number,\r\n ) => number;\r\n viewport?: HTMLElement | \"parent\";\r\n center?: boolean;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\nconst props = defineProps<FitinCanvasProps>();\r\n\r\nconst canvasRef = ref<HTMLElement>();\r\n\r\nconst canvasStyle = computed<CSSProperties>(() =>\r\n props.center\r\n ? {\r\n position: \"absolute\",\r\n top: \"50%\",\r\n left: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n overflow: \"hidden\"\r\n }\r\n : {\r\n position: \"relative\",\r\n overflow: \"hidden\"\r\n },\r\n);\r\n\r\nlet fitin: Fitin;\r\nlet subject = new BehaviorSubject<IResizeEvent | undefined>(undefined);\r\nprovide(FitinCanvasKey, subject.asObservable());\r\nconst resizeObservable = inject(FitinCanvasKey);\r\n\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n logE(\"Nested fitin-canvas is forbidden\");\r\n return;\r\n }\r\n\r\n fitin = new Fitin(props);\r\n\r\n const handleResize = (event: IResizeEvent) => {\r\n if (event && canvasRef.value) {\r\n canvasRef.value.style.width = `${event.effectiveWidth}px`;\r\n canvasRef.value.style.height = `${event.effectiveHeight}px`;\r\n }\r\n subject.next(event);\r\n };\r\n\r\n const viewport =\r\n props.viewport === \"parent\"\r\n ? canvasRef.value?.parentElement\r\n : props.viewport;\r\n if (viewport) {\r\n fitin.subscribe(viewport, handleResize);\r\n } else {\r\n fitin.subscribe(handleResize);\r\n }\r\n\r\n onCleanup(() => {\r\n fitin.dispose();\r\n });\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n ref=\"canvasRef\"\r\n role=\"fitin-canvas\"\r\n :style=\"canvasStyle\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport {\r\n computed,\r\n inject,\r\n provide,\r\n ref,\r\n watchEffect,\r\n type CSSProperties,\r\n} from \"vue\";\r\nimport { BehaviorSubject } from \"rxjs\";\r\nimport { Fitin, type Mode, type IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey } from \"./keys\";\r\nimport { logE } from \"./logging\";\r\n\r\n/**\r\n * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).\r\n * However, these are exactly the types that `FitinOptions` depends on.\r\n * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).\r\n */\r\ntype FitinCanvasProps = {\r\n designWidth: number; // physical design width of container\r\n designHeight: number; // physical design height of container\r\n mode?: Mode;\r\n minDesignWidth?: number; // minimum physical design width of container\r\n minDesignHeight?: number; // minimum physical design height of container\r\n wait?: number; // in ms\r\n computeScaleXY?: (\r\n designWidth: number,\r\n designHeight: number,\r\n scaleX: number,\r\n scaleY: number,\r\n ) => number;\r\n viewport?: HTMLElement | \"parent\";\r\n center?: boolean;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\nconst props = defineProps<FitinCanvasProps>();\r\n\r\nconst canvasRef = ref<HTMLElement>();\r\n\r\nconst canvasStyle = computed<CSSProperties>(() =>\r\n props.center\r\n ? {\r\n position: \"absolute\",\r\n top: \"50%\",\r\n left: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n overflow: \"hidden\"\r\n }\r\n : {\r\n position: \"relative\",\r\n overflow: \"hidden\"\r\n },\r\n);\r\n\r\nlet fitin: Fitin;\r\nlet subject = new BehaviorSubject<IResizeEvent | undefined>(undefined);\r\nprovide(FitinCanvasKey, subject.asObservable());\r\nconst resizeObservable = inject(FitinCanvasKey);\r\n\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n logE(\"Nested fitin-canvas is forbidden\");\r\n return;\r\n }\r\n\r\n fitin = new Fitin(props);\r\n\r\n const handleResize = (event: IResizeEvent) => {\r\n if (event && canvasRef.value) {\r\n canvasRef.value.style.width = `${event.effectiveWidth}px`;\r\n canvasRef.value.style.height = `${event.effectiveHeight}px`;\r\n }\r\n subject.next(event);\r\n };\r\n\r\n const viewport =\r\n props.viewport === \"parent\"\r\n ? canvasRef.value?.parentElement\r\n : props.viewport;\r\n if (viewport) {\r\n fitin.subscribe(viewport, handleResize);\r\n } else {\r\n fitin.subscribe(handleResize);\r\n }\r\n\r\n onCleanup(() => {\r\n fitin.dispose();\r\n });\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n ref=\"canvasRef\"\r\n role=\"fitin-canvas\"\r\n :style=\"canvasStyle\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","import { inject, provide, reactive, watchEffect } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { FitinCanvasKey, FitinContainerKey } from \"./keys\";\r\nimport { logE, logW } from \"./logging\";\r\nimport type { Observable } from \"rxjs\";\r\n\r\nexport function provideFitinContainer(\r\n value: \"affect-self\" | \"affect-descendant\",\r\n) {\r\n const paths = inject(FitinContainerKey) || [];\r\n paths.push(value);\r\n provide(FitinContainerKey, paths);\r\n}\r\n\r\nexport function useFitin(\r\n resizeHandler?: (event: undefined | IResizeEvent) => void,\r\n): undefined | Observable<undefined | IResizeEvent> {\r\n const resizeObservable = inject(FitinCanvasKey);\r\n if (resizeObservable) {\r\n const paths = inject(FitinContainerKey) || [];\r\n if (paths.find((path) => path === \"affect-descendant\")) {\r\n logW(\"There's an ancestor fitin-container resizing all descendants\");\r\n }\r\n\r\n watchEffect((onCleanup) => {\r\n const subscription = resizeObservable.subscribe(resizeHandler);\r\n onCleanup(() => subscription.unsubscribe());\r\n });\r\n } else {\r\n logE(\"A fitin-container must be used within a fitin-canvas context\");\r\n }\r\n\r\n return resizeObservable;\r\n}\r\n\r\nexport function useFitinState() {\r\n const state = reactive<{\r\n scaleX: number;\r\n scaleY: number;\r\n scaleXY: number;\r\n effectiveWidth: undefined | number;\r\n effectiveHeight: undefined | number;\r\n }>({\r\n scaleX: 1,\r\n scaleY: 1,\r\n scaleXY: 1,\r\n effectiveWidth: undefined,\r\n effectiveHeight: undefined,\r\n });\r\n useFitin((event) => {\r\n if (event) {\r\n state.scaleX = event.scaleX;\r\n state.scaleY = event.scaleY;\r\n state.scaleXY = event.scaleXY;\r\n state.effectiveWidth = event.effectiveWidth;\r\n state.effectiveHeight = event.effectiveHeight;\r\n }\r\n });\r\n\r\n return state;\r\n}\r\n","<script setup lang=\"ts\">\r\nimport { ref, watchEffect, computed, type CSSProperties } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { provideFitinContainer, useFitin } from \"./hooks\";\r\n\r\ntype FitinContainerProps = {\r\n designWidth: number;\r\n designHeight: number;\r\n transformOriginX?: \"left\" | \"center\" | \"right\";\r\n transformOriginY?: \"top\" | \"center\" | \"bottom\";\r\n style?: CSSProperties;\r\n class?: string;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\n\r\nconst props = defineProps<FitinContainerProps>();\r\n\r\nconst affectScope = computed(() =>\r\n props.transformOriginX || props.transformOriginY\r\n ? \"affect-descendant\"\r\n : \"affect-self\",\r\n);\r\n\r\nprovideFitinContainer(affectScope.value);\r\n\r\nconst fitinStyle = ref<CSSProperties>({});\r\n\r\nfunction adjustContainer(event: IResizeEvent, props: FitinContainerProps) {\r\n if (props.transformOriginX || props.transformOriginY) {\r\n let transform: string;\r\n let transformOrigin: string;\r\n if (props.transformOriginX && props.transformOriginY) {\r\n const res = event.transformXY(\r\n props.transformOriginX,\r\n props.transformOriginY,\r\n );\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else if (props.transformOriginX) {\r\n const res = event.transformX(props.transformOriginX);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else {\r\n const res = event.transformY(props.transformOriginY!);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n }\r\n fitinStyle.value = {\r\n position: \"absolute\",\r\n width: `${props.designWidth}px`,\r\n height: `${props.designHeight}px`,\r\n transform: transform,\r\n transformOrigin: transformOrigin,\r\n };\r\n } else {\r\n fitinStyle.value = {\r\n width: `${event.scaleX * props.designWidth}px`,\r\n height: `${event.scaleY * props.designHeight}px`,\r\n };\r\n }\r\n}\r\n\r\nconst resizeObservable = useFitin();\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n const subscription = resizeObservable.subscribe(\r\n (event: IResizeEvent | undefined) => {\r\n if (event) {\r\n adjustContainer(event, props);\r\n }\r\n },\r\n );\r\n onCleanup(() => subscription.unsubscribe());\r\n }\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n role=\"fitin-container\"\r\n :data-scope=\"affectScope\"\r\n :style=\"[props.style, fitinStyle]\"\r\n :class=\"props.class\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { ref, watchEffect, computed, type CSSProperties } from \"vue\";\r\nimport type { IResizeEvent } from \"@fitin/core\";\r\nimport { provideFitinContainer, useFitin } from \"./hooks\";\r\n\r\ntype FitinContainerProps = {\r\n designWidth: number;\r\n designHeight: number;\r\n transformOriginX?: \"left\" | \"center\" | \"right\";\r\n transformOriginY?: \"top\" | \"center\" | \"bottom\";\r\n style?: CSSProperties;\r\n class?: string;\r\n};\r\n\r\ndefineOptions({\r\n inheritAttrs: false,\r\n});\r\n\r\nconst props = defineProps<FitinContainerProps>();\r\n\r\nconst affectScope = computed(() =>\r\n props.transformOriginX || props.transformOriginY\r\n ? \"affect-descendant\"\r\n : \"affect-self\",\r\n);\r\n\r\nprovideFitinContainer(affectScope.value);\r\n\r\nconst fitinStyle = ref<CSSProperties>({});\r\n\r\nfunction adjustContainer(event: IResizeEvent, props: FitinContainerProps) {\r\n if (props.transformOriginX || props.transformOriginY) {\r\n let transform: string;\r\n let transformOrigin: string;\r\n if (props.transformOriginX && props.transformOriginY) {\r\n const res = event.transformXY(\r\n props.transformOriginX,\r\n props.transformOriginY,\r\n );\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else if (props.transformOriginX) {\r\n const res = event.transformX(props.transformOriginX);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n } else {\r\n const res = event.transformY(props.transformOriginY!);\r\n transform = res.transform;\r\n transformOrigin = res.transformOrigin;\r\n }\r\n fitinStyle.value = {\r\n position: \"absolute\",\r\n width: `${props.designWidth}px`,\r\n height: `${props.designHeight}px`,\r\n transform: transform,\r\n transformOrigin: transformOrigin,\r\n };\r\n } else {\r\n fitinStyle.value = {\r\n width: `${event.scaleX * props.designWidth}px`,\r\n height: `${event.scaleY * props.designHeight}px`,\r\n };\r\n }\r\n}\r\n\r\nconst resizeObservable = useFitin();\r\nwatchEffect((onCleanup) => {\r\n if (resizeObservable) {\r\n const subscription = resizeObservable.subscribe(\r\n (event: IResizeEvent | undefined) => {\r\n if (event) {\r\n adjustContainer(event, props);\r\n }\r\n },\r\n );\r\n onCleanup(() => subscription.unsubscribe());\r\n }\r\n});\r\n</script>\r\n\r\n<template>\r\n <section\r\n role=\"fitin-container\"\r\n :data-scope=\"affectScope\"\r\n :style=\"[props.style, fitinStyle]\"\r\n :class=\"props.class\"\r\n >\r\n <slot />\r\n </section>\r\n</template>\r\n","import type { App, Plugin } from \"vue\";\r\nimport FitinCanvas from \"./components/fitin-canvas.vue\";\r\nimport FitinContainer from \"./components/fitin-container.vue\";\r\nexport { useFitin, useFitinState } from \"./components/hooks\";\r\n\r\nexport { FitinCanvas, FitinContainer };\r\nexport const plugin: Plugin = {\r\n install(app: App) {\r\n app.component(\"FitinCanvas\", FitinCanvas);\r\n app.component(\"FitinContainer\", FitinContainer);\r\n },\r\n};\r\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type Mode } from "@fitin/core";
2
+ /**
3
+ * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).
4
+ * However, these are exactly the types that `FitinOptions` depends on.
5
+ * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).
6
+ */
7
+ type FitinCanvasProps = {
8
+ designWidth: number;
9
+ designHeight: number;
10
+ mode?: Mode;
11
+ minDesignWidth?: number;
12
+ minDesignHeight?: number;
13
+ wait?: number;
14
+ computeScaleXY?: (designWidth: number, designHeight: number, scaleX: number, scaleY: number) => number;
15
+ viewport?: HTMLElement | "parent";
16
+ center?: boolean;
17
+ };
18
+ declare var __VLS_1: {};
19
+ type __VLS_Slots = {} & {
20
+ default?: (props: typeof __VLS_1) => any;
21
+ };
22
+ declare const __VLS_base: import("vue").DefineComponent<FitinCanvasProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FitinCanvasProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
24
+ declare const _default: typeof __VLS_export;
25
+ export default _default;
26
+ type __VLS_WithSlots<T, S> = T & {
27
+ new (): {
28
+ $slots: S;
29
+ };
30
+ };
31
+ //# sourceMappingURL=fitin-canvas.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fitin-canvas.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fitin-canvas.vue"],"names":[],"mappings":"AAoHA,OAAO,EAAS,KAAK,IAAI,EAAqB,MAAM,aAAa,CAAC;AAIlE;;;;GAIG;AACH,KAAK,gBAAgB,GAAG;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,KACX,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAsFF,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAO/C,QAAA,MAAM,UAAU,4SAEd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type CSSProperties } from "vue";
2
+ type FitinContainerProps = {
3
+ designWidth: number;
4
+ designHeight: number;
5
+ transformOriginX?: "left" | "center" | "right";
6
+ transformOriginY?: "top" | "center" | "bottom";
7
+ style?: CSSProperties;
8
+ class?: string;
9
+ };
10
+ declare var __VLS_1: {};
11
+ type __VLS_Slots = {} & {
12
+ default?: (props: typeof __VLS_1) => any;
13
+ };
14
+ declare const __VLS_base: import("vue").DefineComponent<FitinContainerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FitinContainerProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
18
+ type __VLS_WithSlots<T, S> = T & {
19
+ new (): {
20
+ $slots: S;
21
+ };
22
+ };
23
+ //# sourceMappingURL=fitin-container.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fitin-container.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fitin-container.vue"],"names":[],"mappings":"AAAA,OA6FO,EAA8B,KAAK,aAAa,EAAE,MAAM,KAAK,CAAC;AAIrE,KAAK,mBAAmB,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC/C,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC/C,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+FF,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAK/C,QAAA,MAAM,UAAU,kTAEd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { IResizeEvent } from "@fitin/core";
2
+ import type { Observable } from "rxjs";
3
+ export declare function provideFitinContainer(value: "affect-self" | "affect-descendant"): void;
4
+ export declare function useFitin(resizeHandler?: (event: undefined | IResizeEvent) => void): undefined | Observable<undefined | IResizeEvent>;
5
+ export declare function useFitinState(): {
6
+ scaleX: number;
7
+ scaleY: number;
8
+ scaleXY: number;
9
+ effectiveWidth: undefined | number;
10
+ effectiveHeight: undefined | number;
11
+ };
12
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/components/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,GAAG,mBAAmB,QAK3C;AAED,wBAAgB,QAAQ,CACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,KAAK,IAAI,GACxD,SAAS,GAAG,UAAU,CAAC,SAAS,GAAG,YAAY,CAAC,CAiBlD;AAED,wBAAgB,aAAa;YAEjB,MAAM;YACN,MAAM;aACL,MAAM;oBACC,SAAS,GAAG,MAAM;qBACjB,SAAS,GAAG,MAAM;EAmBtC"}
@@ -0,0 +1,6 @@
1
+ import { type InjectionKey } from "vue";
2
+ import type { Observable } from "rxjs";
3
+ import type { IResizeEvent } from "@fitin/core";
4
+ export declare const FitinCanvasKey: InjectionKey<Observable<IResizeEvent | undefined>>;
5
+ export declare const FitinContainerKey: InjectionKey<("affect-self" | "affect-descendant")[]>;
6
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/components/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,KAAK,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,cAAc,EAAE,YAAY,CACvC,UAAU,CAAC,YAAY,GAAG,SAAS,CAAC,CACZ,CAAC;AAE3B,eAAO,MAAM,iBAAiB,EAAE,YAAY,CAC1C,CAAC,aAAa,GAAG,mBAAmB,CAAC,EAAE,CACZ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function logE(message: string): void;
2
+ export declare function logW(message: string): void;
3
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../src/components/logging.ts"],"names":[],"mappings":"AAAA,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AACD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C"}
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from "vue";
2
+ import FitinCanvas from "./components/fitin-canvas.vue";
3
+ import FitinContainer from "./components/fitin-container.vue";
4
+ export { useFitin, useFitinState } from "./components/hooks";
5
+ export { FitinCanvas, FitinContainer };
6
+ export declare const plugin: Plugin;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,MAAM,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,WAAW,MAAM,+BAA+B,CAAC;AACxD,OAAO,cAAc,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;AACvC,eAAO,MAAM,MAAM,EAAE,MAKpB,CAAC"}
package/index.html ADDED
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>vue</title>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <div id="app"></div>
16
+ <script type="module" src="/src/main.ts"></script>
17
+ </body>
18
+ </html>
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@fitin/vue",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/index.umd.js",
7
+ "module": "./dist/index.es.js",
8
+ "types": "./dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.es.js",
12
+ "require": "./dist/index.umd.js",
13
+ "types": "./dist/types/index.d.ts"
14
+ }
15
+ },
16
+ "peerDependencies": {
17
+ "@fitin/core": "0.1.0",
18
+ "rxjs": "^7.8.2",
19
+ "vue": "^3.5.41"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^24.13.3",
23
+ "@vitejs/plugin-vue": "^6.0.8",
24
+ "@vue/tsconfig": "^0.9.1",
25
+ "typescript": "~6.0.2",
26
+ "vite": "^8.2.2",
27
+ "vue-tsc": "^3.3.11"
28
+ },
29
+ "scripts": {
30
+ "dev": "vite",
31
+ "build": "vue-tsc -b --noEmit && vite build && vue-tsc -b",
32
+ "preview": "vite preview"
33
+ }
34
+ }
package/src/App.vue ADDED
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import FitinCanvas from "./components/fitin-canvas.vue";
3
+ import FitinContainer from "./components/fitin-container.vue";
4
+ </script>
5
+
6
+ <template>
7
+ <FitinCanvas :design-width="920" :design-height="680">
8
+ <FitinContainer :design-width="200" :design-height="300">
9
+ <div
10
+ style="
11
+ height: 100%;
12
+ display: flex;
13
+ flex-direction: column;
14
+ background: red;
15
+ "
16
+ ></div>
17
+ </FitinContainer>
18
+ </FitinCanvas>
19
+ </template>
@@ -0,0 +1,104 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ computed,
4
+ inject,
5
+ provide,
6
+ ref,
7
+ watchEffect,
8
+ type CSSProperties,
9
+ } from "vue";
10
+ import { BehaviorSubject } from "rxjs";
11
+ import { Fitin, type Mode, type IResizeEvent } from "@fitin/core";
12
+ import { FitinCanvasKey } from "./keys";
13
+ import { logE } from "./logging";
14
+
15
+ /**
16
+ * Vue SFC does not support TypeScript advanced types(e.g., Omit, Pick and Partial).
17
+ * However, these are exactly the types that `FitinOptions` depends on.
18
+ * Solution: declare the type from scratch (i.e. rewrite it explicitly without relying on utility types).
19
+ */
20
+ type FitinCanvasProps = {
21
+ designWidth: number; // physical design width of container
22
+ designHeight: number; // physical design height of container
23
+ mode?: Mode;
24
+ minDesignWidth?: number; // minimum physical design width of container
25
+ minDesignHeight?: number; // minimum physical design height of container
26
+ wait?: number; // in ms
27
+ computeScaleXY?: (
28
+ designWidth: number,
29
+ designHeight: number,
30
+ scaleX: number,
31
+ scaleY: number,
32
+ ) => number;
33
+ viewport?: HTMLElement | "parent";
34
+ center?: boolean;
35
+ };
36
+
37
+ defineOptions({
38
+ inheritAttrs: false,
39
+ });
40
+ const props = defineProps<FitinCanvasProps>();
41
+
42
+ const canvasRef = ref<HTMLElement>();
43
+
44
+ const canvasStyle = computed<CSSProperties>(() =>
45
+ props.center
46
+ ? {
47
+ position: "absolute",
48
+ top: "50%",
49
+ left: "50%",
50
+ transform: "translate(-50%, -50%)",
51
+ overflow: "hidden"
52
+ }
53
+ : {
54
+ position: "relative",
55
+ overflow: "hidden"
56
+ },
57
+ );
58
+
59
+ let fitin: Fitin;
60
+ let subject = new BehaviorSubject<IResizeEvent | undefined>(undefined);
61
+ provide(FitinCanvasKey, subject.asObservable());
62
+ const resizeObservable = inject(FitinCanvasKey);
63
+
64
+ watchEffect((onCleanup) => {
65
+ if (resizeObservable) {
66
+ logE("Nested fitin-canvas is forbidden");
67
+ return;
68
+ }
69
+
70
+ fitin = new Fitin(props);
71
+
72
+ const handleResize = (event: IResizeEvent) => {
73
+ if (event && canvasRef.value) {
74
+ canvasRef.value.style.width = `${event.effectiveWidth}px`;
75
+ canvasRef.value.style.height = `${event.effectiveHeight}px`;
76
+ }
77
+ subject.next(event);
78
+ };
79
+
80
+ const viewport =
81
+ props.viewport === "parent"
82
+ ? canvasRef.value?.parentElement
83
+ : props.viewport;
84
+ if (viewport) {
85
+ fitin.subscribe(viewport, handleResize);
86
+ } else {
87
+ fitin.subscribe(handleResize);
88
+ }
89
+
90
+ onCleanup(() => {
91
+ fitin.dispose();
92
+ });
93
+ });
94
+ </script>
95
+
96
+ <template>
97
+ <section
98
+ ref="canvasRef"
99
+ role="fitin-canvas"
100
+ :style="canvasStyle"
101
+ >
102
+ <slot />
103
+ </section>
104
+ </template>
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import { ref, watchEffect, computed, type CSSProperties } from "vue";
3
+ import type { IResizeEvent } from "@fitin/core";
4
+ import { provideFitinContainer, useFitin } from "./hooks";
5
+
6
+ type FitinContainerProps = {
7
+ designWidth: number;
8
+ designHeight: number;
9
+ transformOriginX?: "left" | "center" | "right";
10
+ transformOriginY?: "top" | "center" | "bottom";
11
+ style?: CSSProperties;
12
+ class?: string;
13
+ };
14
+
15
+ defineOptions({
16
+ inheritAttrs: false,
17
+ });
18
+
19
+ const props = defineProps<FitinContainerProps>();
20
+
21
+ const affectScope = computed(() =>
22
+ props.transformOriginX || props.transformOriginY
23
+ ? "affect-descendant"
24
+ : "affect-self",
25
+ );
26
+
27
+ provideFitinContainer(affectScope.value);
28
+
29
+ const fitinStyle = ref<CSSProperties>({});
30
+
31
+ function adjustContainer(event: IResizeEvent, props: FitinContainerProps) {
32
+ if (props.transformOriginX || props.transformOriginY) {
33
+ let transform: string;
34
+ let transformOrigin: string;
35
+ if (props.transformOriginX && props.transformOriginY) {
36
+ const res = event.transformXY(
37
+ props.transformOriginX,
38
+ props.transformOriginY,
39
+ );
40
+ transform = res.transform;
41
+ transformOrigin = res.transformOrigin;
42
+ } else if (props.transformOriginX) {
43
+ const res = event.transformX(props.transformOriginX);
44
+ transform = res.transform;
45
+ transformOrigin = res.transformOrigin;
46
+ } else {
47
+ const res = event.transformY(props.transformOriginY!);
48
+ transform = res.transform;
49
+ transformOrigin = res.transformOrigin;
50
+ }
51
+ fitinStyle.value = {
52
+ position: "absolute",
53
+ width: `${props.designWidth}px`,
54
+ height: `${props.designHeight}px`,
55
+ transform: transform,
56
+ transformOrigin: transformOrigin,
57
+ };
58
+ } else {
59
+ fitinStyle.value = {
60
+ width: `${event.scaleX * props.designWidth}px`,
61
+ height: `${event.scaleY * props.designHeight}px`,
62
+ };
63
+ }
64
+ }
65
+
66
+ const resizeObservable = useFitin();
67
+ watchEffect((onCleanup) => {
68
+ if (resizeObservable) {
69
+ const subscription = resizeObservable.subscribe(
70
+ (event: IResizeEvent | undefined) => {
71
+ if (event) {
72
+ adjustContainer(event, props);
73
+ }
74
+ },
75
+ );
76
+ onCleanup(() => subscription.unsubscribe());
77
+ }
78
+ });
79
+ </script>
80
+
81
+ <template>
82
+ <section
83
+ role="fitin-container"
84
+ :data-scope="affectScope"
85
+ :style="[props.style, fitinStyle]"
86
+ :class="props.class"
87
+ >
88
+ <slot />
89
+ </section>
90
+ </template>
@@ -0,0 +1,61 @@
1
+ import { inject, provide, reactive, watchEffect } from "vue";
2
+ import type { IResizeEvent } from "@fitin/core";
3
+ import { FitinCanvasKey, FitinContainerKey } from "./keys";
4
+ import { logE, logW } from "./logging";
5
+ import type { Observable } from "rxjs";
6
+
7
+ export function provideFitinContainer(
8
+ value: "affect-self" | "affect-descendant",
9
+ ) {
10
+ const paths = inject(FitinContainerKey) || [];
11
+ paths.push(value);
12
+ provide(FitinContainerKey, paths);
13
+ }
14
+
15
+ export function useFitin(
16
+ resizeHandler?: (event: undefined | IResizeEvent) => void,
17
+ ): undefined | Observable<undefined | IResizeEvent> {
18
+ const resizeObservable = inject(FitinCanvasKey);
19
+ if (resizeObservable) {
20
+ const paths = inject(FitinContainerKey) || [];
21
+ if (paths.find((path) => path === "affect-descendant")) {
22
+ logW("There's an ancestor fitin-container resizing all descendants");
23
+ }
24
+
25
+ watchEffect((onCleanup) => {
26
+ const subscription = resizeObservable.subscribe(resizeHandler);
27
+ onCleanup(() => subscription.unsubscribe());
28
+ });
29
+ } else {
30
+ logE("A fitin-container must be used within a fitin-canvas context");
31
+ }
32
+
33
+ return resizeObservable;
34
+ }
35
+
36
+ export function useFitinState() {
37
+ const state = reactive<{
38
+ scaleX: number;
39
+ scaleY: number;
40
+ scaleXY: number;
41
+ effectiveWidth: undefined | number;
42
+ effectiveHeight: undefined | number;
43
+ }>({
44
+ scaleX: 1,
45
+ scaleY: 1,
46
+ scaleXY: 1,
47
+ effectiveWidth: undefined,
48
+ effectiveHeight: undefined,
49
+ });
50
+ useFitin((event) => {
51
+ if (event) {
52
+ state.scaleX = event.scaleX;
53
+ state.scaleY = event.scaleY;
54
+ state.scaleXY = event.scaleXY;
55
+ state.effectiveWidth = event.effectiveWidth;
56
+ state.effectiveHeight = event.effectiveHeight;
57
+ }
58
+ });
59
+
60
+ return state;
61
+ }
@@ -0,0 +1,11 @@
1
+ import { type InjectionKey } from "vue";
2
+ import type { Observable } from "rxjs";
3
+ import type { IResizeEvent } from "@fitin/core";
4
+
5
+ export const FitinCanvasKey: InjectionKey<
6
+ Observable<IResizeEvent | undefined>
7
+ > = Symbol("fitin-canvas");
8
+
9
+ export const FitinContainerKey: InjectionKey<
10
+ ("affect-self" | "affect-descendant")[]
11
+ > = Symbol("fitin-container");
@@ -0,0 +1,6 @@
1
+ export function logE(message: string): void {
2
+ console.error("[@fitin/vue]", message);
3
+ }
4
+ export function logW(message: string): void {
5
+ console.warn("[@fitin/vue]", message);
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import FitinCanvas from "./components/fitin-canvas.vue";
3
+ import FitinContainer from "./components/fitin-container.vue";
4
+ export { useFitin, useFitinState } from "./components/hooks";
5
+
6
+ export { FitinCanvas, FitinContainer };
7
+ export const plugin: Plugin = {
8
+ install(app: App) {
9
+ app.component("FitinCanvas", FitinCanvas);
10
+ app.component("FitinContainer", FitinContainer);
11
+ },
12
+ };
package/src/main.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { createApp } from "vue";
2
+ import App from "./App.vue";
3
+
4
+ createApp(App).mount("#app");
@@ -0,0 +1,30 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "types": ["vite/client"],
6
+ "allowArbitraryExtensions": true,
7
+
8
+ /* Linting */
9
+ "noUnusedLocals": true,
10
+ "noUnusedParameters": true,
11
+ "noFallthroughCasesInSwitch": true,
12
+ "erasableSyntaxOnly": true,
13
+
14
+ /* Bundler */
15
+ "noEmit": false,
16
+ "declaration": true,
17
+ "declarationMap": true,
18
+ "emitDeclarationOnly": true,
19
+ "outDir": "dist/types",
20
+ "rootDir": "src"
21
+ },
22
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
23
+ "exclude": [
24
+ "**/*.test.ts",
25
+ "**/*.spec.ts",
26
+ "**/__tests__/**",
27
+ "src/main.ts",
28
+ "src/App.vue"
29
+ ]
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "types": ["node"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "module": "nodenext",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "erasableSyntaxOnly": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["vite.config.ts"]
23
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import vue from "@vitejs/plugin-vue";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ build: {
7
+ sourcemap: true,
8
+ lib: {
9
+ entry: "src/index.ts",
10
+ name: "Fitin",
11
+ fileName: (format) => `index.${format}.js`,
12
+ formats: ["es", "umd"],
13
+ },
14
+ rolldownOptions: {
15
+ external: ["vue", "rxjs", "@fitin/core"],
16
+ output: {
17
+ format: "umd",
18
+ globals: {
19
+ vue: "Vue",
20
+ rxjs: "rxjs",
21
+ "@fitin/core": "FitinCore",
22
+ },
23
+ },
24
+ },
25
+ },
26
+ });