vectify 2.0.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +679 -0
  3. package/README.zh-CN.md +683 -0
  4. package/dist/chunk-4BWKFV7W.mjs +1311 -0
  5. package/dist/chunk-CIKTK6HI.mjs +96 -0
  6. package/dist/cli.d.mts +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +1483 -0
  9. package/dist/cli.mjs +56 -0
  10. package/dist/helpers-UPZEBRGK.mjs +26 -0
  11. package/dist/index.d.mts +281 -0
  12. package/dist/index.d.ts +281 -0
  13. package/dist/index.js +1463 -0
  14. package/dist/index.mjs +27 -0
  15. package/dist/templates/angular/component.ts.hbs +121 -0
  16. package/dist/templates/angular/createIcon.ts.hbs +116 -0
  17. package/dist/templates/astro/component.astro.hbs +110 -0
  18. package/dist/templates/astro/createIcon.astro.hbs +111 -0
  19. package/dist/templates/lit/component.js.hbs +12 -0
  20. package/dist/templates/lit/component.ts.hbs +19 -0
  21. package/dist/templates/lit/createIcon.js.hbs +98 -0
  22. package/dist/templates/lit/createIcon.ts.hbs +99 -0
  23. package/dist/templates/preact/component.jsx.hbs +8 -0
  24. package/dist/templates/preact/component.tsx.hbs +11 -0
  25. package/dist/templates/preact/createIcon.jsx.hbs +101 -0
  26. package/dist/templates/preact/createIcon.tsx.hbs +121 -0
  27. package/dist/templates/qwik/component.jsx.hbs +7 -0
  28. package/dist/templates/qwik/component.tsx.hbs +8 -0
  29. package/dist/templates/qwik/createIcon.jsx.hbs +100 -0
  30. package/dist/templates/qwik/createIcon.tsx.hbs +111 -0
  31. package/dist/templates/react/component.jsx.hbs +8 -0
  32. package/dist/templates/react/component.tsx.hbs +11 -0
  33. package/dist/templates/react/createIcon.jsx.hbs +100 -0
  34. package/dist/templates/react/createIcon.tsx.hbs +117 -0
  35. package/dist/templates/solid/component.tsx.hbs +10 -0
  36. package/dist/templates/solid/createIcon.jsx.hbs +127 -0
  37. package/dist/templates/solid/createIcon.tsx.hbs +139 -0
  38. package/dist/templates/svelte/component.js.svelte.hbs +9 -0
  39. package/dist/templates/svelte/component.ts.svelte.hbs +10 -0
  40. package/dist/templates/svelte/icon.js.svelte.hbs +123 -0
  41. package/dist/templates/svelte/icon.ts.svelte.hbs +124 -0
  42. package/dist/templates/template-engine.ts +107 -0
  43. package/dist/templates/vanilla/component.ts.hbs +8 -0
  44. package/dist/templates/vanilla/createIcon.js.hbs +111 -0
  45. package/dist/templates/vanilla/createIcon.ts.hbs +124 -0
  46. package/dist/templates/vue/component.js.vue.hbs +21 -0
  47. package/dist/templates/vue/component.ts.vue.hbs +22 -0
  48. package/dist/templates/vue/icon.js.vue.hbs +155 -0
  49. package/dist/templates/vue/icon.ts.vue.hbs +157 -0
  50. package/package.json +78 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,27 @@
