@place-framework/place-block-image 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/constants/index.d.ts +2 -2
  2. package/dist/constants/index.d.ts.map +1 -1
  3. package/dist/constants/index.js +4 -5
  4. package/dist/constants/index.js.map +1 -1
  5. package/dist/generate-components.d.ts +16 -0
  6. package/dist/generate-components.d.ts.map +1 -0
  7. package/dist/generate-components.js +93 -0
  8. package/dist/generate-components.js.map +1 -0
  9. package/dist/react/PlaceBlockImage.cjs +1 -0
  10. package/dist/react/PlaceBlockImage.js +67 -0
  11. package/dist/templates/shared/index.d.ts.map +1 -1
  12. package/dist/templates/shared/index.js +8 -16
  13. package/dist/templates/shared/index.js.map +1 -1
  14. package/dist/templates/shared/vue.d.ts +6 -0
  15. package/dist/templates/shared/vue.d.ts.map +1 -0
  16. package/dist/templates/shared/vue.js +26 -0
  17. package/dist/templates/shared/vue.js.map +1 -0
  18. package/dist/templates/vue.d.ts.map +1 -1
  19. package/dist/templates/vue.js +14 -19
  20. package/dist/templates/vue.js.map +1 -1
  21. package/dist/utils/index.d.ts +61 -7
  22. package/dist/utils/index.d.ts.map +1 -1
  23. package/dist/utils/index.js +112 -27
  24. package/dist/utils/index.js.map +1 -1
  25. package/dist/vue/PlaceBlockImage.cjs +1 -0
  26. package/dist/vue/PlaceBlockImage.js +83 -0
  27. package/dist/webpack-plugin.d.ts +21 -8
  28. package/dist/webpack-plugin.d.ts.map +1 -1
  29. package/dist/webpack-plugin.js +24 -36
  30. package/dist/webpack-plugin.js.map +1 -1
  31. package/package.json +36 -10
  32. package/src/constants/index.ts +5 -6
  33. package/src/react/PlaceBlockImage.tsx +63 -0
  34. package/src/react/index.ts +1 -0
  35. package/src/utils/index.ts +125 -30
  36. package/src/vue/PlaceBlockImage.vue +98 -0
  37. package/src/vue/index.ts +1 -0
  38. package/src/webpack-plugin.ts +47 -49
  39. package/tsconfig.json +8 -3
  40. package/vite.config.mts +27 -0
  41. package/vite.react.config.mts +25 -0
  42. package/vite.vue.config.mts +26 -0
  43. package/src/templates/react-jsx.ts +0 -27
  44. package/src/templates/react-tsx.ts +0 -33
  45. package/src/templates/shared/index.ts +0 -47
  46. package/src/templates/shared/react.ts +0 -51
  47. package/src/templates/vue.ts +0 -98
  48. package/src/templates.ts +0 -29
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <picture :class="[wrapperClassName, attrs.class]">
3
+ <img
4
+ ref="imgRef"
5
+ :src="imageSrc"
6
+ :alt="alt"
7
+ :class="imgClassName"
8
+ v-bind="imgAttrs"
9
+ />
10
+ </picture>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed, ref, onMounted, onUnmounted, watch, useAttrs } from 'vue';
15
+ import { getWrapperClass, getLazyClass, createLazyObserver } from '../utils/index';
16
+ import { IMAGE_PREFIX } from '../constants/index';
17
+
18
+ declare const __PLUGIN_ASSET_PATH__: string;
19
+
20
+ defineOptions({ inheritAttrs: false });
21
+
22
+ /**
23
+ * PlaceBlockImage — prevents layout shift using CSS custom properties.
24
+ * Import: import PlaceBlockImage from '@place-framework/place-block-image/vue'
25
+ *
26
+ * Usage:
27
+ * <PlaceBlockImage src="/assets/logo.png" alt="Logo" />
28
+ * <PlaceBlockImage src="/assets/hero.jpg" alt="Hero" :lazy="true" />
29
+ */
30
+
31
+ const attrs = useAttrs();
32
+
33
+ const props = defineProps({
34
+ src: { type: String, required: true },
35
+ alt: { type: String, required: true },
36
+ imagePrefix: { type: String, default: () => IMAGE_PREFIX },
37
+ lazy: { type: Boolean, default: false }
38
+ });
39
+
40
+ // All attrs except class go to the <img>
41
+ const imgAttrs = computed(() => {
42
+ const { class: _, ...rest } = attrs;
43
+ return rest;
44
+ });
45
+
46
+ const imgRef = ref(null);
47
+ const imageSrc = ref(props.lazy ? '' : props.src);
48
+ const isLoaded = ref(!props.lazy);
49
+ let observer: IntersectionObserver | null = null;
50
+
51
+ const assetPath: string = typeof __PLUGIN_ASSET_PATH__ !== 'undefined' ? __PLUGIN_ASSET_PATH__ : '';
52
+
53
+ const wrapperClassName = computed(() =>
54
+ getWrapperClass(props.src, props.imagePrefix, assetPath)
55
+ );
56
+
57
+ const imgClassName = computed(() => getLazyClass(props.lazy, isLoaded.value));
58
+
59
+ const setupLazyLoading = () => {
60
+ if (!props.lazy || isLoaded.value || !imgRef.value) return;
61
+
62
+ observer = createLazyObserver(imgRef.value, () => {
63
+ imageSrc.value = props.src;
64
+ isLoaded.value = true;
65
+ });
66
+ };
67
+
68
+ const cleanupObserver = () => {
69
+ if (observer) {
70
+ observer.disconnect();
71
+ observer = null;
72
+ }
73
+ };
74
+
75
+ onMounted(() => {
76
+ setupLazyLoading();
77
+ });
78
+
79
+ onUnmounted(() => {
80
+ cleanupObserver();
81
+ });
82
+
83
+ watch(() => props.src, (newSrc) => {
84
+ if (!props.lazy) {
85
+ imageSrc.value = newSrc;
86
+ } else if (!isLoaded.value) {
87
+ cleanupObserver();
88
+ setupLazyLoading();
89
+ }
90
+ });
91
+
92
+ watch(isLoaded, (loaded) => {
93
+ if (loaded) {
94
+ imageSrc.value = props.src;
95
+ cleanupObserver();
96
+ }
97
+ });
98
+ </script>
@@ -0,0 +1 @@
1
+ export { default as PlaceBlockImage } from './PlaceBlockImage.vue';
@@ -2,17 +2,33 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { glob } from 'glob';
4
4
  import sizeOf from 'image-size';
