@studiocms/ui 0.4.2 → 0.4.4
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/dist/components/Dropdown/Dropdown.astro +11 -8
- package/dist/components/Dropdown/dropdown.css +9 -4
- package/dist/components/Icon/Icon.astro +5 -31
- package/dist/components/Icon/IconBase.astro +77 -0
- package/dist/index.js +2 -0
- package/dist/utils/iconifyUtils.d.ts +134 -0
- package/dist/utils/iconifyUtils.js +327 -0
- package/package.json +2 -2
|
@@ -102,14 +102,17 @@ const {
|
|
|
102
102
|
role="option"
|
|
103
103
|
aria-selected="false"
|
|
104
104
|
>
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
{href ? (
|
|
106
|
+
<a href={href} class="sui-dropdown-link sui-dropdown-line-container">
|
|
107
|
+
{icon && (<Icon width={24} height={24} name={icon} />)}
|
|
108
|
+
<span>{label}</span>
|
|
109
|
+
</a>
|
|
110
|
+
) : (
|
|
111
|
+
<div class="sui-dropdown-line-container">
|
|
112
|
+
{icon && (<Icon width={24} height={24} name={icon} />)}
|
|
113
|
+
<span>{label}</span>
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
113
116
|
</li>
|
|
114
117
|
))}
|
|
115
118
|
</ul>
|
|
@@ -100,18 +100,24 @@
|
|
|
100
100
|
transform-origin: bottom right;
|
|
101
101
|
}
|
|
102
102
|
.sui-dropdown-option {
|
|
103
|
-
padding: .5rem .75rem;
|
|
104
103
|
cursor: pointer;
|
|
105
104
|
font-size: .975em;
|
|
106
105
|
transition: all .15s ease;
|
|
107
106
|
display: flex;
|
|
108
107
|
flex-direction: row;
|
|
109
|
-
gap: .5rem;
|
|
110
108
|
align-items: center;
|
|
111
109
|
width: 100%;
|
|
112
110
|
white-space: normal;
|
|
113
111
|
user-select: none;
|
|
114
112
|
}
|
|
113
|
+
.sui-dropdown-line-container {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: row;
|
|
116
|
+
align-items: center;
|
|
117
|
+
padding: .5rem .75rem;
|
|
118
|
+
width: 100%;
|
|
119
|
+
gap: .5rem;
|
|
120
|
+
}
|
|
115
121
|
.sui-dropdown-option:hover,
|
|
116
122
|
.sui-dropdown-option:focus,
|
|
117
123
|
.sui-dropdown-option.focused {
|
|
@@ -121,7 +127,6 @@
|
|
|
121
127
|
padding: 0;
|
|
122
128
|
}
|
|
123
129
|
.sui-dropdown-link {
|
|
124
|
-
padding: .5rem .75rem;
|
|
125
130
|
width: 100%;
|
|
126
131
|
text-decoration: none;
|
|
127
132
|
color: hsl(var(--text-normal));
|
|
@@ -175,6 +180,6 @@
|
|
|
175
180
|
.sui-dropdown-option.end {
|
|
176
181
|
justify-content: space-between;
|
|
177
182
|
}
|
|
178
|
-
.sui-dropdown-option.has-icon {
|
|
183
|
+
.sui-dropdown-option.has-icon .sui-dropdown-line-container {
|
|
179
184
|
padding-left: .5rem;
|
|
180
185
|
}
|
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { icons } from '@iconify-json/heroicons';
|
|
3
|
-
import { getIconData, iconToSVG, replaceIDs } from '@iconify/utils';
|
|
4
3
|
import type { HTMLAttributes } from 'astro/types';
|
|
4
|
+
import IconBase from './IconBase.astro';
|
|
5
5
|
import { type HeroIconName } from './iconType.js';
|
|
6
6
|
|
|
7
|
-
interface
|
|
8
|
-
// biome-ignore lint/suspicious/noExplicitAny: Allow any string index
|
|
9
|
-
[key: string]: any;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Props extends HTMLAttributes<'svg'> {
|
|
7
|
+
interface Props extends Omit<HTMLAttributes<'svg'>, 'name'> {
|
|
13
8
|
name: HeroIconName;
|
|
9
|
+
height?: number;
|
|
10
|
+
width?: number;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
const { name, ...props } = Astro.props;
|
|
17
|
-
const attributes = props as SVGAttributes;
|
|
18
|
-
const iconData = getIconData(icons, name);
|
|
19
|
-
|
|
20
|
-
if (!iconData) {
|
|
21
|
-
throw new Error(`Icon "${name}" is missing`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const renderData = iconToSVG(iconData, {
|
|
25
|
-
height: attributes.height || 24,
|
|
26
|
-
width: attributes.width || 24,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const body = replaceIDs(renderData.body);
|
|
30
|
-
|
|
31
|
-
let renderAttribsHTML =
|
|
32
|
-
body.indexOf('xlink:') === -1 ? '' : ' xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
33
|
-
|
|
34
|
-
for (const attr in attributes) {
|
|
35
|
-
renderAttribsHTML += ` ${attr}="${attributes[attr]}"`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg"${renderAttribsHTML}>${body}</svg>`;
|
|
39
14
|
---
|
|
40
|
-
|
|
41
|
-
<Fragment set:html={svg} />
|
|
15
|
+
<IconBase iconCollection={icons} {name} {...props} />
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { IconifyJSON } from '@iconify/types';
|
|
3
|
+
import { AstroError } from 'astro/errors';
|
|
4
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
5
|
+
import { getIconData, iconToSVG, replaceIDs } from '../../utils/iconifyUtils.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends HTMLAttributes<'svg'> {
|
|
8
|
+
/**
|
|
9
|
+
* Collection of icons
|
|
10
|
+
*
|
|
11
|
+
* Must be an `IconifyJSON` object from an Iconify JSON collection
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* ---
|
|
16
|
+
* import { icons } from '@iconify-json/heroicons';
|
|
17
|
+
* import type { HTMLAttributes } from 'astro/types';
|
|
18
|
+
* import { IconBase } from 'studiocms:ui/components';
|
|
19
|
+
*
|
|
20
|
+
* interface Props extends Omit<HTMLAttributes<'svg'>, 'name'> {
|
|
21
|
+
* name: keyof typeof icons;
|
|
22
|
+
* height?: number;
|
|
23
|
+
* width?: number;
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* const { name, ...props } = Astro.props;
|
|
27
|
+
* ---
|
|
28
|
+
* <IconBase iconCollection={icons} {name} {...props} />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
iconCollection: IconifyJSON;
|
|
32
|
+
/**
|
|
33
|
+
* Name of the icon from the collection
|
|
34
|
+
*/
|
|
35
|
+
name: string;
|
|
36
|
+
/**
|
|
37
|
+
* Height of the icon in pixels
|
|
38
|
+
*/
|
|
39
|
+
height?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Width of the icon in pixels
|
|
42
|
+
*/
|
|
43
|
+
width?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PropsAttributes extends Props {
|
|
47
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow any string index
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { iconCollection, name, height = 24, width = 24, ...props } = Astro.props;
|
|
52
|
+
|
|
53
|
+
const attributes = props as PropsAttributes;
|
|
54
|
+
|
|
55
|
+
const iconData = getIconData(iconCollection, name);
|
|
56
|
+
|
|
57
|
+
if (!iconData) {
|
|
58
|
+
throw new AstroError(`Icon "${name}" is missing in collection`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const renderData = iconToSVG(iconData, {
|
|
62
|
+
height: height,
|
|
63
|
+
width: width,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const body = replaceIDs(renderData.body);
|
|
67
|
+
|
|
68
|
+
let renderAttribsHTML =
|
|
69
|
+
body.indexOf('xlink:') === -1 ? '' : ' xmlns:xlink="http://www.w3.org/1999/xlink';
|
|
70
|
+
|
|
71
|
+
for (const attr in attributes) {
|
|
72
|
+
renderAttribsHTML += ` ${attr}="${attributes[attr]}"`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const svg = `<svg style="min-width: ${height}px" xmlns="http://www.w3.org/2000/svg"${renderAttribsHTML}>${body}</svg>`;
|
|
76
|
+
---
|
|
77
|
+
<Fragment set:html={svg} />
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,7 @@ function integration(options = {}) {
|
|
|
65
65
|
export { default as Group } from '${resolve("./components/Group/Group.astro")}';
|
|
66
66
|
export { default as Badge } from '${resolve("./components/Badge/Badge.astro")}';
|
|
67
67
|
export { default as Icon } from '${resolve("./components/Icon/Icon.astro")}';
|
|
68
|
+
export { default as IconBase } from '${resolve("./components/Icon/IconBase.astro")}';
|
|
68
69
|
|
|
69
70
|
export { ProgressHelper } from '${resolve("./components/Progress/helper.js")}';
|
|
70
71
|
export { SingleSidebarHelper, DoubleSidebarHelper } from '${resolve("./components/Sidebar/helpers.js")}';
|
|
@@ -135,6 +136,7 @@ function integration(options = {}) {
|
|
|
135
136
|
export const Group: typeof import('${resolve("./components/Group/Group.astro")}').default;
|
|
136
137
|
export const Badge: typeof import('${resolve("./components/Badge/Badge.astro")}').default;
|
|
137
138
|
export const Icon: typeof import('${resolve("./components/Icon/Icon.astro")}').default;
|
|
139
|
+
export const IconBase: typeof import('${resolve("./components/Icon/IconBase.astro")}').default;
|
|
138
140
|
export const toast: typeof import('${resolve("./components/Toast/toast.js")}').toast;
|
|
139
141
|
export type HeroIconName = import('${resolve("./components/Icon/iconType.js")}').HeroIconName;
|
|
140
142
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ExtendedIconifyIcon, IconifyDimenisons, IconifyIcon, IconifyJSON, IconifyOptional, IconifyTransformations } from '@iconify/types';
|
|
2
|
+
export type FullIconifyIcon = Required<IconifyIcon>;
|
|
3
|
+
/**
|
|
4
|
+
* SVG viewBox: x, y, width, height
|
|
5
|
+
*/
|
|
6
|
+
export type SVGViewBox = [x: number, y: number, width: number, height: number];
|
|
7
|
+
/**
|
|
8
|
+
* Get viewBox from string
|
|
9
|
+
*/
|
|
10
|
+
export declare function getSVGViewBox(value: string): SVGViewBox | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Extract definitions from SVG
|
|
13
|
+
*
|
|
14
|
+
* Can be used with other tags, but name kept for backwards compatibility.
|
|
15
|
+
* Should be used only with tags that cannot be nested, such as masks, clip paths, etc.
|
|
16
|
+
*/
|
|
17
|
+
interface SplitSVGDefsResult {
|
|
18
|
+
defs: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function splitSVGDefs(content: string, tag?: string): SplitSVGDefsResult;
|
|
22
|
+
/**
|
|
23
|
+
* Merge defs and content
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeDefsAndContent(defs: string, content: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Wrap SVG content, without wrapping definitions
|
|
28
|
+
*/
|
|
29
|
+
export declare function wrapSVGContent(body: string, start: string, end: string): string;
|
|
30
|
+
export type PartialExtendedIconifyIcon = Partial<ExtendedIconifyIcon>;
|
|
31
|
+
type IconifyIconExtraProps = Omit<ExtendedIconifyIcon, keyof IconifyIcon>;
|
|
32
|
+
export type FullExtendedIconifyIcon = FullIconifyIcon & IconifyIconExtraProps;
|
|
33
|
+
/**
|
|
34
|
+
* Calculate second dimension when only 1 dimension is set
|
|
35
|
+
*/
|
|
36
|
+
export declare function calculateSize(size: string, ratio: number, precision?: number): string;
|
|
37
|
+
export declare function calculateSize(size: number, ratio: number, precision?: number): number;
|
|
38
|
+
export declare function calculateSize(size: string | number, ratio: number, precision?: number): string | number;
|
|
39
|
+
/**
|
|
40
|
+
* Default values for dimensions
|
|
41
|
+
*/
|
|
42
|
+
export declare const defaultIconDimensions: Required<IconifyDimenisons>;
|
|
43
|
+
/**
|
|
44
|
+
* Default values for transformations
|
|
45
|
+
*/
|
|
46
|
+
export declare const defaultIconTransformations: Required<IconifyTransformations>;
|
|
47
|
+
/**
|
|
48
|
+
* Default values for all optional IconifyIcon properties
|
|
49
|
+
*/
|
|
50
|
+
export declare const defaultIconProps: Required<IconifyOptional>;
|
|
51
|
+
/**
|
|
52
|
+
* Default values for all properties used in ExtendedIconifyIcon
|
|
53
|
+
*/
|
|
54
|
+
export declare const defaultExtendedIconProps: Required<FullExtendedIconifyIcon>;
|
|
55
|
+
/**
|
|
56
|
+
* Merge transformations
|
|
57
|
+
*/
|
|
58
|
+
export declare function mergeIconTransformations<T extends IconifyTransformations>(obj1: T, obj2: IconifyTransformations): T;
|
|
59
|
+
/**
|
|
60
|
+
* Icon size
|
|
61
|
+
*/
|
|
62
|
+
export type IconifyIconSize = null | string | number;
|
|
63
|
+
/**
|
|
64
|
+
* Dimensions
|
|
65
|
+
*/
|
|
66
|
+
export interface IconifyIconSizeCustomisations {
|
|
67
|
+
width?: IconifyIconSize;
|
|
68
|
+
height?: IconifyIconSize;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Icon customisations
|
|
72
|
+
*/
|
|
73
|
+
export interface IconifyIconCustomisations extends IconifyTransformations, IconifyIconSizeCustomisations {
|
|
74
|
+
}
|
|
75
|
+
export type FullIconCustomisations = Required<IconifyIconCustomisations>;
|
|
76
|
+
/**
|
|
77
|
+
* Default icon customisations values
|
|
78
|
+
*/
|
|
79
|
+
export declare const defaultIconSizeCustomisations: Required<IconifyIconSizeCustomisations>;
|
|
80
|
+
export declare const defaultIconCustomisations: FullIconCustomisations;
|
|
81
|
+
/**
|
|
82
|
+
* Merge icon and alias
|
|
83
|
+
*
|
|
84
|
+
* Can also be used to merge default values and icon
|
|
85
|
+
*/
|
|
86
|
+
export declare function mergeIconData<T extends PartialExtendedIconifyIcon>(parent: T, child: PartialExtendedIconifyIcon): T;
|
|
87
|
+
export type ParentIconsList = string[];
|
|
88
|
+
export type ParentIconsTree = Record<string, ParentIconsList | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve icon set icons
|
|
91
|
+
*
|
|
92
|
+
* Returns parent icon for each icon
|
|
93
|
+
*/
|
|
94
|
+
export declare function getIconsTree(data: IconifyJSON, names?: string[]): ParentIconsTree;
|
|
95
|
+
/**
|
|
96
|
+
* Get icon data, using prepared aliases tree
|
|
97
|
+
*/
|
|
98
|
+
export declare function internalGetIconData(data: IconifyJSON, name: string, tree: string[]): ExtendedIconifyIcon;
|
|
99
|
+
/**
|
|
100
|
+
* Get data for icon
|
|
101
|
+
*/
|
|
102
|
+
export declare function getIconData(data: IconifyJSON, name: string): ExtendedIconifyIcon | null;
|
|
103
|
+
/**
|
|
104
|
+
* Interface for getSVGData() result
|
|
105
|
+
*/
|
|
106
|
+
export interface IconifyIconBuildResult {
|
|
107
|
+
attributes: {
|
|
108
|
+
width?: string;
|
|
109
|
+
height?: string;
|
|
110
|
+
viewBox: string;
|
|
111
|
+
};
|
|
112
|
+
viewBox: SVGViewBox;
|
|
113
|
+
body: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if value should be unset. Allows multiple keywords
|
|
117
|
+
*/
|
|
118
|
+
export declare const isUnsetKeyword: (value: unknown) => value is "none" | "unset" | "undefined";
|
|
119
|
+
/**
|
|
120
|
+
* Replace IDs in SVG output with unique IDs
|
|
121
|
+
*/
|
|
122
|
+
export declare function replaceIDs(body: string, prefix?: string | ((id: string) => string)): string;
|
|
123
|
+
/**
|
|
124
|
+
* Get SVG attributes and content from icon + customisations
|
|
125
|
+
*
|
|
126
|
+
* Does not generate style to make it compatible with frameworks that use objects for style, such as React.
|
|
127
|
+
* Instead, it generates 'inline' value. If true, rendering engine should add verticalAlign: -0.125em to icon.
|
|
128
|
+
*
|
|
129
|
+
* Customisations should be normalised by platform specific parser.
|
|
130
|
+
* Result should be converted to <svg> by platform specific parser.
|
|
131
|
+
* Use replaceIDs to generate unique IDs for body.
|
|
132
|
+
*/
|
|
133
|
+
export declare function iconToSVG(icon: IconifyIcon, customisations?: IconifyIconCustomisations): IconifyIconBuildResult;
|
|
134
|
+
export {};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
function getSVGViewBox(value) {
|
|
2
|
+
const result = value.trim().split(/\s+/).map(Number);
|
|
3
|
+
if (result.length === 4 && result.reduce((prev, value2) => prev && !Number.isNaN(value2), true)) {
|
|
4
|
+
return result;
|
|
5
|
+
}
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
function splitSVGDefs(content, tag = "defs") {
|
|
9
|
+
let defs = "";
|
|
10
|
+
const index = content.indexOf(`<${tag}`);
|
|
11
|
+
while (index >= 0) {
|
|
12
|
+
const start = content.indexOf(">", index);
|
|
13
|
+
const end = content.indexOf(`</${tag}`);
|
|
14
|
+
if (start === -1 || end === -1) {
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
const endEnd = content.indexOf(">", end);
|
|
18
|
+
if (endEnd === -1) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
defs += content.slice(start + 1, end).trim();
|
|
22
|
+
content = content.slice(0, index).trim() + content.slice(endEnd + 1);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
defs,
|
|
26
|
+
content
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function mergeDefsAndContent(defs, content) {
|
|
30
|
+
return defs ? `<defs>${defs}</defs>${content}` : content;
|
|
31
|
+
}
|
|
32
|
+
function wrapSVGContent(body, start, end) {
|
|
33
|
+
const split = splitSVGDefs(body);
|
|
34
|
+
return mergeDefsAndContent(split.defs, start + split.content + end);
|
|
35
|
+
}
|
|
36
|
+
const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;
|
|
37
|
+
const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;
|
|
38
|
+
function calculateSize(size, ratio, precision) {
|
|
39
|
+
if (ratio === 1) {
|
|
40
|
+
return size;
|
|
41
|
+
}
|
|
42
|
+
precision = precision || 100;
|
|
43
|
+
if (typeof size === "number") {
|
|
44
|
+
return Math.ceil(size * ratio * precision) / precision;
|
|
45
|
+
}
|
|
46
|
+
if (typeof size !== "string") {
|
|
47
|
+
return size;
|
|
48
|
+
}
|
|
49
|
+
const oldParts = size.split(unitsSplit);
|
|
50
|
+
if (oldParts === null || !oldParts.length) {
|
|
51
|
+
return size;
|
|
52
|
+
}
|
|
53
|
+
const newParts = [];
|
|
54
|
+
let code = oldParts.shift();
|
|
55
|
+
let isNumber = unitsTest.test(code);
|
|
56
|
+
while (true) {
|
|
57
|
+
if (isNumber) {
|
|
58
|
+
const num = Number.parseFloat(code);
|
|
59
|
+
if (Number.isNaN(num)) {
|
|
60
|
+
newParts.push(code);
|
|
61
|
+
} else {
|
|
62
|
+
newParts.push(Math.ceil(num * ratio * precision) / precision);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
newParts.push(code);
|
|
66
|
+
}
|
|
67
|
+
code = oldParts.shift();
|
|
68
|
+
if (code === void 0) {
|
|
69
|
+
return newParts.join("");
|
|
70
|
+
}
|
|
71
|
+
isNumber = !isNumber;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const defaultIconDimensions = Object.freeze({
|
|
75
|
+
left: 0,
|
|
76
|
+
top: 0,
|
|
77
|
+
width: 16,
|
|
78
|
+
height: 16
|
|
79
|
+
});
|
|
80
|
+
const defaultIconTransformations = Object.freeze({
|
|
81
|
+
rotate: 0,
|
|
82
|
+
vFlip: false,
|
|
83
|
+
hFlip: false
|
|
84
|
+
});
|
|
85
|
+
const defaultIconProps = Object.freeze({
|
|
86
|
+
...defaultIconDimensions,
|
|
87
|
+
...defaultIconTransformations
|
|
88
|
+
});
|
|
89
|
+
const defaultExtendedIconProps = Object.freeze({
|
|
90
|
+
...defaultIconProps,
|
|
91
|
+
body: "",
|
|
92
|
+
hidden: false
|
|
93
|
+
});
|
|
94
|
+
function mergeIconTransformations(obj1, obj2) {
|
|
95
|
+
const result = {};
|
|
96
|
+
if (!obj1.hFlip !== !obj2.hFlip) {
|
|
97
|
+
result.hFlip = true;
|
|
98
|
+
}
|
|
99
|
+
if (!obj1.vFlip !== !obj2.vFlip) {
|
|
100
|
+
result.vFlip = true;
|
|
101
|
+
}
|
|
102
|
+
const rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4;
|
|
103
|
+
if (rotate) {
|
|
104
|
+
result.rotate = rotate;
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
const defaultIconSizeCustomisations = Object.freeze(
|
|
109
|
+
{
|
|
110
|
+
width: null,
|
|
111
|
+
height: null
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
const defaultIconCustomisations = Object.freeze({
|
|
115
|
+
// Dimensions
|
|
116
|
+
...defaultIconSizeCustomisations,
|
|
117
|
+
// Transformations
|
|
118
|
+
...defaultIconTransformations
|
|
119
|
+
});
|
|
120
|
+
function mergeIconData(parent, child) {
|
|
121
|
+
const result = mergeIconTransformations(parent, child);
|
|
122
|
+
for (const key in defaultExtendedIconProps) {
|
|
123
|
+
if (key in defaultIconTransformations) {
|
|
124
|
+
if (key in parent && !(key in result)) {
|
|
125
|
+
result[key] = defaultIconTransformations[key];
|
|
126
|
+
}
|
|
127
|
+
} else if (key in child) {
|
|
128
|
+
result[key] = child[key];
|
|
129
|
+
} else if (key in parent) {
|
|
130
|
+
result[key] = parent[key];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function getIconsTree(data, names) {
|
|
136
|
+
const icons = data.icons;
|
|
137
|
+
const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
|
|
138
|
+
const resolved = /* @__PURE__ */ Object.create(null);
|
|
139
|
+
function resolve(name) {
|
|
140
|
+
if (icons[name]) {
|
|
141
|
+
return resolved[name] = [];
|
|
142
|
+
}
|
|
143
|
+
if (!(name in resolved)) {
|
|
144
|
+
resolved[name] = null;
|
|
145
|
+
const parent = aliases[name]?.parent;
|
|
146
|
+
const value = parent && resolve(parent);
|
|
147
|
+
if (value) {
|
|
148
|
+
resolved[name] = [parent].concat(value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return resolved[name] || null;
|
|
152
|
+
}
|
|
153
|
+
(names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve);
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
156
|
+
function internalGetIconData(data, name, tree) {
|
|
157
|
+
const icons = data.icons;
|
|
158
|
+
const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
|
|
159
|
+
let currentProps = {};
|
|
160
|
+
function parse(name2) {
|
|
161
|
+
currentProps = mergeIconData(
|
|
162
|
+
icons[name2] || aliases[name2],
|
|
163
|
+
currentProps
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
parse(name);
|
|
167
|
+
tree.forEach(parse);
|
|
168
|
+
return mergeIconData(data, currentProps);
|
|
169
|
+
}
|
|
170
|
+
function getIconData(data, name) {
|
|
171
|
+
if (data.icons[name]) {
|
|
172
|
+
return internalGetIconData(data, name, []);
|
|
173
|
+
}
|
|
174
|
+
const tree = getIconsTree(data, [name])[name];
|
|
175
|
+
return tree ? internalGetIconData(data, name, tree) : null;
|
|
176
|
+
}
|
|
177
|
+
const isUnsetKeyword = (value) => value === "unset" || value === "undefined" || value === "none";
|
|
178
|
+
const regex = /\sid="(\S+)"/g;
|
|
179
|
+
const randomPrefix = `IconifyId${Date.now().toString(16)}${(Math.random() * 16777216 | 0).toString(16)}`;
|
|
180
|
+
let counter = 0;
|
|
181
|
+
function replaceIDs(body, prefix = randomPrefix) {
|
|
182
|
+
const ids = [];
|
|
183
|
+
let match;
|
|
184
|
+
while (match = regex.exec(body)) {
|
|
185
|
+
ids.push(match[1]);
|
|
186
|
+
}
|
|
187
|
+
if (!ids.length) {
|
|
188
|
+
return body;
|
|
189
|
+
}
|
|
190
|
+
const suffix = `suffix${(Math.random() * 16777216 | Date.now()).toString(16)}`;
|
|
191
|
+
ids.forEach((id) => {
|
|
192
|
+
const newID = typeof prefix === "function" ? prefix(id) : prefix + (counter++).toString();
|
|
193
|
+
const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
194
|
+
body = body.replace(
|
|
195
|
+
// Allowed characters before id: [#;"]
|
|
196
|
+
// Allowed characters after id: [)"], .[a-z]
|
|
197
|
+
new RegExp(`([#;"])(${escapedID})([")]|\\.[a-z])`, "g"),
|
|
198
|
+
`$1${newID}${suffix}$3`
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
body = body.replace(new RegExp(suffix, "g"), "");
|
|
202
|
+
return body;
|
|
203
|
+
}
|
|
204
|
+
function iconToSVG(icon, customisations) {
|
|
205
|
+
const fullIcon = {
|
|
206
|
+
...defaultIconProps,
|
|
207
|
+
...icon
|
|
208
|
+
};
|
|
209
|
+
const fullCustomisations = {
|
|
210
|
+
...defaultIconCustomisations,
|
|
211
|
+
...customisations
|
|
212
|
+
};
|
|
213
|
+
const box = {
|
|
214
|
+
left: fullIcon.left,
|
|
215
|
+
top: fullIcon.top,
|
|
216
|
+
width: fullIcon.width,
|
|
217
|
+
height: fullIcon.height
|
|
218
|
+
};
|
|
219
|
+
let body = fullIcon.body;
|
|
220
|
+
[fullIcon, fullCustomisations].forEach((props) => {
|
|
221
|
+
const transformations = [];
|
|
222
|
+
const hFlip = props.hFlip;
|
|
223
|
+
const vFlip = props.vFlip;
|
|
224
|
+
let rotation = props.rotate;
|
|
225
|
+
if (hFlip) {
|
|
226
|
+
if (vFlip) {
|
|
227
|
+
rotation += 2;
|
|
228
|
+
} else {
|
|
229
|
+
transformations.push(
|
|
230
|
+
`translate(${(box.width + box.left).toString()} ${(0 - box.top).toString()})`
|
|
231
|
+
);
|
|
232
|
+
transformations.push("scale(-1 1)");
|
|
233
|
+
box.top = box.left = 0;
|
|
234
|
+
}
|
|
235
|
+
} else if (vFlip) {
|
|
236
|
+
transformations.push(
|
|
237
|
+
`translate(${(0 - box.left).toString()} ${(box.height + box.top).toString()})`
|
|
238
|
+
);
|
|
239
|
+
transformations.push("scale(1 -1)");
|
|
240
|
+
box.top = box.left = 0;
|
|
241
|
+
}
|
|
242
|
+
let tempValue;
|
|
243
|
+
if (rotation < 0) {
|
|
244
|
+
rotation -= Math.floor(rotation / 4) * 4;
|
|
245
|
+
}
|
|
246
|
+
rotation = rotation % 4;
|
|
247
|
+
switch (rotation) {
|
|
248
|
+
case 1:
|
|
249
|
+
tempValue = box.height / 2 + box.top;
|
|
250
|
+
transformations.unshift(`rotate(90 ${tempValue.toString()} ${tempValue.toString()})`);
|
|
251
|
+
break;
|
|
252
|
+
case 2:
|
|
253
|
+
transformations.unshift(
|
|
254
|
+
`rotate(180 ${(box.width / 2 + box.left).toString()} ${(box.height / 2 + box.top).toString()})`
|
|
255
|
+
);
|
|
256
|
+
break;
|
|
257
|
+
case 3:
|
|
258
|
+
tempValue = box.width / 2 + box.left;
|
|
259
|
+
transformations.unshift(`rotate(-90 ${tempValue.toString()} ${tempValue.toString()})`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (rotation % 2 === 1) {
|
|
263
|
+
if (box.left !== box.top) {
|
|
264
|
+
tempValue = box.left;
|
|
265
|
+
box.left = box.top;
|
|
266
|
+
box.top = tempValue;
|
|
267
|
+
}
|
|
268
|
+
if (box.width !== box.height) {
|
|
269
|
+
tempValue = box.width;
|
|
270
|
+
box.width = box.height;
|
|
271
|
+
box.height = tempValue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (transformations.length) {
|
|
275
|
+
body = wrapSVGContent(body, `<g transform="${transformations.join(" ")}">`, "</g>");
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const customisationsWidth = fullCustomisations.width;
|
|
279
|
+
const customisationsHeight = fullCustomisations.height;
|
|
280
|
+
const boxWidth = box.width;
|
|
281
|
+
const boxHeight = box.height;
|
|
282
|
+
let width;
|
|
283
|
+
let height;
|
|
284
|
+
if (customisationsWidth === null) {
|
|
285
|
+
height = customisationsHeight === null ? "1em" : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
|
|
286
|
+
width = calculateSize(height, boxWidth / boxHeight);
|
|
287
|
+
} else {
|
|
288
|
+
width = customisationsWidth === "auto" ? boxWidth : customisationsWidth;
|
|
289
|
+
height = customisationsHeight === null ? calculateSize(width, boxHeight / boxWidth) : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
|
|
290
|
+
}
|
|
291
|
+
const attributes = {};
|
|
292
|
+
const setAttr = (prop, value) => {
|
|
293
|
+
if (!isUnsetKeyword(value)) {
|
|
294
|
+
attributes[prop] = value.toString();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
setAttr("width", width);
|
|
298
|
+
setAttr("height", height);
|
|
299
|
+
const viewBox = [box.left, box.top, boxWidth, boxHeight];
|
|
300
|
+
attributes.viewBox = viewBox.join(" ");
|
|
301
|
+
return {
|
|
302
|
+
attributes,
|
|
303
|
+
viewBox,
|
|
304
|
+
body
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export {
|
|
308
|
+
calculateSize,
|
|
309
|
+
defaultExtendedIconProps,
|
|
310
|
+
defaultIconCustomisations,
|
|
311
|
+
defaultIconDimensions,
|
|
312
|
+
defaultIconProps,
|
|
313
|
+
defaultIconSizeCustomisations,
|
|
314
|
+
defaultIconTransformations,
|
|
315
|
+
getIconData,
|
|
316
|
+
getIconsTree,
|
|
317
|
+
getSVGViewBox,
|
|
318
|
+
iconToSVG,
|
|
319
|
+
internalGetIconData,
|
|
320
|
+
isUnsetKeyword,
|
|
321
|
+
mergeDefsAndContent,
|
|
322
|
+
mergeIconData,
|
|
323
|
+
mergeIconTransformations,
|
|
324
|
+
replaceIDs,
|
|
325
|
+
splitSVGDefs,
|
|
326
|
+
wrapSVGContent
|
|
327
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studiocms/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/heroicons": "^1.2.1",
|
|
57
|
-
"@iconify/
|
|
57
|
+
"@iconify/types": "^2.0.0",
|
|
58
58
|
"astro-transition-event-polyfill": "^1.1.0",
|
|
59
59
|
"pathe": "^1.1.2"
|
|
60
60
|
},
|