@cyberalien/svg-utils 1.1.4 → 1.2.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.
Files changed (123) hide show
  1. package/lib/components/export/merge.js +3 -1
  2. package/lib/components/helpers/content/stringify.d.ts +2 -2
  3. package/lib/components/helpers/content/stringify.js +4 -10
  4. package/lib/components/helpers/css/generate.d.ts +2 -1
  5. package/lib/components/helpers/css/generate.js +43 -46
  6. package/lib/components/helpers/filenames/css.d.ts +1 -1
  7. package/lib/components/helpers/filenames/css.js +2 -3
  8. package/lib/components/helpers/functions/custom.d.ts +14 -0
  9. package/lib/components/helpers/functions/custom.js +17 -0
  10. package/lib/components/helpers/functions/fallback.d.ts +8 -0
  11. package/lib/components/helpers/functions/fallback.js +53 -0
  12. package/lib/components/helpers/functions/innerhtml.d.ts +8 -0
  13. package/lib/components/helpers/functions/innerhtml.js +35 -0
  14. package/lib/components/helpers/functions/size.js +1 -1
  15. package/lib/components/helpers/imports/create.js +1 -2
  16. package/lib/components/helpers/imports/stringify.js +0 -2
  17. package/lib/components/helpers/imports/types.d.ts +0 -1
  18. package/lib/components/jsx.js +79 -8
  19. package/lib/components/prepare/iconify.d.ts +1 -1
  20. package/lib/components/prepare/iconify.js +6 -4
  21. package/lib/components/prepare/states.d.ts +8 -0
  22. package/lib/components/prepare/states.js +73 -0
  23. package/lib/components/raw.js +10 -7
  24. package/lib/components/svelte.js +73 -14
  25. package/lib/components/types/component.d.ts +2 -1
  26. package/lib/components/types/css.d.ts +1 -2
  27. package/lib/components/types/data.d.ts +0 -3
  28. package/lib/components/types/options.d.ts +9 -2
  29. package/lib/components/types/source.d.ts +21 -6
  30. package/lib/components/vue-func.js +71 -12
  31. package/lib/components/vue.js +70 -13
  32. package/lib/css/find/animations.d.ts +10 -0
  33. package/lib/css/find/animations.js +35 -0
  34. package/lib/css/find/classname.d.ts +5 -0
  35. package/lib/css/find/classname.js +14 -0
  36. package/lib/css/find/prop.d.ts +5 -0
  37. package/lib/css/find/prop.js +12 -0
  38. package/lib/css/minify.d.ts +2 -0
  39. package/lib/css/minify.js +5 -0
  40. package/lib/css/rules.d.ts +10 -0
  41. package/lib/css/rules.js +26 -0
  42. package/lib/css/stringify.d.ts +8 -2
  43. package/lib/css/stringify.js +16 -8
  44. package/lib/css/stylesheet.d.ts +0 -2
  45. package/lib/css/stylesheet.js +3 -3
  46. package/lib/css/types.d.ts +1 -1
  47. package/lib/helpers/data/compact.d.ts +20 -0
  48. package/lib/helpers/data/compact.js +38 -0
  49. package/lib/helpers/data/expand.d.ts +5 -0
  50. package/lib/helpers/data/expand.js +9 -0
  51. package/lib/helpers/reduce-motion.d.ts +2 -0
  52. package/lib/helpers/reduce-motion.js +3 -0
  53. package/lib/index.d.ts +15 -2
  54. package/lib/index.js +11 -2
  55. package/lib/svg-css/icon/css.d.ts +25 -0
  56. package/lib/svg-css/icon/css.js +57 -0
  57. package/lib/svg-css/icon/types.d.ts +45 -0
  58. package/lib/svg-css/icon/types.js +1 -0
  59. package/lib/svg-css/icon-set/add.d.ts +7 -0
  60. package/lib/svg-css/icon-set/add.js +52 -0
  61. package/lib/svg-css/icon-set/create.d.ts +6 -0
  62. package/lib/svg-css/icon-set/create.js +8 -0
  63. package/lib/svg-css/icon-set/get.d.ts +4 -0
  64. package/lib/svg-css/icon-set/get.js +92 -0
  65. package/lib/svg-css/icon-set/minify/expand.d.ts +10 -0
  66. package/lib/svg-css/icon-set/minify/expand.js +35 -0
  67. package/lib/svg-css/icon-set/minify/keys.d.ts +5 -0
  68. package/lib/svg-css/icon-set/minify/keys.js +9 -0
  69. package/lib/svg-css/icon-set/minify/minify.d.ts +19 -0
  70. package/lib/svg-css/icon-set/minify/minify.js +74 -0
  71. package/lib/svg-css/icon-set/types.d.ts +43 -0
  72. package/lib/svg-css/icon-set/types.js +1 -0
  73. package/lib/svg-css/states/cleanup-values.d.ts +6 -0
  74. package/lib/svg-css/states/cleanup-values.js +15 -0
  75. package/lib/svg-css/states/fallback/parse.d.ts +7 -0
  76. package/lib/svg-css/states/fallback/parse.js +46 -0
  77. package/lib/svg-css/states/fallback/stringify.d.ts +6 -0
  78. package/lib/svg-css/states/fallback/stringify.js +9 -0
  79. package/lib/svg-css/states/fallback/test.d.ts +9 -0
  80. package/lib/svg-css/states/fallback/test.js +21 -0
  81. package/lib/svg-css/states/fallback/types.d.ts +20 -0
  82. package/lib/svg-css/states/fallback/types.js +1 -0
  83. package/lib/svg-css/states/focus.d.ts +10 -0
  84. package/lib/svg-css/states/focus.js +14 -0
  85. package/lib/svg-css/states/generator.d.ts +19 -0
  86. package/lib/svg-css/states/generator.js +31 -0
  87. package/lib/svg-css/states/key.d.ts +6 -0
  88. package/lib/svg-css/states/key.js +22 -0
  89. package/lib/svg-css/states/object.d.ts +6 -0
  90. package/lib/svg-css/states/object.js +13 -0
  91. package/lib/svg-css/states/selector/helpers/iterate.d.ts +10 -0
  92. package/lib/svg-css/states/selector/helpers/iterate.js +71 -0
  93. package/lib/svg-css/states/selector/merge.d.ts +6 -0
  94. package/lib/svg-css/states/selector/merge.js +29 -0
  95. package/lib/svg-css/states/selector/parse.d.ts +13 -0
  96. package/lib/svg-css/states/selector/parse.js +74 -0
  97. package/lib/svg-css/states/selector/part/merge.d.ts +10 -0
  98. package/lib/svg-css/states/selector/part/merge.js +37 -0
  99. package/lib/svg-css/states/selector/part/split.d.ts +13 -0
  100. package/lib/svg-css/states/selector/part/split.js +60 -0
  101. package/lib/svg-css/states/selector/part/stringify.d.ts +9 -0
  102. package/lib/svg-css/states/selector/part/stringify.js +32 -0
  103. package/lib/svg-css/states/selector/split.d.ts +6 -0
  104. package/lib/svg-css/states/selector/split.js +29 -0
  105. package/lib/svg-css/states/selector/stringify.d.ts +8 -0
  106. package/lib/svg-css/states/selector/stringify.js +143 -0
  107. package/lib/svg-css/states/selector/sub/merge.d.ts +6 -0
  108. package/lib/svg-css/states/selector/sub/merge.js +36 -0
  109. package/lib/svg-css/states/selector/sub/split.d.ts +10 -0
  110. package/lib/svg-css/states/selector/sub/split.js +61 -0
  111. package/lib/svg-css/states/selector/sub/stringify.d.ts +6 -0
  112. package/lib/svg-css/states/selector/sub/stringify.js +17 -0
  113. package/lib/svg-css/states/selector/types.d.ts +37 -0
  114. package/lib/svg-css/states/selector/types.js +1 -0
  115. package/lib/svg-css/states/types.d.ts +9 -0
  116. package/lib/svg-css/states/types.js +1 -0
  117. package/lib/svg-css/states/validate.d.ts +6 -0
  118. package/lib/svg-css/states/validate.js +55 -0
  119. package/lib/svg-css/states/value.d.ts +10 -0
  120. package/lib/svg-css/states/value.js +15 -0
  121. package/package.json +8 -8
  122. package/lib/components/helpers/css/name.d.ts +0 -7
  123. package/lib/components/helpers/css/name.js +0 -12
