update
This commit is contained in:
parent
1e73a9527b
commit
ef369f2955
@ -30,6 +30,7 @@
|
|||||||
"apexcharts": "^4.3.0",
|
"apexcharts": "^4.3.0",
|
||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"ibantools": "^4.5.1",
|
"ibantools": "^4.5.1",
|
||||||
|
"lucide": "^0.488.0",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.52.2",
|
||||||
"pdfjs-dist": "^4.10.38",
|
"pdfjs-dist": "^4.10.38",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -53,6 +53,9 @@ importers:
|
|||||||
ibantools:
|
ibantools:
|
||||||
specifier: ^4.5.1
|
specifier: ^4.5.1
|
||||||
version: 4.5.1
|
version: 4.5.1
|
||||||
|
lucide:
|
||||||
|
specifier: ^0.488.0
|
||||||
|
version: 0.488.0
|
||||||
monaco-editor:
|
monaco-editor:
|
||||||
specifier: ^0.52.2
|
specifier: ^0.52.2
|
||||||
version: 0.52.2
|
version: 0.52.2
|
||||||
@ -3092,6 +3095,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
|
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
|
||||||
engines: {node: '>=16.14'}
|
engines: {node: '>=16.14'}
|
||||||
|
|
||||||
|
lucide@0.488.0:
|
||||||
|
resolution: {integrity: sha512-AcCuN/R9ZRDEH+H47azM5RWbhUTi02+OL4QHeY6kP/MGMSislGWtRDNqj/2EJIBp/qMKpuAU1v5BLAVT0BDE/g==}
|
||||||
|
|
||||||
make-dir@3.1.0:
|
make-dir@3.1.0:
|
||||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8640,6 +8646,8 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@8.0.5: {}
|
lru-cache@8.0.5: {}
|
||||||
|
|
||||||
|
lucide@0.488.0: {}
|
||||||
|
|
||||||
make-dir@3.1.0:
|
make-dir@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
|
@ -1,31 +1,159 @@
|
|||||||
import { html } from '@design.estate/dees-element';
|
import { html } from '@design.estate/dees-element';
|
||||||
|
import { icons, type IconWithPrefix } from './dees-icon.js';
|
||||||
|
import * as lucideIcons from 'lucide';
|
||||||
|
|
||||||
import { faIcons } from './dees-icon.js';
|
export const demoFunc = () => {
|
||||||
|
// Group FontAwesome icons by type
|
||||||
|
const faIcons = Object.keys(icons.fa);
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
// Extract Lucide icons from the lucideIcons object directly
|
||||||
|
// Log the first few keys to understand the structure
|
||||||
|
console.log('First few Lucide keys:', Object.keys(lucideIcons).slice(0, 5));
|
||||||
|
|
||||||
|
// Get all icon functions from lucideIcons (they have PascalCase names)
|
||||||
|
const lucideIconsList = Object.keys(lucideIcons)
|
||||||
|
.filter(key => {
|
||||||
|
// Skip utility functions and focus on icon components (first letter is uppercase)
|
||||||
|
const isUppercaseFirst = key[0] === key[0].toUpperCase() && key[0] !== key[0].toLowerCase();
|
||||||
|
const isFunction = typeof lucideIcons[key] === 'function';
|
||||||
|
const notUtility = !['createElement', 'createIcons', 'default'].includes(key);
|
||||||
|
return isFunction && isUppercaseFirst && notUtility;
|
||||||
|
})
|
||||||
|
.map(pascalName => {
|
||||||
|
// Convert PascalCase to camelCase
|
||||||
|
return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log how many icons we found
|
||||||
|
console.log(`Found ${lucideIconsList.length} Lucide icons`);
|
||||||
|
|
||||||
|
// If we didn't find any, try an alternative approach
|
||||||
|
if (lucideIconsList.length === 0) {
|
||||||
|
console.log('Trying alternative approach to find Lucide icons');
|
||||||
|
|
||||||
|
// Try to get icon names from a known property if available
|
||||||
|
if (lucideIcons.icons) {
|
||||||
|
const iconSource = lucideIcons.icons || {};
|
||||||
|
lucideIconsList.push(...Object.keys(iconSource));
|
||||||
|
console.log(`Found ${lucideIconsList.length} icons via alternative method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the functions in TS scope instead of script tags
|
||||||
|
const searchIcons = (event: InputEvent) => {
|
||||||
|
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase().trim();
|
||||||
|
const containers = document.querySelectorAll('.iconContainer');
|
||||||
|
|
||||||
|
containers.forEach(container => {
|
||||||
|
const iconName = container.getAttribute('data-name');
|
||||||
|
|
||||||
|
// If search term is empty, show all icons
|
||||||
|
if (searchTerm === '') {
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
} else if (iconName && iconName.includes(searchTerm)) {
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
container.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update counts
|
||||||
|
document.querySelectorAll('.section-container').forEach(section => {
|
||||||
|
const visibleIcons = section.querySelectorAll('.iconContainer:not(.hidden)').length;
|
||||||
|
const countElement = section.querySelector('.icon-count');
|
||||||
|
if (countElement) {
|
||||||
|
const totalIconsCount = section.classList.contains('fa-section')
|
||||||
|
? faIcons.length
|
||||||
|
: lucideIconsList.length;
|
||||||
|
|
||||||
|
countElement.textContent = visibleIcons === totalIconsCount
|
||||||
|
? `${totalIconsCount} icons`
|
||||||
|
: `${visibleIcons} of ${totalIconsCount} icons`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyIconName = (iconNameToCopy: string, type: 'fa' | 'lucide') => {
|
||||||
|
// Use the new prefix format
|
||||||
|
const textToCopy = `${type}:${iconNameToCopy}`;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||||
|
// Find the event target
|
||||||
|
const currentEvent = window.event as MouseEvent;
|
||||||
|
const currentTarget = currentEvent.currentTarget as HTMLElement;
|
||||||
|
// Show feedback
|
||||||
|
const tooltip = currentTarget.querySelector('.copy-tooltip');
|
||||||
|
if (tooltip) {
|
||||||
|
tooltip.textContent = 'Copied!';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
tooltip.textContent = 'Click to copy';
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
<style>
|
<style>
|
||||||
.demoContainer {
|
.demoContainer {
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
background: #111111;
|
background: #111111;
|
||||||
padding: 10px; font-size: 30px;
|
padding: 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#iconSearch {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #222;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#iconSearch:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e4002b;
|
||||||
|
}
|
||||||
|
|
||||||
dees-icon {
|
dees-icon {
|
||||||
transition: color 0.02s;
|
transition: all 0.2s ease;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
dees-icon:hover {
|
dees-icon:hover {
|
||||||
color: #e4002b;
|
color: #e4002b;
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconContainer {
|
.iconContainer {
|
||||||
display: block;
|
display: flex;
|
||||||
padding: 16px 16px 0px 16px;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 16px 0px 16px;
|
||||||
border: 1px solid #333333;
|
border: 1px solid #333333;
|
||||||
margin-right: 8px;
|
margin-right: 10px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconContainer:hover {
|
||||||
|
background-color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconName {
|
.iconName {
|
||||||
@ -33,23 +161,132 @@ export const demoFunc = () => html`
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
background: #333333;
|
background: #333333;
|
||||||
padding: 4px 8px;
|
padding: 6px 10px;
|
||||||
padding-bottom: 4px;
|
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
margin-right: -16px;
|
margin-right: -16px;
|
||||||
margin-top: 16px;
|
margin-top: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 120px;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
width: 100%;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #333333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-note {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #e4002b;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #e4002b;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(228, 0, 43, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
font-weight: normal;
|
||||||
|
background: #222;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
top: -30px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconContainer:hover .copy-tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="demoContainer">
|
<div class="demoContainer">
|
||||||
${Object.keys(faIcons).map(
|
<div class="search-container">
|
||||||
(iconName) => html`
|
<input type="text" id="iconSearch" placeholder="Search icons..." @input=${searchIcons}>
|
||||||
<div class="iconContainer">
|
|
||||||
<dees-icon .iconFA=${iconName as any}></dees-icon>
|
|
||||||
<div class="iconName">${iconName}</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
<div class="api-note">
|
||||||
|
New API: Use <code>icon="fa:iconName"</code> or <code>icon="lucide:iconName"</code> instead of <code>iconFA</code>.
|
||||||
|
Click any icon to copy its new format to clipboard.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-container fa-section">
|
||||||
|
<div class="section-title">
|
||||||
|
FontAwesome Icons
|
||||||
|
<span class="icon-count">${faIcons.length} icons</span>
|
||||||
|
</div>
|
||||||
|
<div class="icons-grid">
|
||||||
|
${faIcons.map(
|
||||||
|
(iconName) => {
|
||||||
|
const prefixedName = `fa:${iconName}`;
|
||||||
|
return html`
|
||||||
|
<div class="iconContainer fa-icon" data-name=${iconName.toLowerCase()} @click=${() => copyIconName(iconName, 'fa')}>
|
||||||
|
<dees-icon .icon=${prefixedName as IconWithPrefix} iconSize="24"></dees-icon>
|
||||||
|
<div class="iconName">${iconName}</div>
|
||||||
|
<span class="copy-tooltip">Click to copy</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-container lucide-section">
|
||||||
|
<div class="section-title">
|
||||||
|
Lucide Icons
|
||||||
|
<span class="icon-count">${lucideIconsList.length} icons</span>
|
||||||
|
</div>
|
||||||
|
<div class="icons-grid">
|
||||||
|
${lucideIconsList.map(
|
||||||
|
(iconName) => {
|
||||||
|
const prefixedName = `lucide:${iconName}`;
|
||||||
|
return html`
|
||||||
|
<div class="iconContainer lucide-icon" data-name=${iconName.toLowerCase()} @click=${() => copyIconName(iconName, 'lucide')}>
|
||||||
|
<dees-icon .icon=${prefixedName as IconWithPrefix} iconSize="24"></dees-icon>
|
||||||
|
<div class="iconName">${iconName}</div>
|
||||||
|
<span class="copy-tooltip">Click to copy</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
@ -75,7 +75,12 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { demoFunc } from './dees-icon.demo.js';
|
import { demoFunc } from './dees-icon.demo.js';
|
||||||
|
|
||||||
export const faIcons = {
|
// Import Lucide icons and the createElement function
|
||||||
|
import * as lucideIcons from 'lucide';
|
||||||
|
import { createElement } from 'lucide';
|
||||||
|
|
||||||
|
// Collect FontAwesome icons
|
||||||
|
const faIcons = {
|
||||||
// normal
|
// normal
|
||||||
arrowRight: faArrowRightSolid,
|
arrowRight: faArrowRightSolid,
|
||||||
arrowUpRightFromSquare: faArrowUpRightFromSquareSolid,
|
arrowUpRightFromSquare: faArrowUpRightFromSquareSolid,
|
||||||
@ -136,7 +141,32 @@ export const faIcons = {
|
|||||||
twitter: faTwitter,
|
twitter: faTwitter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIconKey = keyof typeof faIcons;
|
// Create a string literal type for all FA icons
|
||||||
|
type FAIconKey = keyof typeof faIcons;
|
||||||
|
|
||||||
|
// Create union types for the icons with prefixes
|
||||||
|
export type IconWithPrefix = `fa:${FAIconKey}` | `lucide:${string}`;
|
||||||
|
|
||||||
|
// Export only FontAwesome icons directly
|
||||||
|
export const icons = {
|
||||||
|
fa: faIcons
|
||||||
|
};
|
||||||
|
|
||||||
|
// Legacy type for backward compatibility
|
||||||
|
export type TIconKey = FAIconKey | `lucide:${string}`;
|
||||||
|
|
||||||
|
// Use a global static cache for all icons to reduce rendering
|
||||||
|
const iconCache = new Map<string, string>();
|
||||||
|
|
||||||
|
// Clear cache items occasionally to prevent memory leaks
|
||||||
|
const MAX_CACHE_SIZE = 500;
|
||||||
|
function limitCacheSize() {
|
||||||
|
if (iconCache.size > MAX_CACHE_SIZE) {
|
||||||
|
// Remove oldest entries (first 20% of items)
|
||||||
|
const keysToDelete = Array.from(iconCache.keys()).slice(0, MAX_CACHE_SIZE / 5);
|
||||||
|
keysToDelete.forEach(key => iconCache.delete(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -148,31 +178,170 @@ declare global {
|
|||||||
export class DeesIcon extends DeesElement {
|
export class DeesIcon extends DeesElement {
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use the `icon` property instead with format "fa:iconName" or "lucide:iconName"
|
||||||
|
*/
|
||||||
@property({
|
@property({
|
||||||
type: String
|
type: String,
|
||||||
|
converter: {
|
||||||
|
// Convert attribute string to property (for reflected attributes)
|
||||||
|
fromAttribute: (value: string): TIconKey => value as TIconKey,
|
||||||
|
// Convert property to attribute (for reflection)
|
||||||
|
toAttribute: (value: TIconKey): string => value
|
||||||
|
}
|
||||||
})
|
})
|
||||||
public iconFA: keyof typeof faIcons;
|
public iconFA?: TIconKey;
|
||||||
|
|
||||||
@property()
|
/**
|
||||||
|
* The preferred icon property. Use format "fa:iconName" or "lucide:iconName"
|
||||||
|
* Examples: "fa:check", "lucide:menu"
|
||||||
|
*/
|
||||||
|
@property({
|
||||||
|
type: String,
|
||||||
|
converter: {
|
||||||
|
fromAttribute: (value: string): IconWithPrefix => value as IconWithPrefix,
|
||||||
|
toAttribute: (value: IconWithPrefix): string => value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
public icon?: IconWithPrefix;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
public iconSize: number;
|
public iconSize: number;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public color: string = 'currentColor';
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public strokeWidth: number = 2;
|
||||||
|
|
||||||
|
// For tracking when we need to re-render
|
||||||
|
private lastIcon: IconWithPrefix | TIconKey | null = null;
|
||||||
|
private lastIconSize: number | null = null;
|
||||||
|
private lastColor: string | null = null;
|
||||||
|
private lastStrokeWidth: number | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
domtools.elementBasic.setup();
|
domtools.elementBasic.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effective icon value, supporting both the new `icon` property
|
||||||
|
* and the legacy `iconFA` property for backward compatibility.
|
||||||
|
* Prefers `icon` if both are set.
|
||||||
|
*/
|
||||||
|
private getEffectiveIcon(): IconWithPrefix | TIconKey | null {
|
||||||
|
// Prefer the new API
|
||||||
|
if (this.icon) {
|
||||||
|
return this.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the old API
|
||||||
|
if (this.iconFA) {
|
||||||
|
// If iconFA is already in the proper format (lucide:name), use it directly
|
||||||
|
if (this.iconFA.startsWith('lucide:')) {
|
||||||
|
return this.iconFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For FontAwesome icons with no prefix, add the prefix
|
||||||
|
return `fa:${this.iconFA}` as IconWithPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an icon string into its type and name parts
|
||||||
|
* @param iconStr The icon string in format "type:name"
|
||||||
|
* @returns Object with type and name properties
|
||||||
|
*/
|
||||||
|
private parseIconString(iconStr: string): { type: 'fa' | 'lucide', name: string } {
|
||||||
|
if (iconStr.startsWith('fa:')) {
|
||||||
|
return {
|
||||||
|
type: 'fa',
|
||||||
|
name: iconStr.substring(3) // Remove 'fa:' prefix
|
||||||
|
};
|
||||||
|
} else if (iconStr.startsWith('lucide:')) {
|
||||||
|
return {
|
||||||
|
type: 'lucide',
|
||||||
|
name: iconStr.substring(7) // Remove 'lucide:' prefix
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// For backward compatibility, assume FontAwesome if no prefix
|
||||||
|
return {
|
||||||
|
type: 'fa',
|
||||||
|
name: iconStr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLucideIcon(iconName: string): string {
|
||||||
|
// Create a cache key based on all visual properties
|
||||||
|
const cacheKey = `lucide:${iconName}:${this.iconSize}:${this.color}:${this.strokeWidth}`;
|
||||||
|
|
||||||
|
// Check if we already have this icon in the cache
|
||||||
|
if (iconCache.has(cacheKey)) {
|
||||||
|
return iconCache.get(cacheKey) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the Pascal case icon name (Menu instead of menu)
|
||||||
|
const pascalCaseName = iconName.charAt(0).toUpperCase() + iconName.slice(1);
|
||||||
|
|
||||||
|
// Check if the icon exists in lucideIcons
|
||||||
|
if (!lucideIcons[pascalCaseName]) {
|
||||||
|
console.warn(`Lucide icon '${pascalCaseName}' not found in lucideIcons object`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the exact pattern from Lucide documentation
|
||||||
|
const svgElement = createElement(lucideIcons[pascalCaseName], {
|
||||||
|
color: this.color,
|
||||||
|
size: this.iconSize,
|
||||||
|
strokeWidth: this.strokeWidth
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!svgElement) {
|
||||||
|
console.warn(`createElement returned empty result for ${pascalCaseName}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the HTML
|
||||||
|
const result = svgElement.outerHTML;
|
||||||
|
|
||||||
|
// Cache the result for future use
|
||||||
|
iconCache.set(cacheKey, result);
|
||||||
|
limitCacheSize();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error rendering Lucide icon ${iconName}:`, error);
|
||||||
|
|
||||||
|
// Create a fallback SVG with the icon name
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${this.iconSize}" height="${this.iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.color}" stroke-width="${this.strokeWidth}" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<text x="50%" y="50%" font-size="6" text-anchor="middle" dominant-baseline="middle" fill="${this.color}">${iconName}</text>
|
||||||
|
</svg>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: inline-flex;
|
||||||
white-space: nowrap;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
* {
|
|
||||||
transition: inherit !important;
|
/* Improve rendering performance */
|
||||||
|
#iconContainer svg {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
will-change: transform; /* Helps with animations */
|
||||||
|
contain: strict; /* Performance optimization */
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -181,8 +350,8 @@ export class DeesIcon extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
${domtools.elementBasic.styles}
|
${domtools.elementBasic.styles}
|
||||||
<style>
|
<style>
|
||||||
#iconContainer svg {
|
#iconContainer {
|
||||||
display: block;
|
width: ${this.iconSize}px;
|
||||||
height: ${this.iconSize}px;
|
height: ${this.iconSize}px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -190,14 +359,95 @@ export class DeesIcon extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updated() {
|
public updated() {
|
||||||
|
// If size is not specified, use font size as a base
|
||||||
if (!this.iconSize) {
|
if (!this.iconSize) {
|
||||||
this.iconSize = parseInt(globalThis.getComputedStyle(this).fontSize.replace(/\D/g,''));
|
this.iconSize = parseInt(globalThis.getComputedStyle(this).fontSize.replace(/\D/g,''));
|
||||||
}
|
}
|
||||||
if (this.iconFA) {
|
|
||||||
this.shadowRoot.querySelector('#iconContainer').innerHTML = this.iconFA
|
// Get the effective icon (either from icon or iconFA property)
|
||||||
? icon(faIcons[this.iconFA]).html[0]
|
const effectiveIcon = this.getEffectiveIcon();
|
||||||
: 'icon not found';
|
|
||||||
|
// Check if we actually need to update the icon
|
||||||
|
// This prevents unnecessary DOM operations when properties haven't changed
|
||||||
|
if (this.lastIcon === effectiveIcon &&
|
||||||
|
this.lastIconSize === this.iconSize &&
|
||||||
|
this.lastColor === this.color &&
|
||||||
|
this.lastStrokeWidth === this.strokeWidth) {
|
||||||
|
return; // No visual changes - skip update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update our "last properties" for future change detection
|
||||||
|
this.lastIcon = effectiveIcon;
|
||||||
|
this.lastIconSize = this.iconSize;
|
||||||
|
this.lastColor = this.color;
|
||||||
|
this.lastStrokeWidth = this.strokeWidth;
|
||||||
|
|
||||||
|
const container = this.shadowRoot?.querySelector('#iconContainer');
|
||||||
|
if (!container || !effectiveIcon) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the icon string to get type and name
|
||||||
|
const { type, name } = this.parseIconString(effectiveIcon);
|
||||||
|
|
||||||
|
if (type === 'lucide') {
|
||||||
|
// For Lucide, use direct DOM manipulation as shown in the docs
|
||||||
|
// This approach avoids HTML string issues
|
||||||
|
container.innerHTML = ''; // Clear container
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert to PascalCase
|
||||||
|
const pascalCaseName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
|
if (lucideIcons[pascalCaseName]) {
|
||||||
|
// Use the documented pattern from Lucide docs
|
||||||
|
const svgElement = createElement(lucideIcons[pascalCaseName], {
|
||||||
|
color: this.color,
|
||||||
|
size: this.iconSize,
|
||||||
|
strokeWidth: this.strokeWidth
|
||||||
|
});
|
||||||
|
|
||||||
|
if (svgElement) {
|
||||||
|
// Directly append the element
|
||||||
|
container.appendChild(svgElement);
|
||||||
|
return; // Exit early since we've added the element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here, something went wrong
|
||||||
|
throw new Error(`Could not create element for ${pascalCaseName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error rendering Lucide icon:`, error);
|
||||||
|
|
||||||
|
// Fall back to the string-based approach
|
||||||
|
const iconHtml = this.renderLucideIcon(name);
|
||||||
|
if (iconHtml) {
|
||||||
|
container.innerHTML = iconHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use FontAwesome rendering via HTML string
|
||||||
|
const faIcon = icons.fa[name as FAIconKey];
|
||||||
|
if (faIcon) {
|
||||||
|
const iconHtml = icon(faIcon).html[0];
|
||||||
|
container.innerHTML = iconHtml;
|
||||||
|
} else {
|
||||||
|
console.warn(`FontAwesome icon not found: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating icon ${effectiveIcon}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up resources when element is removed
|
||||||
|
async disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
// Clear our references
|
||||||
|
this.lastIcon = null;
|
||||||
|
this.lastIconSize = null;
|
||||||
|
this.lastColor = null;
|
||||||
|
this.lastStrokeWidth = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user