5
- import { Compiler } from 'webpack';
6
- import { getTemplate } from './templates';
5
+ import { Compiler, DefinePlugin } from 'webpack';
6
+ import { generateImageClassName } from './utils';
7
7
 
8
8
  export interface PlaceBlockImagePluginOptions {
9
9
  imagePrefix?: string;
10
10
  imageDir: string;
11
11
  scssPath: string;
12
- // Component generation options
13
- componentPath?: string;
14
- componentType?: 'tsx' | 'jsx' | 'vue';
15
- generateComponent?: boolean;
12
+ /**
13
+ * The URL path at which images are served (e.g. '/images').
14
+ * Used by the webpack plugin to inject a __PLACE_ASSET_PATH__ constant
15
+ * that the React/Vue components use to strip the prefix from src before
16
+ * generating the CSS class name — ensuring it matches what the plugin
17
+ * generates from the file path relative to imageDir.
18
+ *
19
+ * Defaults to the basename of imageDir prefixed with '/' (e.g. '/images').
20
+ */
21
+ assetPath?: string;
22
+ /**
23
+ * Output path prefix prepended to the relative image path when generating
24
+ * CSS class names — must match the prefix used in assetModuleFilename so
25
+ * the class name the plugin writes into the SCSS matches what the component
26
+ * derives from the served URL at runtime.
27
+ *
28
+ * e.g. if assetModuleFilename outputs "images/story/foo.png" (served as
29
+ * "/images/story/foo.png"), set outputPathPrefix: 'images'
30
+ */
31
+ outputPathPrefix?: string;
16
32
  }