1
+ import {
2
+ findConfig,
3
+ frameworkRegistry,
4
+ generate,
5
+ generateIcons,
6
+ getFrameworkStrategy,
7
+ init,
8
+ loadConfig,
9
+ watch
10
+ } from "./chunk-4BWKFV7W.mjs";
11
+ import "./chunk-CIKTK6HI.mjs";
12
+
13
+ // src/types.ts
14
+ function defineConfig(config) {
15
+ return config;
16
+ }
17
+ export {
18
+ defineConfig,
19
+ findConfig,
20
+ frameworkRegistry,
21
+ generate,
22
+ generateIcons,
23
+ getFrameworkStrategy,
24
+ init,
25
+ loadConfig,
26
+ watch
27
+ };
@@ -0,0 +1,121 @@
1
+ import { Component, Input } from '@angular/core'
2
+ import { CommonModule } from '@angular/common'
3
+ import type { IconNode } from 'vectify'
4
+
5
+ const iconNode: IconNode[] = [
6
+ {{{formattedNodes}}}
7
+ ]
8
+
9
+ @Component({
10
+ selector: '{{componentName}}',
11
+ standalone: true,
12
+ imports: [CommonModule],
13
+ template: `
14
+ <svg
15
+ [attr.width]="size"
16
+ [attr.height]="size"
17
+ viewBox="0 0 24 24"
18
+ [attr.aria-hidden]="shouldHide"
19
+ [attr.aria-label]="ariaLabel"
20
+ [attr.role]="title || ariaLabel ? 'img' : null"
21
+ [attr.class]="mergedClassName"
22
+ >
23
+ <title *ngIf="title">{{ title }}</title>
24
+ <ng-container *ngFor="let node of processedNodes">
25
+ <ng-container [ngSwitch]="node.type">
26
+ <path *ngSwitchCase="'path'" [attr.d]="node.attrs.d" [ngStyle]="node.attrs"></path>
27
+ <circle *ngSwitchCase="'circle'" [attr.cx]="node.attrs.cx" [attr.cy]="node.attrs.cy" [attr.r]="node.attrs.r" [ngStyle]="node.attrs"></circle>
28
+ <rect *ngSwitchCase="'rect'" [attr.x]="node.attrs.x" [attr.y]="node.attrs.y" [attr.width]="node.attrs.width" [attr.height]="node.attrs.height" [ngStyle]="node.attrs"></rect>
29
+ <line *ngSwitchCase="'line'" [attr.x1]="node.attrs.x1" [attr.y1]="node.attrs.y1" [attr.x2]="node.attrs.x2" [attr.y2]="node.attrs.y2" [ngStyle]="node.attrs"></line>
30
+ <polyline *ngSwitchCase="'polyline'" [attr.points]="node.attrs.points" [ngStyle]="node.attrs"></polyline>
31
+ <polygon *ngSwitchCase="'polygon'" [attr.points]="node.attrs.points" [ngStyle]="node.attrs"></polygon>
32
+ <ellipse *ngSwitchCase="'ellipse'" [attr.cx]="node.attrs.cx" [attr.cy]="node.attrs.cy" [attr.rx]="node.attrs.rx" [attr.ry]="node.attrs.ry" [ngStyle]="node.attrs"></ellipse>
33
+ <g *ngSwitchDefault [ngStyle]="node.attrs">
34
+ <ng-container *ngFor="let child of node.children">
35
+ <ng-container [ngSwitch]="child.type">
36
+ <path *ngSwitchCase="'path'" [attr.d]="child.attrs.d" [ngStyle]="child.attrs"></path>
37
+ <circle *ngSwitchCase="'circle'" [attr.cx]="child.attrs.cx" [attr.cy]="child.attrs.cy" [attr.r]="child.attrs.r" [ngStyle]="child.attrs"></circle>
38
+ <rect *ngSwitchCase="'rect'" [attr.x]="child.attrs.x" [attr.y]="child.attrs.y" [attr.width]="child.attrs.width" [attr.height]="child.attrs.height" [ngStyle]="child.attrs"></rect>
39
+ </ng-container>
40
+ </ng-container>
41
+ </g>
42
+ </ng-container>
43
+ </ng-container>
44
+ </svg>
45
+ `
46
+ })
47
+ export class {{componentName}}Component {
48
+ @Input() size: number | string = 24
49
+ @Input() color: string = 'currentColor'
50
+ @Input() strokeWidth: number | string = 2
51
+ @Input() className?: string
52
+ @Input() title?: string
53
+ @Input() ariaLabel?: string
54
+ @Input() ariaHidden?: boolean
55
+
56
+ private iconNode: IconNode[] = iconNode
57
+ private keepColors: boolean = {{keepColors}}
58
+
59
+ get shouldHide(): boolean {
60
+ return this.ariaHidden !== undefined ? this.ariaHidden : (!this.title && !this.ariaLabel)
61
+ }
62
+
63
+ get mergedClassName(): string {
64
+ return this.className ? `vectify-icon ${this.className}` : 'vectify-icon'
65
+ }
66
+
67
+ get processedNodes(): any[] {
68
+ return this.iconNode.map(node => this.renderNode(node))
69
+ }
70
+
71
+ private renderNode(node: IconNode): any {
72
+ const [type, attrs, children] = node
73
+
74
+ let cleanedAttrs: Record<string, any>
75
+
76
+ if (this.keepColors) {
77
+ cleanedAttrs = attrs
78
+ } else {
79
+ // Track color attributes to determine icon type
80
+ let hasFill = false
81
+ let hasStroke = false
82
+ let originalStrokeWidth: number | string | undefined
83
+
84
+ Object.entries(attrs).forEach(([key, value]) => {
85
+ if (key === 'fill' && value !== 'none') {
86
+ hasFill = true
87
+ }
88
+ if (key === 'stroke') {
89
+ hasStroke = true
90
+ }
91
+ if (key === 'strokeWidth' || key === 'stroke-width') {
92
+ originalStrokeWidth = value
93
+ }
94
+ })
95
+
96
+ // Keep non-color attributes
97
+ cleanedAttrs = Object.fromEntries(
98
+ Object.entries(attrs).filter(([key]) =>
99
+ !['stroke', 'fill', 'strokeWidth', 'stroke-width'].includes(key)
100
+ )
101
+ )
102
+
103
+ // Apply color based on original attributes
104
+ if (hasFill) {
105
+ cleanedAttrs.fill = this.color
106
+ } else if (hasStroke) {
107
+ cleanedAttrs.fill = 'none'
108
+ cleanedAttrs.stroke = this.color
109
+ cleanedAttrs['stroke-width'] = originalStrokeWidth ?? this.strokeWidth
110
+ cleanedAttrs['stroke-linecap'] = 'round'
111
+ cleanedAttrs['stroke-linejoin'] = 'round'
112
+ }
113
+ }
114
+
115
+ return {
116
+ type,
117
+ attrs: cleanedAttrs,
118
+ children: children && children.length > 0 ? children.map(c => this.renderNode(c)) : []
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,116 @@
1
+ import { Component, Input } from '@angular/core'
2
+ import { CommonModule } from '@angular/common'
3
+ import type { IconNode } from 'vectify'
4
+
5
+ @Component({
6
+ selector: 'vectify-icon-base',
7
+ standalone: true,
8
+ imports: [CommonModule],
9
+ template: `
10
+ <svg
11
+ [attr.width]="size"
12
+ [attr.height]="size"
13
+ viewBox="0 0 24 24"
14
+ [attr.aria-hidden]="shouldHide"
15
+ [attr.aria-label]="ariaLabel"
16
+ [attr.role]="title || ariaLabel ? 'img' : null"
17
+ [attr.class]="mergedClassName"
18
+ >
19
+ <title *ngIf="title">{{ title }}</title>
20
+ <ng-container *ngFor="let node of processedNodes">
21
+ <ng-container [ngSwitch]="node.type">
22
+ <path *ngSwitchCase="'path'" [attr.d]="node.attrs.d" [ngStyle]="node.attrs"></path>
23
+ <circle *ngSwitchCase="'circle'" [attr.cx]="node.attrs.cx" [attr.cy]="node.attrs.cy" [attr.r]="node.attrs.r" [ngStyle]="node.attrs"></circle>
24
+ <rect *ngSwitchCase="'rect'" [attr.x]="node.attrs.x" [attr.y]="node.attrs.y" [attr.width]="node.attrs.width" [attr.height]="node.attrs.height" [ngStyle]="node.attrs"></rect>
25
+ <line *ngSwitchCase="'line'" [attr.x1]="node.attrs.x1" [attr.y1]="node.attrs.y1" [attr.x2]="node.attrs.x2" [attr.y2]="node.attrs.y2" [ngStyle]="node.attrs"></line>
26
+ <polyline *ngSwitchCase="'polyline'" [attr.points]="node.attrs.points" [ngStyle]="node.attrs"></polyline>
27
+ <polygon *ngSwitchCase="'polygon'" [attr.points]="node.attrs.points" [ngStyle]="node.attrs"></polygon>
28
+ <ellipse *ngSwitchCase="'ellipse'" [attr.cx]="node.attrs.cx" [attr.cy]="node.attrs.cy" [attr.rx]="node.attrs.rx" [attr.ry]="node.attrs.ry" [ngStyle]="node.attrs"></ellipse>
29
+ <g *ngSwitchDefault [ngStyle]="node.attrs">
30
+ <ng-container *ngFor="let child of node.children">
31
+ <ng-container [ngSwitch]="child.type">
32
+ <path *ngSwitchCase="'path'" [attr.d]="child.attrs.d" [ngStyle]="child.attrs"></path>
33
+ <circle *ngSwitchCase="'circle'" [attr.cx]="child.attrs.cx" [attr.cy]="child.attrs.cy" [attr.r]="child.attrs.r" [ngStyle]="child.attrs"></circle>
34
+ <rect *ngSwitchCase="'rect'" [attr.x]="child.attrs.x" [attr.y]="child.attrs.y" [attr.width]="child.attrs.width" [attr.height]="child.attrs.height" [ngStyle]="child.attrs"></rect>
35
+ </ng-container>
36
+ </ng-container>
37
+ </g>
38
+ </ng-container>
39
+ </ng-container>
40
+ </svg>
41
+ `
42
+ })
43
+ export class IconBaseComponent {
44
+ @Input() size: number | string = 24
45
+ @Input() color: string = 'currentColor'
46
+ @Input() strokeWidth: number | string = 2
47
+ @Input() className?: string
48
+ @Input() title?: string
49
+ @Input() ariaLabel?: string
50
+ @Input() ariaHidden?: boolean
51
+ @Input() iconNode: IconNode[] = []
52
+ @Input() keepColors: boolean = false
53
+
54
+ get shouldHide(): boolean {
55
+ return this.ariaHidden !== undefined ? this.ariaHidden : (!this.title && !this.ariaLabel)
56
+ }
57
+
58
+ get mergedClassName(): string {
59
+ return this.className ? `vectify-icon ${this.className}` : 'vectify-icon'
60
+ }
61
+
62
+ get processedNodes(): any[] {
63
+ return this.iconNode.map(node => this.renderNode(node))
64
+ }
65
+
66
+ private renderNode(node: IconNode): any {
67
+ const [type, attrs, children] = node
68
+
69
+ let cleanedAttrs: Record<string, any>
70
+
71
+ if (this.keepColors) {
72
+ cleanedAttrs = attrs
73
+ } else {
74
+ // Track color attributes to determine icon type
75
+ let hasFill = false
76
+ let hasStroke = false
77
+ let originalStrokeWidth: number | string | undefined
78
+
79
+ Object.entries(attrs).forEach(([key, value]) => {
80
+ if (key === 'fill' && value !== 'none') {
81
+ hasFill = true
82
+ }
83
+ if (key === 'stroke') {
84
+ hasStroke = true
85
+ }
86
+ if (key === 'strokeWidth' || key === 'stroke-width') {
87
+ originalStrokeWidth = value
88
+ }
89
+ })
90
+
91
+ // Keep non-color attributes
92
+ cleanedAttrs = Object.fromEntries(
93
+ Object.entries(attrs).filter(([key]) =>
94
+ !['stroke', 'fill', 'strokeWidth', 'stroke-width'].includes(key)
95
+ )
96
+ )
97
+
98
+ // Apply color based on original attributes
99
+ if (hasFill) {
100
+ cleanedAttrs.fill = this.color
101
+ } else if (hasStroke) {
102
+ cleanedAttrs.fill = 'none'
103
+ cleanedAttrs.stroke = this.color
104
+ cleanedAttrs['stroke-width'] = originalStrokeWidth ?? this.strokeWidth
105
+ cleanedAttrs['stroke-linecap'] = 'round'
106
+ cleanedAttrs['stroke-linejoin'] = 'round'
107
+ }
108
+ }
109
+
110
+ return {
111
+ type,
112
+ attrs: cleanedAttrs,
113
+ children: children && children.length > 0 ? children.map(c => this.renderNode(c)) : []
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,110 @@
1
+ ---
2
+ import type { IconNode } from 'vectify'
3
+
4
+ export interface Props {
5
+ size?: number | string
6
+ color?: string
7
+ strokeWidth?: number | string
8
+ class?: string
9
+ title?: string
10
+ 'aria-label'?: string
11
+ 'aria-hidden'?: boolean | 'true' | 'false'
12
+ }
13
+
14
+ const iconNode: IconNode[] = [
15
+ {{{formattedNodes}}}
16
+ ]
17
+
18
+ const {
19
+ size = 24,
20
+ color = 'currentColor',
21
+ strokeWidth = 2,
22
+ class: className,
23
+ title,
24
+ 'aria-label': ariaLabel,
25
+ 'aria-hidden': ariaHidden,
26
+ ...props
27
+ } = Astro.props
28
+
29
+ const keepColors = {{keepColors}}
30
+
31
+ // Determine if icon should be hidden from screen readers
32
+ const shouldHide = ariaHidden !== undefined ? ariaHidden : (!title && !ariaLabel)
33
+
34
+ const mergedClassName = className ? `vectify-icon ${className}` : 'vectify-icon'
35
+
36
+ function renderNode(node: IconNode): any {
37
+ const [type, attrs, children] = node
38
+
39
+ let cleanedAttrs: Record<string, any>
40
+
41
+ if (keepColors) {
42
+ cleanedAttrs = attrs
43
+ } else {
44
+ // Track color attributes to determine icon type
45
+ let hasFill = false
46
+ let hasStroke = false
47
+ let originalStrokeWidth: number | string | undefined
48
+
49
+ Object.entries(attrs).forEach(([key, value]) => {
50
+ if (key === 'fill' && value !== 'none') {
51
+ hasFill = true
52
+ }
53
+ if (key === 'stroke') {
54
+ hasStroke = true
55
+ }
56
+ if (key === 'strokeWidth' || key === 'stroke-width') {
57
+ originalStrokeWidth = value
58
+ }
59
+ })
60
+
61
+ // Keep non-color attributes
62
+ cleanedAttrs = Object.fromEntries(
63
+ Object.entries(attrs).filter(([key]) =>
64
+ !['stroke', 'fill', 'strokeWidth', 'stroke-width'].includes(key)
65
+ )
66
+ )
67
+
68
+ // Apply color based on original attributes
69
+ if (hasFill) {
70
+ cleanedAttrs.fill = color
71
+ } else if (hasStroke) {
72
+ cleanedAttrs.fill = 'none'
73
+ cleanedAttrs.stroke = color
74
+ cleanedAttrs['stroke-width'] = originalStrokeWidth ?? strokeWidth
75
+ cleanedAttrs['stroke-linecap'] = 'round'
76
+ cleanedAttrs['stroke-linejoin'] = 'round'
77
+ }
78
+ }
79
+
80
+ return {
81
+ type,
82
+ attrs: cleanedAttrs,
83
+ children: children && children.length > 0 ? children.map(renderNode) : []
84
+ }
85
+ }
86
+
87
+ const processedNodes = iconNode.map(renderNode)
88
+ ---
89
+
90
+ <svg
91
+ width={size}
92
+ height={size}
93
+ viewBox="0 0 24 24"
94
+ aria-hidden={shouldHide}
95
+ aria-label={ariaLabel}
96
+ role={title || ariaLabel ? 'img' : undefined}
97
+ {...props}
98
+ class={mergedClassName}
99
+ >
100
+ {title && <title>{title}</title>}
101
+ {processedNodes.map((node) => {
102
+ const Element = node.type as any
103
+ return <Element {...node.attrs}>
104
+ {node.children && node.children.length > 0 && node.children.map((child: any) => {
105
+ const ChildElement = child.type as any
106
+ return <ChildElement {...child.attrs} />
107
+ })}
108
+ </Element>
109
+ })}
110
+ </svg>
@@ -0,0 +1,111 @@
1
+ ---
2
+ import type { IconNode } from 'vectify'
3
+
4
+ export interface Props {
5
+ size?: number | string
6
+ color?: string
7
+ strokeWidth?: number | string
8
+ class?: string
9
+ title?: string
10
+ 'aria-label'?: string
11
+ 'aria-hidden'?: boolean | 'true' | 'false'
12
+ }
13
+
14
+ interface IconBaseProps extends Props {
15
+ iconNode: IconNode[]
16
+ keepColors?: boolean
17
+ }
18
+
19
+ const {
20
+ size = 24,
21
+ color = 'currentColor',
22
+ strokeWidth = 2,
23
+ class: className,
24
+ title,
25
+ 'aria-label': ariaLabel,
26
+ 'aria-hidden': ariaHidden,
27
+ iconNode,
28
+ keepColors = false,
29
+ ...props
30
+ } = Astro.props as IconBaseProps
31
+
32
+ // Determine if icon should be hidden from screen readers
33
+ const shouldHide = ariaHidden !== undefined ? ariaHidden : (!title && !ariaLabel)
34
+
35
+ const mergedClassName = className ? `vectify-icon ${className}` : 'vectify-icon'
36
+
37
+ function renderNode(node: IconNode): any {
38
+ const [type, attrs, children] = node
39
+
40
+ let cleanedAttrs: Record<string, any>
41
+
42
+ if (keepColors) {
43
+ cleanedAttrs = attrs
44
+ } else {
45
+ // Track color attributes to determine icon type
46
+ let hasFill = false
47
+ let hasStroke = false
48
+ let originalStrokeWidth: number | string | undefined
49
+
50
+ Object.entries(attrs).forEach(([key, value]) => {
51
+ if (key === 'fill' && value !== 'none') {
52
+ hasFill = true
53
+ }
54
+ if (key === 'stroke') {
55
+ hasStroke = true
56
+ }
57
+ if (key === 'strokeWidth' || key === 'stroke-width') {
58
+ originalStrokeWidth = value
59
+ }
60
+ })
61
+
62
+ // Keep non-color attributes
63
+ cleanedAttrs = Object.fromEntries(
64
+ Object.entries(attrs).filter(([key]) =>
65
+ !['stroke', 'fill', 'strokeWidth', 'stroke-width'].includes(key)
66
+ )
67
+ )
68
+
69
+ // Apply color based on original attributes
70
+ if (hasFill) {
71
+ cleanedAttrs.fill = color
72
+ } else if (hasStroke) {
73
+ cleanedAttrs.fill = 'none'
74
+ cleanedAttrs.stroke = color
75
+ cleanedAttrs['stroke-width'] = originalStrokeWidth ?? strokeWidth
76
+ cleanedAttrs['stroke-linecap'] = 'round'
77
+ cleanedAttrs['stroke-linejoin'] = 'round'
78
+ }
79
+ }
80
+
81
+ return {
82
+ type,
83
+ attrs: cleanedAttrs,
84
+ children: children && children.length > 0 ? children.map(renderNode) : []
85
+ }
86
+ }
87
+
88
+ const processedNodes = iconNode.map(renderNode)
89
+ ---
90
+
91
+ <svg
92
+ width={size}
93
+ height={size}
94
+ viewBox="0 0 24 24"
95
+ aria-hidden={shouldHide}
96
+ aria-label={ariaLabel}
97
+ role={title || ariaLabel ? 'img' : undefined}
98
+ {...props}
99
+ class={mergedClassName}
100
+ >
101
+ {title && <title>{title}</title>}
102
+ {processedNodes.map((node) => {
103
+ const Element = node.type as any
104
+ return <Element {...node.attrs}>
105
+ {node.children && node.children.length > 0 && node.children.map((child: any) => {
106
+ const ChildElement = child.type as any
107
+ return <ChildElement {...child.attrs} />
108
+ })}
109
+ </Element>
110
+ })}
111
+ </svg>
@@ -0,0 +1,12 @@
1
+ import { customElement } from 'lit/decorators.js'
2
+ import { IconBase } from './createIcon'
3
+
4
+ export const iconNode = [
5
+ {{{formattedNodes}}}
6
+ ]
7
+
8
+ @customElement('{{componentName}}')
9
+ export class {{componentName}} extends IconBase {
10
+ iconNode = iconNode
11
+ keepColors = {{keepColors}}
12
+ }
@@ -0,0 +1,19 @@
1
+ import { customElement } from 'lit/decorators.js'
2
+ import { IconBase } from './createIcon'
3
+ import type { IconNode } from 'vectify'
4
+
5
+ export const iconNode: IconNode[] = [
6
+ {{{formattedNodes}}}
7
+ ]
8
+
9
+ @customElement('{{componentName}}')
10
+ export class {{componentName}} extends IconBase {
11
+ iconNode = iconNode
12
+ keepColors = {{keepColors}}
13
+ }
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ '{{componentName}}': {{componentName}}
18
+ }
19
+ }
@@ -0,0 +1,98 @@
1
+ import { LitElement, html, css, svg } from 'lit'
2
+ import { property } from 'lit/decorators.js'
3
+
4
+ export class IconBase extends LitElement {
5
+ static properties = {
6
+ size: { type: Number },
7
+ color: { type: String },
8
+ strokeWidth: { type: Number },
9
+ title: { type: String },
10
+ ariaLabel: { type: String },
11
+ ariaHidden: { type: Boolean }
12
+ }
13
+
14
+ constructor() {
15
+ super()
16
+ this.size = 24
17
+ this.color = 'currentColor'
18
+ this.strokeWidth = 2
19
+ this.title = ''
20
+ this.ariaLabel = ''
21
+ this.ariaHidden = false
22
+ }
23
+
24
+ static styles = css`
25
+ :host {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ }
30
+ `
31
+
32
+ renderNode(node) {
33
+ const [type, attrs, children] = node
34
+ let cleanedAttrs
35
+
36
+ if (this.keepColors) {
37
+ cleanedAttrs = attrs
38
+ } else {
39
+ // Track color attributes
40
+ let hasFill = false
41
+ let hasStroke = false
42
+ let originalStrokeWidth
43
+
44
+ if (attrs.fill && attrs.fill !== 'none') {
45
+ hasFill = true
46
+ }
47
+ if (attrs.stroke) {
48
+ hasStroke = true
49
+ }
50
+ if (attrs.strokeWidth || attrs['stroke-width']) {
51
+ originalStrokeWidth = attrs.strokeWidth || attrs['stroke-width']
52
+ }
53
+
54
+ // Keep non-color attributes
55
+ cleanedAttrs = Object.fromEntries(
56
+ Object.entries(attrs).filter(([key]) =>
57
+ !['stroke', 'fill', 'strokeWidth', 'stroke-width'].includes(key)
58
+ )
59
+ )
60
+
61
+ // Apply color
62
+ if (hasFill) {
63
+ cleanedAttrs.fill = this.color
64
+ } else if (hasStroke) {
65
+ cleanedAttrs.fill = 'none'
66
+ cleanedAttrs.stroke = this.color
67
+ cleanedAttrs['stroke-width'] = originalStrokeWidth ?? this.strokeWidth
68
+ cleanedAttrs['stroke-linecap'] = 'round'
69
+ cleanedAttrs['stroke-linejoin'] = 'round'
70
+ }
71
+ }
72
+
73
+ const childNodes = children && children.length > 0
74
+ ? children.map(child => this.renderNode(child))
75
+ : []
76
+
77
+ return svg`<${type} ...${cleanedAttrs}>${childNodes}</${type}>`
78
+ }
79
+
80
+ render() {
81
+ const shouldHide = this.ariaHidden !== undefined ? this.ariaHidden : (!this.title && !this.ariaLabel)
82
+
83
+ return html`
84
+ <svg
85
+ width="${this.size}"
86
+ height="${this.size}"
87
+ viewBox="0 0 24 24"
88
+ class="vectify-icon"
89
+ aria-hidden="${shouldHide}"
90
+ aria-label="${this.ariaLabel || undefined}"
91
+ role="${this.title || this.ariaLabel ? 'img' : undefined}"
92
+ >
93
+ ${this.title ? svg`<title>${this.title}</title>` : ''}
94
+ ${this.iconNode.map(node => this.renderNode(node))}
95
+ </svg>
96
+ `
97
+ }
98
+ }