@@ -1,3 +1,4 @@
1
+ import { stringifyStylesheet } from "../css/stylesheet.js";
1
2
  import { getComponentSizeValues } from "./helpers/content/size.js";
2
3
  import { stringifyFactoryIconContent } from "./helpers/content/stringify.js";
3
4
  import { stringifyIconViewBox } from "../svg/viewbox/value.js";
@@ -11,21 +12,23 @@ import { getGeneratedComponentTypesFilename } from "./helpers/filenames/types.js
11
12
  * Create raw component code
12
13
  */
13
14
  function createRawComponent(data, options) {
15
+ const icon = data.icon;
16
+ const viewBox = icon.viewBox;
14
17
  const assets = [];
15
18
  const imports = createFactoryImports();
16
19
  const codeLines = [];
17
- const style = generateCSSFilesForComponent(data.icon, imports, assets, options);
20
+ const style = generateCSSFilesForComponent(icon, imports, assets, options);
18
21
  const isEmbeddedCSS = options.cssMode === "embed";
19
22
  const props = {
20
23
  xmlns: "http://www.w3.org/2000/svg",
21
- ...getComponentSizeValues(options, data.viewBox),
22
- viewBox: stringifyIconViewBox(data.viewBox)
24
+ ...getComponentSizeValues(options, viewBox),
25
+ viewBox: stringifyIconViewBox(viewBox)
23
26
  };
24
- const icon = {
25
- ...data.icon,
26
- content: `<svg ${stringifyFactoryProps(props, factoryPropTemplate)}>${isEmbeddedCSS && style ? `<style>${style}</style>` : ""}${data.icon.content}</svg>`
27
+ const iconContent = {
28
+ ...icon,
29
+ content: `<svg ${stringifyFactoryProps(props, factoryPropTemplate)}>${isEmbeddedCSS && style ? `<style>${stringifyStylesheet(style)}</style>` : ""}${icon.content}</svg>`
27
30
  };
28
- codeLines.push(`const icon = ${stringifyFactoryIconContent(icon, options)};\n`);
31
+ codeLines.push(`const icon = ${stringifyFactoryIconContent(iconContent)};\n`);
29
32
  codeLines.push("export default icon;\n");
30
33
  const importsCode = stringifyFactoryImports(imports);
31
34
  if (importsCode) codeLines.unshift(importsCode);
@@ -1,3 +1,4 @@
1
+ import { stringifyStylesheet } from "../css/stylesheet.js";
1
2
  import { getComponentSizeValues } from "./helpers/content/size.js";
2
3
  import { stringifyFactoryIconContent } from "./helpers/content/stringify.js";
3
4
  import { stringifyIconViewBox } from "../svg/viewbox/value.js";
@@ -10,34 +11,90 @@ import { makeSquareViewBox } from "../svg/viewbox/square.js";
10
11
  import { getUsedFactoryProps, stringifyFactoryPropTypes } from "./helpers/props/ts.js";
11
12
  import { minifyViewBox } from "../svg/viewbox/minify.js";
12
13
  import { getViewBoxRatio } from "./helpers/content/ratio.js";
14
+ import { addCustomFunctionAsset } from "./helpers/functions/custom.js";
15
+ import { addFallbackFunctionAsset } from "./helpers/functions/fallback.js";
13
16
  import { addSvelteComponentTypes } from "./helpers/ts/svelte.js";
14
17
 
15
18
  /**
16
19
  * Create Svelte component code
17
20
  */
18
21
  function createSvelteComponent(data, options) {
22
+ const icon = data.icon;
23
+ const viewBox = icon.viewBox;
24
+ const fallback = icon.defaultFallback;
25
+ const statefulData = icon.statefulData;
19
26
  const useTS = options.ts ?? false;
20
27
  const assets = [];
21
28
  const imports = createFactoryImports();
22
29
  const dependencies = /* @__PURE__ */ new Set();
23
- const hasFallback = !!data.fallback;
24
- if (hasFallback) {
30
+ if (fallback) {
25
31
  imports.default["@iconify/css-svelte"] = "Icon";
26
32
  dependencies.add("@iconify/css-svelte");
27
33
  }
28
- const styleContent = generateCSSFilesForComponent(data.icon, imports, assets, {
34
+ const styleContent = generateCSSFilesForComponent(icon, imports, assets, {
29
35
  ...options,
30
36
  componentType: "svelte"
31
37
  });
32
38
  let hasFixedSize = !!options.width && !!options.height;
33
- const viewBox = data.viewBox;
34
39
  const hasComputedViewbox = options.square && !hasFixedSize && viewBox.width !== viewBox.height;
35
- const isStringViewBox = !hasFallback;
40
+ const isStringViewBox = !fallback;
36
41
  const hasComputedRatio = hasComputedViewbox && isStringViewBox;
37
42
  if (!hasComputedViewbox && (options.width || options.height)) hasFixedSize = true;
38
43
  const componentCode = [];
39
44
  const props = {};
40
- if (!hasFallback) props.xmlns = "http://www.w3.org/2000/svg";
45
+ if (!fallback) props.xmlns = "http://www.w3.org/2000/svg";
46
+ let computedFallback = false;
47
+ if (statefulData) {
48
+ const { supportedStates, allStates } = statefulData;
49
+ if (supportedStates.size) {
50
+ const computedStates = [];
51
+ let addedStateFunc = false;
52
+ for (const state of allStates) if (typeof state === "string") {
53
+ if (supportedStates.has(state)) {
54
+ props[state] = {
55
+ type: "boolean",
56
+ value: state,
57
+ template: ""
58
+ };
59
+ computedStates.push(`'${state}': ${state}`);
60
+ }
61
+ } else {
62
+ const stateName = state[0];
63
+ if (supportedStates.has(stateName)) {
64
+ const stateValues = state[1];
65
+ const defaultStateValue = state[2] ?? stateValues[0];
66
+ props[stateName] = {
67
+ type: stateValues.map((value) => `'${value}'`).join(" | "),
68
+ value: stateName,
69
+ template: ""
70
+ };
71
+ computedStates.push(`'${stateName}': namedStateValue(${stateName}, '${defaultStateValue}')`);
72
+ if (!addedStateFunc) {
73
+ addedStateFunc = true;
74
+ addCustomFunctionAsset(imports, assets, options, {
75
+ functionName: "namedStateValue",
76
+ content: `export function namedStateValue(value, defaultValue) {
77
+ return value && value !== defaultValue ? value : undefined;
78
+ }`
79
+ });
80
+ }
81
+ }
82
+ }
83
+ if (computedStates.length) {
84
+ componentCode.push(`let states = $derived(({ ${computedStates.join(", ")} }));`);
85
+ if (fallback && statefulData.fallback) {
86
+ computedFallback = true;
87
+ const func = addFallbackFunctionAsset(imports, assets, options, statefulData.defaultStateValues);
88
+ componentCode.push(`let fallback = $derived(${func}(${JSON.stringify(statefulData.fallback)},states));`);
89
+ }
90
+ componentCode.push(`let className = $derived(Object.entries(states).map(([key, value]) => value ? \`state-\${value === true ? key : value}\` : '').join(' ').trim() || undefined);`);
91
+ props.class = {
92
+ value: "className",
93
+ template: "class={className}"
94
+ };
95
+ }
96
+ }
97
+ }
41
98
  const viewBoxPropValue = `viewBox${hasComputedViewbox ? "Computed" : ""}`;
42
99
  const getViewBox = (viewBox) => isStringViewBox ? `'${stringifyIconViewBox(viewBox)}'` : JSON.stringify(minifyViewBox(viewBox));
43
100
  if (hasComputedViewbox) componentCode.push(`const baseViewBox = ${getViewBox(viewBox)};`, `const squareViewBox = ${getViewBox(makeSquareViewBox(viewBox))};`, `let ${viewBoxPropValue} = $derived(square ? squareViewBox : baseViewBox);`);
@@ -45,11 +102,11 @@ function createSvelteComponent(data, options) {
45
102
  const ratioValue = getViewBoxRatio(viewBox);
46
103
  if (hasComputedRatio) componentCode.push(`let ratio = $derived(square ? 1 : ${ratioValue});`);
47
104
  if (hasFixedSize) {
48
- const sizeProps = getComponentSizeValues(options, data.viewBox);
105
+ const sizeProps = getComponentSizeValues(options, viewBox);
49
106
  if (!sizeProps) throw new Error("Fixed size expected, but could not be determined");
50
107
  props.width = sizeProps.width;
51
108
  props.height = sizeProps.height;
52
- } else if (hasFallback) {
109
+ } else if (fallback) {
53
110
  props.width = {
54
111
  type: "string",
55
112
  value: "width",
@@ -79,17 +136,19 @@ function createSvelteComponent(data, options) {
79
136
  value: "viewBox",
80
137
  template: `viewBox={${viewBoxPropValue}}`
81
138
  };
82
- componentCode.push(`const content = ${stringifyFactoryIconContent(data.icon, options)};`);
83
- const innerHTML = hasFallback ? "" : "{@html content}";
139
+ componentCode.push(`const content = ${stringifyFactoryIconContent(icon)};`);
140
+ const innerHTML = fallback ? "" : "{@html content}";
84
141
  props.content = {
85
142
  value: "content",
86
- template: hasFallback ? `content={content} fallback="${data.fallback}"` : ""
143
+ template: fallback ? `content={content} fallback=${computedFallback ? "{fallback}" : `"${fallback}"`}` : ""
87
144
  };
88
145
  const usedProps = getUsedFactoryProps(props);
89
146
  const propsDestricturing = usedProps.length ? `{${[...usedProps, "...props"].join(", ")}}` : "props";
90
147
  componentCode.unshift(`let ${propsDestricturing}${useTS ? ": Props" : ""} = $props();\n`);
91
- if (useTS) componentCode.unshift(`interface Props {\n${stringifyFactoryPropTypes(props)}\n};\n`);
92
- const tag = hasFallback ? "Icon" : "svg";
148
+ const propTypes = stringifyFactoryPropTypes(props);
149
+ if (useTS) componentCode.unshift(`interface Props {\n${propTypes}\n};\n`);
150
+ else if (propTypes.trim()) componentCode.unshift(`/** @type {{${propTypes.replace(/\s*\n\s*/g, " ").trim()}}} */`);
151
+ const tag = fallback ? "Icon" : "svg";
93
152
  const template = `<${tag} ${stringifyFactoryProps(props, "{prop}={{value}}")} {...props}>${innerHTML}</${tag}>`;
94
153
  let content = `<script${useTS ? " lang=\"ts\"" : ""}>
95
154
  ${stringifyFactoryImports(imports)}
@@ -98,7 +157,7 @@ ${componentCode.join("\n")}
98
157
  ${template}
99
158
  `;
100
159
  const style = options.cssMode === "prop" ? styleContent : void 0;
101
- if (styleContent && !style) content += `<style>\n${styleContent}\n</style>\n`;
160
+ if (styleContent && !style) content += `<style>\n${stringifyStylesheet(styleContent)}\n</style>\n`;
102
161
  const types = addSvelteComponentTypes(data, options, assets, props);
103
162
  return {
104
163
  assets,
@@ -1,3 +1,4 @@
1
+ import { CSSGeneratedStylesheet } from "../../css/types.js";
1
2
  /**
2
3
  * File generated by component factory
3
4
  */
@@ -17,7 +18,7 @@ interface GeneratedAssetFile extends GeneratedComponentFile {
17
18
  interface FactoryGeneratedComponent {
18
19
  assets: GeneratedAssetFile[];
19
20
  content: string;
20
- style?: string;
21
+ style?: CSSGeneratedStylesheet;
21
22
  types?: string;
22
23
  dependencies?: Set<string>;
23
24
  }
@@ -2,9 +2,8 @@
2
2
  * Method of importing CSS in generated components
3
3
  *
4
4
  * 'import' - Create external CSS file and import it in component
5
- * 'module' - Create CSS module and import it in component
6
5
  * 'embed' - Embed CSS styles in component (if supported by component, throws error otherwise)
7
6
  * 'prop' - Export CSS as separate property in generated data, do not import in component, do not create asset
8
7
  */
9
- type CSSExportMode = 'import' | 'module' | 'embed' | 'prop';
8
+ type CSSExportMode = 'import' | 'embed' | 'prop';
10
9
  export { CSSExportMode };
@@ -1,4 +1,3 @@
1
- import { IconViewBox } from "../../svg/viewbox/types.js";
2
1
  import { ComponentFactorySource } from "./source.js";
3
2
  /**
4
3
  * Icon data
@@ -7,7 +6,5 @@ interface FactoryIconData {
7
6
  prefix: string;
8
7
  name: string;
9
8
  icon: ComponentFactorySource;
10
- viewBox: IconViewBox;
11
- fallback?: string;
12
9
  }
13
10
  export { FactoryIconData };
@@ -1,4 +1,5 @@
1
1
  import { UniqueHashPartialOptions } from "../../helpers/hash/types.js";
2
+ import { StatefulIconSelectorsConfig } from "../../svg-css/states/selector/types.js";
2
3
  import { CSSExportMode } from "./css.js";
3
4
  /**
4
5
  * Asset path, generated from configuration
@@ -19,10 +20,16 @@ interface ComponentFactoryFileSystemOptions {
19
20
  helpersDirectory?: string;
20
21
  sharedTypes?: boolean;
21
22
  }
23
+ /**
24
+ * Rendering options for stateful icons
25
+ */
26
+ interface ComponentFactoryStatefulIconRenderingOptions {
27
+ stateSelectors?: StatefulIconSelectorsConfig;
28
+ }
22
29
  /**
23
30
  * Rendering options for component factory
24
31
  */
25
- interface ComponentFactoryRenderingOptions {
32
+ interface ComponentFactoryRenderingOptions extends ComponentFactoryStatefulIconRenderingOptions {
26
33
  square?: boolean;
27
34
  cssMode: CSSExportMode;
28
35
  mergeCSS?: GeneratedAssetPath;
@@ -33,4 +40,4 @@ interface ComponentFactoryRenderingOptions {
33
40
  * Options for component factory
34
41
  */
35
42
  interface ComponentFactoryOptions extends ComponentFactoryFileSystemOptions, ComponentFactoryRenderingOptions, Pick<UniqueHashPartialOptions, 'context'> {}
36
- export { ComponentFactoryFileSystemOptions, ComponentFactoryOptions, ComponentFactoryRenderingOptions, GeneratedAssetPath };
43
+ export { ComponentFactoryFileSystemOptions, ComponentFactoryOptions, ComponentFactoryRenderingOptions, ComponentFactoryStatefulIconRenderingOptions, GeneratedAssetPath };
@@ -1,10 +1,25 @@
1
- import { CSSKeyframes, CSSRules } from "../../css/types.js";
2
- import { ConvertedSVGContent } from "../../svg-css/types.js";
1
+ import { IconViewBox } from "../../svg/viewbox/types.js";
2
+ import { IconStatesList } from "../../svg-css/states/types.js";
3
+ import { SVGCSSStatefulIcon } from "../../svg-css/icon/types.js";
4
+ import { IconFallbackTemplate } from "../../svg-css/states/fallback/types.js";
5
+ import { StatefulIconSelectorsContext } from "../../svg-css/states/selector/types.js";
6
+ /**
7
+ * Generated data for stateful icon
8
+ */
9
+ interface StatefulComponentFactorySource {
10
+ fallback?: IconFallbackTemplate;
11
+ allStates: IconStatesList;
12
+ supportedStates: Set<string>;
13
+ defaultStateValues: Record<string, string | boolean>;
14
+ supportedStateValues: Record<string, string | boolean>;
15
+ context: StatefulIconSelectorsContext;
16
+ }
3
17
  /**
4
18
  * Content for component factory
5
19
  */
6
- interface ComponentFactorySource extends Omit<ConvertedSVGContent, 'classes' | 'keyframes'> {
7
- classes?: Record<string, CSSRules | string>;
8
- keyframes?: Record<string, CSSKeyframes | string>;
20
+ interface ComponentFactorySource extends Omit<SVGCSSStatefulIcon, 'viewBox' | 'fallback' | 'states'> {
21
+ viewBox: IconViewBox;
22
+ defaultFallback?: string;
23
+ statefulData?: StatefulComponentFactorySource;
9
24
  }
10
- export { ComponentFactorySource };
25
+ export { ComponentFactorySource, StatefulComponentFactorySource };
@@ -10,44 +10,100 @@ import { makeSquareViewBox } from "../svg/viewbox/square.js";
10
10
  import { getUsedFactoryProps } from "./helpers/props/ts.js";
11
11
  import { minifyViewBox } from "../svg/viewbox/minify.js";
12
12
  import { getViewBoxRatio } from "./helpers/content/ratio.js";
13
+ import { addCustomFunctionAsset } from "./helpers/functions/custom.js";
14
+ import { addFallbackFunctionAsset } from "./helpers/functions/fallback.js";
13
15
  import { addVueComponentTypes } from "./helpers/ts/vue.js";
14
16
 
15
17
  /**
16
18
  * Create functional Vue component code
17
19
  */
18
20
  function createVueFunctionalComponent(data, options) {
21
+ const icon = data.icon;
22
+ const viewBox = icon.viewBox;
23
+ const fallback = icon.defaultFallback;
24
+ const statefulData = icon.statefulData;
19
25
  const assets = [];
20
26
  const imports = createFactoryImports();
21
27
  const dependencies = /* @__PURE__ */ new Set();
22
- const hasFallback = !!data.fallback;
23
- if (hasFallback) {
28
+ if (fallback) {
24
29
  imports.named["@iconify/css-vue"] = new Set(["Icon"]);
25
30
  dependencies.add("@iconify/css-vue");
26
31
  }
27
32
  const vueNamedImports = new Set(["defineComponent", "h"]);
28
33
  imports.named["vue"] = vueNamedImports;
29
- const style = generateCSSFilesForComponent(data.icon, imports, assets, options);
34
+ const style = generateCSSFilesForComponent(icon, imports, assets, options);
30
35
  const isEmbeddedCSS = options.cssMode === "embed";
31
36
  let hasFixedSize = !!options.width && !!options.height;
32
- const viewBox = data.viewBox;
33
37
  const hasComputedViewbox = options.square && !hasFixedSize && viewBox.width !== viewBox.height;
34
- const isStringViewBox = !hasFallback;
38
+ const isStringViewBox = !fallback;
35
39
  const hasComputedRatio = hasComputedViewbox && isStringViewBox;
36
40
  if (!hasComputedViewbox && (options.width || options.height)) hasFixedSize = true;
37
41
  const componentCode = [];
38
42
  const props = {};
39
- if (!hasFallback) props.xmlns = "http://www.w3.org/2000/svg";
43
+ if (!fallback) props.xmlns = "http://www.w3.org/2000/svg";
44
+ let computedFallback = false;
45
+ if (statefulData) {
46
+ const { supportedStates, allStates } = statefulData;
47
+ if (supportedStates.size) {
48
+ const computedStates = [];
49
+ let addedStateFunc = false;
50
+ for (const state of allStates) if (typeof state === "string") {
51
+ if (supportedStates.has(state)) {
52
+ props[state] = {
53
+ type: "boolean",
54
+ value: state,
55
+ template: ""
56
+ };
57
+ computedStates.push(`'${state}': props['${state}']`);
58
+ }
59
+ } else {
60
+ const stateName = state[0];
61
+ if (supportedStates.has(stateName)) {
62
+ const stateValues = state[1];
63
+ const defaultStateValue = state[2] ?? stateValues[0];
64
+ props[stateName] = {
65
+ type: stateValues.map((value) => `'${value}'`).join(" | "),
66
+ value: stateName,
67
+ template: ""
68
+ };
69
+ computedStates.push(`'${stateName}': namedStateValue(props['${stateName}'], '${defaultStateValue}')`);
70
+ if (!addedStateFunc) {
71
+ addedStateFunc = true;
72
+ addCustomFunctionAsset(imports, assets, options, {
73
+ functionName: "namedStateValue",
74
+ content: `export function namedStateValue(value, defaultValue) {
75
+ return value && value !== defaultValue ? value : undefined;
76
+ }`
77
+ });
78
+ }
79
+ }
80
+ }
81
+ if (computedStates.length) {
82
+ componentCode.push(`const states = computed(() => ({ ${computedStates.join(", ")} }));`);
83
+ if (fallback && statefulData.fallback) {
84
+ computedFallback = true;
85
+ const func = addFallbackFunctionAsset(imports, assets, options, statefulData.defaultStateValues);
86
+ componentCode.push(`const fallback = computed(() => ${func}(${JSON.stringify(statefulData.fallback)},states.value));`);
87
+ }
88
+ componentCode.push(`const className = computed(() => Object.entries(states.value).map(([key, value]) => value ? \`state-\${value === true ? key : value}\` : '').join(' ').trim() || undefined);`);
89
+ props["class"] = {
90
+ value: "class",
91
+ template: `'class': className.value,`
92
+ };
93
+ }
94
+ }
95
+ }
40
96
  const getViewBox = (viewBox) => isStringViewBox ? `'${stringifyIconViewBox(viewBox)}'` : JSON.stringify(minifyViewBox(viewBox));
41
97
  if (hasComputedViewbox) componentCode.push(`const baseViewBox = ${getViewBox(viewBox)};`, `const squareViewBox = ${getViewBox(makeSquareViewBox(viewBox))};`, `const viewBox = computed(() => props.square ? squareViewBox : baseViewBox);`);
42
98
  else componentCode.push(`const viewBox = ${getViewBox(viewBox)};`);
43
99
  const ratioValue = getViewBoxRatio(viewBox);
44
100
  if (hasComputedRatio) componentCode.push(`const ratio = computed(() => props.square ? 1 : ${ratioValue});`);
45
101
  if (hasFixedSize) {
46
- const sizeProps = getComponentSizeValues(options, data.viewBox);
102
+ const sizeProps = getComponentSizeValues(options, viewBox);
47
103
  if (!sizeProps) throw new Error("Fixed size expected, but could not be determined");
48
104
  props.width = sizeProps.width;
49
105
  props.height = sizeProps.height;
50
- } else if (hasFallback) {
106
+ } else if (fallback) {
51
107
  props.width = {
52
108
  type: "string",
53
109
  value: "width",
@@ -61,7 +117,6 @@ function createVueFunctionalComponent(data, options) {
61
117
  } else {
62
118
  const getSizeProps = addSizeFunctionAsset(imports, assets, options);
63
119
  componentCode.push(`const size = computed(() => ${getSizeProps}(props.width, props.height, ${hasComputedRatio ? "ratio.value" : ratioValue}));`);
64
- vueNamedImports.add("computed");
65
120
  props.width = {
66
121
  type: "string",
67
122
  value: "width",
@@ -73,15 +128,19 @@ function createVueFunctionalComponent(data, options) {
73
128
  template: ""
74
129
  };
75
130
  }
131
+ if (componentCode.some((line) => line.includes("computed("))) vueNamedImports.add("computed");
76
132
  if (options.square) props.square = { type: "boolean" };
77
133
  props.viewBox = {
78
134
  value: "viewBox",
79
135
  template: hasComputedViewbox ? "viewBox: viewBox.value," : "viewBox,"
80
136
  };
81
- props[hasFallback ? "content" : "innerHTML"] = { value: stringifyFactoryIconContent(data.icon, options, isEmbeddedCSS ? style : void 0) };
82
- if (data.fallback) props.fallback = data.fallback;
137
+ props[fallback ? "content" : "innerHTML"] = { value: stringifyFactoryIconContent(icon, isEmbeddedCSS ? style : void 0) };
138
+ if (fallback) props.fallback = computedFallback ? {
139
+ value: "fallback",
140
+ template: "fallback: fallback.value,"
141
+ } : fallback;
83
142
  const types = addVueComponentTypes(data, options, assets, props);
84
- componentCode.push(`return () => h(${hasFallback ? "Icon" : "'svg'"}, {
143
+ componentCode.push(`return () => h(${fallback ? "Icon" : "'svg'"}, {
85
144
  ${stringifyFactoryPropsAsJSON(props, "\n ")}
86
145
  });`);
87
146
  const usedProps = getUsedFactoryProps(props);
@@ -1,3 +1,4 @@
1
+ import { stringifyStylesheet } from "../css/stylesheet.js";
1
2
  import { getComponentSizeValues } from "./helpers/content/size.js";
2
3
  import { stringifyFactoryIconContent } from "./helpers/content/stringify.js";
3
4
  import { stringifyIconViewBox } from "../svg/viewbox/value.js";
@@ -10,44 +11,100 @@ import { makeSquareViewBox } from "../svg/viewbox/square.js";
10
11
  import { getUsedFactoryProps, stringifyFactoryPropTypes } from "./helpers/props/ts.js";
11
12
  import { minifyViewBox } from "../svg/viewbox/minify.js";
12
13
  import { getViewBoxRatio } from "./helpers/content/ratio.js";
14
+ import { addCustomFunctionAsset } from "./helpers/functions/custom.js";
15
+ import { addFallbackFunctionAsset } from "./helpers/functions/fallback.js";
13
16
  import { addVueComponentTypes } from "./helpers/ts/vue.js";
14
17
 
15
18
  /**
16
19
  * Create Vue component code
17
20
  */
18
21
  function createVueComponent(data, options) {
22
+ const icon = data.icon;
23
+ const viewBox = icon.viewBox;
24
+ const fallback = icon.defaultFallback;
25
+ const statefulData = icon.statefulData;
19
26
  const useTS = options.ts ?? false;
20
27
  const assets = [];
21
28
  const imports = createFactoryImports();
22
29
  const dependencies = /* @__PURE__ */ new Set();
23
- const hasFallback = !!data.fallback;
24
- if (hasFallback) {
30
+ if (fallback) {
25
31
  imports.named["@iconify/css-vue"] = new Set(["Icon"]);
26
32
  dependencies.add("@iconify/css-vue");
27
33
  }
28
34
  const vueNamedImports = /* @__PURE__ */ new Set();
29
35
  imports.named["vue"] = vueNamedImports;
30
- const styleContent = generateCSSFilesForComponent(data.icon, imports, assets, options);
36
+ const styleContent = generateCSSFilesForComponent(icon, imports, assets, options);
31
37
  let hasFixedSize = !!options.width && !!options.height;
32
- const viewBox = data.viewBox;
33
38
  const hasComputedViewbox = options.square && !hasFixedSize && viewBox.width !== viewBox.height;
34
- const isStringViewBox = !hasFallback;
39
+ const isStringViewBox = !fallback;
35
40
  const hasComputedRatio = hasComputedViewbox && isStringViewBox;
36
41
  if (!hasComputedViewbox && (options.width || options.height)) hasFixedSize = true;
37
42
  const componentCode = [];
38
43
  const props = {};
39
- if (!hasFallback) props.xmlns = "http://www.w3.org/2000/svg";
44
+ if (!fallback) props.xmlns = "http://www.w3.org/2000/svg";
45
+ let computedFallback = false;
46
+ if (statefulData) {
47
+ const { supportedStates, allStates } = statefulData;
48
+ if (supportedStates.size) {
49
+ const computedStates = [];
50
+ let addedStateFunc = false;
51
+ for (const state of allStates) if (typeof state === "string") {
52
+ if (supportedStates.has(state)) {
53
+ props[state] = {
54
+ type: "boolean",
55
+ value: state,
56
+ template: ""
57
+ };
58
+ computedStates.push(`'${state}': props['${state}']`);
59
+ }
60
+ } else {
61
+ const stateName = state[0];
62
+ if (supportedStates.has(stateName)) {
63
+ const stateValues = state[1];
64
+ const defaultStateValue = state[2] ?? stateValues[0];
65
+ props[stateName] = {
66
+ type: stateValues.map((value) => `'${value}'`).join(" | "),
67
+ value: stateName,
68
+ template: ""
69
+ };
70
+ computedStates.push(`'${stateName}': namedStateValue(props['${stateName}'], '${defaultStateValue}')`);
71
+ if (!addedStateFunc) {
72
+ addedStateFunc = true;
73
+ addCustomFunctionAsset(imports, assets, options, {
74
+ functionName: "namedStateValue",
75
+ content: `export function namedStateValue(value, defaultValue) {
76
+ return value && value !== defaultValue ? value : undefined;
77
+ }`
78
+ });
79
+ }
80
+ }
81
+ }
82
+ if (computedStates.length) {
83
+ componentCode.push(`const states = computed(() => ({ ${computedStates.join(", ")} }));`);
84
+ if (fallback && statefulData.fallback) {
85
+ computedFallback = true;
86
+ const func = addFallbackFunctionAsset(imports, assets, options, statefulData.defaultStateValues);
87
+ componentCode.push(`const fallback = computed(() => ${func}(${JSON.stringify(statefulData.fallback)},states.value));`);
88
+ }
89
+ componentCode.push(`const className = computed(() => Object.entries(states.value).map(([key, value]) => value ? \`state-\${value === true ? key : value}\` : '').join(' ').trim() || undefined);`);
90
+ props.className = {
91
+ value: "className",
92
+ template: ":class=\"className\""
93
+ };
94
+ }
95
+ }
96
+ }
40
97
  const getViewBox = (viewBox) => isStringViewBox ? `'${stringifyIconViewBox(viewBox)}'` : JSON.stringify(minifyViewBox(viewBox));
41
98
  if (hasComputedViewbox) componentCode.push(`const baseViewBox = ${getViewBox(viewBox)};`, `const squareViewBox = ${getViewBox(makeSquareViewBox(viewBox))};`, `const viewBox = computed(() => props.square ? squareViewBox : baseViewBox);`);
42
99
  else componentCode.push(`const viewBox = ${getViewBox(viewBox)};`);
43
100
  const ratioValue = getViewBoxRatio(viewBox);
44
101
  if (hasComputedRatio) componentCode.push(`const ratio = computed(() => props.square ? 1 : ${ratioValue});`);
45
102
  if (hasFixedSize) {
46
- const sizeProps = getComponentSizeValues(options, data.viewBox);
103
+ const sizeProps = getComponentSizeValues(options, viewBox);
47
104
  if (!sizeProps) throw new Error("Fixed size expected, but could not be determined");
48
105
  props.width = sizeProps.width;
49
106
  props.height = sizeProps.height;
50
- } else if (hasFallback) {
107
+ } else if (fallback) {
51
108
  props.width = {
52
109
  type: "string",
53
110
  value: "width",
@@ -61,7 +118,6 @@ function createVueComponent(data, options) {
61
118
  } else {
62
119
  const getSizeProps = addSizeFunctionAsset(imports, assets, options);
63
120
  componentCode.push(`const size = computed(() => ${getSizeProps}(props.width, props.height, ${hasComputedRatio ? "ratio.value" : ratioValue}));`);
64
- vueNamedImports.add("computed");
65
121
  props.width = {
66
122
  type: "string",
67
123
  value: "width",
@@ -73,22 +129,23 @@ function createVueComponent(data, options) {
73
129
  template: ""
74
130
  };
75
131
  }
132
+ if (componentCode.some((line) => line.includes("computed("))) vueNamedImports.add("computed");
76
133
  if (options.square) props.square = { type: "boolean" };
77
134
  props.viewBox = {
78
135
  value: "viewBox",
79
136
  template: ":viewBox=\"viewBox\""
80
137
  };
81
- componentCode.push(`const content = ${stringifyFactoryIconContent(data.icon, options)};`);
138
+ componentCode.push(`const content = ${stringifyFactoryIconContent(icon)};`);
82
139
  props.content = {
83
140
  value: "content",
84
- template: hasFallback ? `:content="content" fallback="${data.fallback}"` : "v-html=\"content\""
141
+ template: fallback ? `:content="content" ${computedFallback ? ":fallback=\"fallback\"" : `fallback="${fallback}"`}` : "v-html=\"content\""
85
142
  };
86
143
  const usedProps = getUsedFactoryProps(props);
87
144
  if (usedProps.length) {
88
145
  const tsCode = useTS ? `<{\n${stringifyFactoryPropTypes(props)}\n}>` : "";
89
146
  componentCode.unshift(`const props = defineProps${tsCode}(${tsCode ? "" : JSON.stringify(usedProps)});\n`);
90
147
  }
91
- const template = `<template><${hasFallback ? "Icon" : "svg"} ${stringifyFactoryProps(props, ":{prop}=\"{value}\"")} /></template>`;
148
+ const template = `<template><${fallback ? "Icon" : "svg"} ${stringifyFactoryProps(props, ":{prop}=\"{value}\"")} /></template>`;
92
149
  let content = `<script setup${useTS ? " lang=\"ts\"" : ""}>
93
150
  ${stringifyFactoryImports(imports)}
94
151
  ${componentCode.join("\n")}
@@ -96,7 +153,7 @@ ${componentCode.join("\n")}
96
153
  ${template}
97
154
  `;
98
155
  const style = options.cssMode === "prop" ? styleContent : void 0;
99
- if (styleContent && !style) content += `<style>\n${styleContent}\n</style>\n`;
156
+ if (styleContent && !style) content += `<style>\n${stringifyStylesheet(styleContent)}\n</style>\n`;
100
157
  const types = addVueComponentTypes(data, options, assets, props);
101
158
  return {
102
159
  assets,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Find used animation names in content
3
+ *
4
+ * This function is opinionated, it is designed to find animation names in typical CSS content, but it may not cover all edge cases.
5
+ * Assumes animation name uses only the following characters: a-z, 0-9, -, _ and starts with a letter.
6
+ *
7
+ * Might return false positives
8
+ */
9
+ declare function findUsedKeyframes(content: string): string[];
10
+ export { findUsedKeyframes };
@@ -0,0 +1,35 @@
1
+ import { findCSSPropertyValues } from "./prop.js";
2
+
3
+ const reservedAnimationNames = new Set([
4
+ "none",
5
+ "inherit",
6
+ "initial",
7
+ "revert",
8
+ "revert-layer",
9
+ "forwards",
10
+ "infinite",
11
+ "unset",
12
+ "linear",
13
+ "ease",
14
+ "ease-in",
15
+ "ease-out",
16
+ "ease-in-out"
17
+ ]);
18
+ /**
19
+ * Find used animation names in content
20
+ *
21
+ * This function is opinionated, it is designed to find animation names in typical CSS content, but it may not cover all edge cases.
22
+ * Assumes animation name uses only the following characters: a-z, 0-9, -, _ and starts with a letter.
23
+ *
24
+ * Might return false positives
25
+ */
26
+ function findUsedKeyframes(content) {
27
+ const keyframes = /* @__PURE__ */ new Set();
28
+ findCSSPropertyValues(content, "animation-name").forEach((name) => keyframes.add(name));
29
+ findCSSPropertyValues(content, "animation").forEach((value) => {
30
+ value.split(/\s+/).filter((part) => !reservedAnimationNames.has(part) && part.match(/^[a-z]+[a-z0-9_-]*$/)).forEach((part) => keyframes.add(part));
31
+ });
32
+ return Array.from(keyframes);
33
+ }
34
+
35
+ export { findUsedKeyframes };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Find class names in content
3
+ */
4
+ declare function findUsedClassNames(content: string): string[];
5
+ export { findUsedClassNames };
@@ -0,0 +1,14 @@
1
+ import { splitClassName } from "../../classname/toggle.js";
2
+
3
+ /**
4
+ * Find class names in content
5
+ */
6
+ function findUsedClassNames(content) {
7
+ const classNames = /* @__PURE__ */ new Set();
8
+ content.matchAll(/class=["']([^"']+)["']/g).forEach((match) => {
9
+ splitClassName(match[1]).forEach((className) => classNames.add(className));
10
+ });
11
+ return Array.from(classNames);
12
+ }
13
+
14
+ export { findUsedClassNames };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Find all values of a CSS property in content
3
+ */
4
+ declare function findCSSPropertyValues(content: string, property: string): string[];
5
+ export { findCSSPropertyValues };