17
33
 
18
34
  export interface ImageDimensions {
@@ -30,25 +46,22 @@ export class PlaceBlockImagePlugin {
30
46
  constructor(options: PlaceBlockImagePluginOptions) {
31
47
  this.options = {
32
48
  imagePrefix: 'image-',
33
- generateComponent: true,
34
- componentType: 'tsx',
49
+ outputPathPrefix: '',
50
+ assetPath: `/${path.basename(options.imageDir)}`,
35
51
  ...options
36
52
  };
37
53
  }
38
54
 
39
55
  /**
40
- * Generate CSS class name from filename
56
+ * Generate CSS class name from filename - uses shared logic
41
57
  */
42
58
  private generateClassName(filename: string): string {
43
- const name = path.basename(filename, path.extname(filename));
44
- // Convert to kebab-case and remove special characters
45
- const cleanName = name
46
- .toLowerCase()
47
- .replace(/[^a-z0-9-]/g, '-')
48
- .replace(/-+/g, '-')
49
- .replace(/^-|-$/g, '');
50
-
51
- return `${this.options.imagePrefix}${cleanName}`;
59
+ // When outputPathPrefix is set the asset output is flat (just basename),
60
+ // so use only the basename to mirror the served URL: /assets/accent-orchid.png
61
+ const name = this.options.outputPathPrefix
62
+ ? path.basename(filename)
63
+ : filename;
64
+ return generateImageClassName(name, this.options.imagePrefix, this.options.outputPathPrefix);
52
65
  }
53
66
 
54
67
  /**
@@ -137,35 +150,6 @@ export class PlaceBlockImagePlugin {
137
150
  fs.writeFileSync(this.options.scssPath, scssContent);
138
151
  }
139
152
 
140
- /**
141
- * Generate and write component file
142
- */
143
- private async writeComponentFile(): Promise<void> {
144
- if (!this.options.generateComponent || !this.options.componentPath || !this.options.componentType) {
145
- return;
146
- }
147
-
148
- const componentFileName = `PlaceBlockImage.${this.options.componentType}`;
149
- const componentFilePath = path.resolve(this.options.componentPath, componentFileName);
150
-
151
- // Check if component already exists
152
- if (fs.existsSync(componentFilePath)) {
153
- console.log(`📦 Component already exists, skipping: ${componentFilePath}`);
154
- return;
155
- }
156
-
157
- const componentContent = getTemplate(this.options.componentType, this.options.imagePrefix);
158
- const outputDir = path.dirname(componentFilePath);
159
-
160
- // Ensure output directory exists
161
- if (!fs.existsSync(outputDir)) {
162
- fs.mkdirSync(outputDir, { recursive: true });
163
- }
164
-
165
- fs.writeFileSync(componentFilePath, componentContent);
166
- console.log(`📦 Generated component: ${componentFilePath}`);
167
- }
168
-
169
153
  /**
170
154
  * Check if images have changed since last generation
171
155
  */
@@ -195,6 +179,21 @@ export class PlaceBlockImagePlugin {
195
179
  }
196
180
 
197
181
  apply(compiler: Compiler): void {
182
+ // Inject @place-block-image alias so the Vue/React source components can
183
+ // resolve their shared utils/constants imports without requiring the
184
+ // consuming app to configure anything manually.
185
+ const pkgSrc = path.resolve(__dirname, '../src');
186
+ compiler.options.resolve.alias = {
187
+ ...((compiler.options.resolve.alias as Record<string, string>) || {}),
188
+ '@place-block-image': pkgSrc,
189
+ };
190
+
191
+ // Inject __PLUGIN_ASSET_PATH__ so the React/Vue components can strip the
192
+ // served URL prefix from src before generating the CSS class name.
193
+ new DefinePlugin({
194
+ __PLUGIN_ASSET_PATH__: JSON.stringify(this.options.assetPath),
195
+ }).apply(compiler);
196
+
198
197
  // Use environment hook to run only once at startup
199
198
  compiler.hooks.environment.tap('PlaceBlockImagePlugin', () => {
200
199
  this.generateImageStyles();
@@ -247,8 +246,7 @@ export class PlaceBlockImagePlugin {
247
246
  }
248
247
 
249
248
  await this.writeScssFile(images);
250
- await this.writeComponentFile();
251
-
249
+
252
250
  console.log(`✅ Generated CSS custom properties for ${images.length} images`);
253
251
  console.log(`📝 Output: ${this.options.scssPath}`);
254
252
 
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
4
  "module": "commonjs",
5
- "lib": ["ES2020"],
5
+ "lib": ["ES2020", "DOM"],
6
6
  "outDir": "./dist",
7
7
  "rootDir": "./src",
8
8
  "strict": true,
@@ -13,8 +13,13 @@
13
13
  "declarationMap": true,
14
14
  "sourceMap": true,
15
15
  "moduleResolution": "node",
16
- "resolveJsonModule": true
16
+ "resolveJsonModule": true,
17
+ "jsx": "react",
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@place-block-image/*": ["./src/*"]
21
+ }
17
22
  },
18
23
  "include": ["src/**/*"],
19
- "exclude": ["node_modules", "dist"]
24
+ "exclude": ["node_modules", "dist", "src/vue"]
20
25
  }
@@ -0,0 +1,27 @@
1
+ import { defineConfig, build, Plugin } from 'vite';
2
+ import { vueConfig } from './vite.vue.config.mts';
3
+ import { reactConfig } from './vite.react.config.mts';
4
+
5
+ // Runs the React build once after the Vue build completes.
6
+ // The instance-level flag prevents closeBundle from re-firing
7
+ // when the spawned React build itself reaches closeBundle.
8
+ function buildReact(): Plugin {
9
+ let done = false;
10
+ return {
11
+ name: 'build-react',
12
+ apply: 'build',
13
+ async closeBundle() {
14
+ if (done) return;
15
+ done = true;
16
+ await build({ configFile: false, ...reactConfig });
17
+ },
18
+ };
19
+ }
20
+
21
+ // Builds:
22
+ // src/vue/PlaceBlockImage.vue → dist/vue/PlaceBlockImage.{js,cjs} (via vite.vue.config.mts)
23
+ // src/react/PlaceBlockImage.tsx → dist/react/PlaceBlockImage.{js,cjs} (via vite.react.config.mts)
24
+ export default defineConfig({
25
+ ...vueConfig,
26
+ plugins: [...(vueConfig.plugins as Plugin[]), buildReact()],
27
+ });
@@ -0,0 +1,25 @@
1
+ import { resolve } from 'path';
2
+ import type { UserConfig } from 'vite';
3
+
4
+
5
+ // Builds src/react/PlaceBlockImage.tsx → dist/react/PlaceBlockImage.{js,cjs}
6
+ // React and its runtime are kept external — consumers supply their own.
7
+ export const reactConfig: UserConfig = {
8
+ build: {
9
+ outDir: 'dist/react',
10
+ emptyOutDir: true,
11
+ lib: {
12
+ entry: resolve(__dirname, 'src/react/PlaceBlockImage.tsx'),
13
+ name: 'PlaceBlockImage',
14
+ fileName: (format: string, name: string) => (format === 'es' ? `${name}.js` : `${name}.cjs`),
15
+ formats: ['es', 'cjs'],
16
+ },
17
+ rollupOptions: {
18
+ external: ['react', 'react/jsx-runtime'],
19
+ output: {
20
+ globals: { react: 'React' },
21
+ exports: 'named',
22
+ },
23
+ },
24
+ },
25
+ };
@@ -0,0 +1,26 @@
1
+ import vue from '@vitejs/plugin-vue';
2
+ import { resolve } from 'path';
3
+ import type { UserConfig } from 'vite';
4
+
5
+
6
+ // Builds src/vue/PlaceBlockImage.vue → dist/vue/PlaceBlockImage.{js,cjs}
7
+ // Vue and its runtime are kept external — consumers supply their own.
8
+ export const vueConfig: UserConfig = {
9
+ plugins: [vue()],
10
+ build: {
11
+ outDir: 'dist/vue',
12
+ emptyOutDir: true,
13
+ lib: {
14
+ entry: resolve(__dirname, 'src/vue/PlaceBlockImage.vue'),
15
+ name: 'PlaceBlockImage',
16
+ fileName: (format: string, name: string) => (format === 'es' ? `${name}.js` : `${name}.cjs`),
17
+ formats: ['es', 'cjs'],
18
+ },
19
+ rollupOptions: {
20
+ external: ['vue'],
21
+ output: {
22
+ globals: { vue: 'Vue' },
23
+ },
24
+ },
25
+ },
26
+ };
@@ -1,27 +0,0 @@
1
- import { getSharedReactTemplate } from './shared/react';
2
-
3
- export function getReactJsxTemplate(imagePrefix: string): string {
4
- const shared = getSharedReactTemplate(imagePrefix);
5
-
6
- return `${shared.imports}
7
-
8
- ${shared.comment}
9
- export const PlaceBlockImage = ({
10
- src,
11
- alt,
12
- lazy = false,
13
- className = '',
14
- ...props
15
- }) => {
16
- ${shared.hooks}
17
-
18
- ${shared.getImageClassName}
19
-
20
- ${shared.classNames}
21
-
22
- ${shared.jsx}
23
- };
24
-
25
- ${shared.export}
26
- `;
27
- }
@@ -1,33 +0,0 @@
1
- import { getSharedReactTemplate } from './shared/react';
2
-
3
- export function getReactTsxTemplate(imagePrefix: string): string {
4
- const shared = getSharedReactTemplate(imagePrefix);
5
-
6
- return `${shared.imports}
7
-
8
- interface PlaceBlockImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
9
- src: string;
10
- alt: string;
11
- lazy?: boolean;
12
- }
13
-
14
- ${shared.comment}
15
- export const PlaceBlockImage: React.FC<PlaceBlockImageProps> = ({
16
- src,
17
- alt,
18
- lazy = false,
19
- className = '',
20
- ...props
21
- }) => {
22
- ${shared.hooks}
23
-
24
- ${shared.getImageClassName}
25
-
26
- ${shared.classNames}
27
-
28
- ${shared.jsx}
29
- };
30
-
31
- ${shared.export}
32
- `;
33
- }
@@ -1,47 +0,0 @@
1
- import { CLASS_NAMES } from '../../constants';
2
-
3
- export const getSharedTemplate = (imagePrefix: string) => ({
4
- // Common comment block
5
- comment: `/**
6
- * PlaceBlockImage component that prevents layout shift using CSS custom properties
7
- * Generated by place-block-image webpack plugin
8
- *
9
- * Usage:
10
- * <PlaceBlockImage src="/images/logo.svg" alt="Logo" />
11
- * <PlaceBlockImage src="/images/hero.jpg" alt="Hero" lazy={true} />
12
- *
13
- * This will automatically apply:
14
- * - .${imagePrefix}wrapper class on picture (for dimensions)
15
- * - .${imagePrefix}logo class on picture (specific dimensions via CSS custom properties)
16
- * - ${CLASS_NAMES.LAZY}/${CLASS_NAMES.LAZY}.${CLASS_NAMES.LOADED} classes for lazy loading states
17
- */`,
18
-
19
- // Common filename extraction logic (as string for interpolation)
20
- getImageClassNameTemplate: `// Extract filename from src to generate class name
21
- const getImageClassName = (imageSrc: string): string => {
22
- // Remove /images/ prefix and file extension, convert to kebab-case
23
- const filename = imageSrc
24
- .replace(/^.*\\/images\\//, '') // Remove path up to /images/
25
- .replace(/\\.[^/.]+$/, '') // Remove file extension
26
- .toLowerCase()
27
- .replace(/[^a-z0-9-]/g, '-') // Convert special chars to hyphens
28
- .replace(/-+/g, '-') // Remove duplicate hyphens
29
- .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
30
-
31
- return \`${imagePrefix}\${filename}\`;
32
- };`,
33
-
34
- // Common intersection observer logic
35
- intersectionObserverTemplate: `const observer = new IntersectionObserver(
36
- (entries) => {
37
- entries.forEach((entry) => {
38
- if (entry.isIntersecting) {
39
- setImageSrc(src);
40
- setIsLoaded(true);
41
- observer.unobserve(entry.target);
42
- }
43
- });
44
- },
45
- { threshold: 0.1 }
46
- );`
47
- });
@@ -1,51 +0,0 @@
1
- import { getSharedTemplate } from './index';
2
- import { CLASS_NAMES } from '../../constants';
3
-
4
- export const getSharedReactTemplate = (imagePrefix: string) => {
5
- const shared = getSharedTemplate(imagePrefix);
6
-
7
- return {
8
- imports: `import React, { useRef, useEffect, useState } from 'react';`,
9
-
10
- comment: shared.comment,
11
-
12
- hooks: ` const imgRef = useRef(null);
13
- const [imageSrc, setImageSrc] = useState(lazy ? '' : src);
14
- const [isLoaded, setIsLoaded] = useState(!lazy);
15
-
16
- useEffect(() => {
17
- if (!lazy || isLoaded) return;
18
-
19
- ${shared.intersectionObserverTemplate}
20
-
21
- if (imgRef.current) {
22
- observer.observe(imgRef.current);
23
- }
24
-
25
- return () => observer.disconnect();
26
- }, [src, lazy, isLoaded]);`,
27
-
28
- getImageClassName: shared.getImageClassNameTemplate,
29
-
30
- classNames: ` const imageClassName = getImageClassName(src);
31
- const wrapperClassName = \`${imagePrefix}wrapper \${imageClassName} \${className || ''}\`.trim();
32
-
33
- // Build img className with lazy states
34
- const lazyClass = lazy ? (isLoaded ? '${CLASS_NAMES.LAZY} ${CLASS_NAMES.LOADED}' : '${CLASS_NAMES.LAZY}') : '';
35
- const imgClassName = \`${CLASS_NAMES.IMAGE_BLOCK} \${lazyClass}\`.trim();`,
36
-
37
- jsx: ` return (
38
- <picture className={wrapperClassName}>
39
- <img
40
- ref={imgRef}
41
- src={imageSrc}
42
- alt={alt}
43
- className={imgClassName}
44
- {...props}
45
- />
46
- </picture>
47
- );`,
48
-
49
- export: `export default PlaceBlockImage;`
50
- };
51
- };
@@ -1,98 +0,0 @@
1
- import { getSharedTemplate } from './shared/';
2
- import { CLASS_NAMES } from '../constants';
3
-
4
- export function getVueTemplate(imagePrefix: string): string {
5
- const shared = getSharedTemplate(imagePrefix);
6
-
7
- return `<template>
8
- <picture :class="wrapperClassName">
9
- <img
10
- ref="imgRef"
11
- :src="imageSrc"
12
- :alt="alt"
13
- :class="imgClassName"
14
- v-bind="$attrs"
15
- />
16
- </picture>
17
- </template>
18
-
19
- <script setup lang="ts">
20
- import { computed, ref, onMounted, onUnmounted, watch } from 'vue';
21
-
22
- interface Props {
23
- src: string;
24
- alt: string;
25
- lazy?: boolean;
26
- class?: string;
27
- }
28
-
29
- const props = withDefaults(defineProps<Props>(), {
30
- lazy: false,
31
- class: ''
32
- });
33
-
34
- ${shared.comment}
35
-
36
- const imgRef = ref<HTMLImageElement | null>(null);
37
- const imageSrc = ref(props.lazy ? '' : props.src);
38
- const isLoaded = ref(!props.lazy);
39
- let observer: IntersectionObserver | null = null;
40
-
41
- ${shared.getImageClassNameTemplate}
42
-
43
- const imageClassName = computed(() => getImageClassName(props.src));
44
- const wrapperClassName = computed(() =>
45
- \`${imagePrefix}wrapper \${imageClassName.value}\`
46
- );
47
-
48
- const lazyClass = computed(() =>
49
- props.lazy ? (isLoaded.value ? '${CLASS_NAMES.LAZY} ${CLASS_NAMES.LOADED}' : '${CLASS_NAMES.LAZY}') : ''
50
- );
51
-
52
- const imgClassName = computed(() =>
53
- \`\${props.class} \${lazyClass.value}\`.trim()
54
- );
55
-
56
- const setupLazyLoading = () => {
57
- if (!props.lazy || isLoaded.value) return;
58
-
59
- ${shared.intersectionObserverTemplate}
60
-
61
- if (imgRef.value) {
62
- observer.observe(imgRef.value);
63
- }
64
- };
65
-
66
- const cleanupObserver = () => {
67
- if (observer) {
68
- observer.disconnect();
69
- observer = null;
70
- }
71
- };
72
-
73
- onMounted(() => {
74
- setupLazyLoading();
75
- });
76
-
77
- onUnmounted(() => {
78
- cleanupObserver();
79
- });
80
-
81
- watch(() => props.src, (newSrc) => {
82
- if (!props.lazy) {
83
- imageSrc.value = newSrc;
84
- } else if (!isLoaded.value) {
85
- cleanupObserver();
86
- setupLazyLoading();
87
- }
88
- });
89
-
90
- watch(isLoaded, (loaded) => {
91
- if (loaded) {
92
- imageSrc.value = props.src;
93
- cleanupObserver();
94
- }
95
- });
96
- </script>
97
- `;
98
- }
package/src/templates.ts DELETED
@@ -1,29 +0,0 @@
1
- // Main template exports
2
- import { getReactTsxTemplate as getReactTsx } from './templates/react-tsx';
3
- import { getReactJsxTemplate as getReactJsx } from './templates/react-jsx';
4
- import { getVueTemplate as getVue } from './templates/vue';
5
-
6
- export function getReactTsxTemplate(imagePrefix: string): string {
7
- return getReactTsx(imagePrefix);
8
- }
9
-
10
- export function getReactJsxTemplate(imagePrefix: string): string {
11
- return getReactJsx(imagePrefix);
12
- }
13
-
14
- export function getVueTemplate(imagePrefix: string): string {
15
- return getVue(imagePrefix);
16
- }
17
-
18
- export function getTemplate(type: 'tsx' | 'jsx' | 'vue', imagePrefix: string): string {
19
- switch (type) {
20
- case 'tsx':
21
- return getReactTsxTemplate(imagePrefix);
22
- case 'jsx':
23
- return getReactJsxTemplate(imagePrefix);
24
- case 'vue':
25
- return getVueTemplate(imagePrefix);
26
- default:
27
- throw new Error(`Unsupported component type: ${type}`);
28
- }
29
- }