mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 04:29:14 +00:00
Compare commits
4 Commits
include-er
...
bramus-sty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c42521fc3 | ||
|
|
9772192d23 | ||
|
|
f601b8aaf4 | ||
|
|
d18edcc941 |
@@ -24,6 +24,63 @@ Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regu
|
||||
<wa-icon family="brands" name="web-awesome"></wa-icon>
|
||||
```
|
||||
|
||||
<div data-alpha="remove">
|
||||
|
||||
### Setting icon info via CSS
|
||||
|
||||
You can also set the icon's family, name, and variant via CSS custom properties.
|
||||
This can be useful when you want to set the icon dynamically (e.g. in response to a CSS pseudo-class or media query) or set defaults for a group of icons (e.g. icons inside callouts or all icons for a given theme).
|
||||
|
||||
```html {.example}
|
||||
<wa-callout>
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
This is a callout.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="danger">
|
||||
<wa-icon slot="icon" name="dumpster-fire" variant="solid"></wa-icon>
|
||||
This is a callout with an explicit icon.
|
||||
</wa-callout>
|
||||
|
||||
<wa-callout variant="warning">
|
||||
<!-- Look ma, no attributes! -->
|
||||
<wa-icon slot="icon"></wa-icon>
|
||||
Here be dragons.
|
||||
<button id="toggle_icon">Toggle <wa-icon name="circle-exclamation"></wa-icon></button>
|
||||
</wa-callout>
|
||||
|
||||
<style>
|
||||
wa-callout {
|
||||
--wa-icon-variant: regular;
|
||||
--wa-icon-name: info-circle;
|
||||
|
||||
&[variant="warning"] {
|
||||
--wa-icon-name: triangle-exclamation;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
toggle_icon.addEventListener('click', e => {
|
||||
let callout = e.target.closest('wa-callout');
|
||||
let value = callout.style.getPropertyValue('--wa-icon-name').trim();
|
||||
if (value) {
|
||||
callout.style.removeProperty('--wa-icon-name');
|
||||
}
|
||||
else {
|
||||
callout.style.setProperty('--wa-icon-name', 'circle-exclamation');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- If you specify attributes, they will override the CSS custom properties, which provides a way to set defaults and then override them as needed.
|
||||
- CSS custom properties inherit — so if you set a `--wa-icon-*` custom property on an element, it will affect *all* icons within it that don’t override these values (either via attributes or CSS custom properties).
|
||||
- These CSS properties are currently not reactive and will only be read when the component is first connected.
|
||||
|
||||
</div>
|
||||
|
||||
### Colors
|
||||
|
||||
Icons inherit their color from the current text color. Thus, you can set the `color` property on the `<wa-icon>` element or an ancestor to change the color.
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "3.0.0-alpha.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bramus/style-observer": "^1.3.0",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"@shoelace-style/animations": "^1.2.0",
|
||||
@@ -676,6 +677,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@bramus/style-observer": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/style-observer/-/style-observer-1.3.0.tgz",
|
||||
"integrity": "sha512-IQjYId9X7xgz0NeKGatC37lqsdS+cE5bfdB9jKh7+zJnA9BqENee2C48boDIRQrTED4JxleRZGhTY86S1/l7QA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspell/cspell-bundled-dicts": {
|
||||
"version": "6.31.3",
|
||||
"resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-6.31.3.tgz",
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bramus/style-observer": "^1.3.0",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"@shoelace-style/animations": "^1.2.0",
|
||||
|
||||
@@ -31,3 +31,24 @@ svg {
|
||||
width: 1em;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@property --wa-icon-family {
|
||||
syntax: '<custom-ident> | auto';
|
||||
inherits: true;
|
||||
initial-value: 'auto';
|
||||
}
|
||||
@property --wa-icon-variant {
|
||||
syntax: '<custom-ident> | auto';
|
||||
inherits: true;
|
||||
initial-value: 'auto';
|
||||
}
|
||||
@property --wa-icon-library {
|
||||
syntax: '<custom-ident> | auto';
|
||||
inherits: true;
|
||||
initial-value: 'auto';
|
||||
}
|
||||
@property --wa-icon-name {
|
||||
syntax: '<custom-ident> | auto';
|
||||
inherits: true;
|
||||
initial-value: 'auto';
|
||||
}
|
||||
|
||||
@@ -48,21 +48,21 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
@state() private svg: SVGElement | HTMLTemplateResult | null = null;
|
||||
|
||||
/** The name of the icon to draw. Available names depend on the icon library being used. */
|
||||
@property({ reflect: true }) name?: string;
|
||||
@property({ cssProperty: true }) name?: string;
|
||||
|
||||
/**
|
||||
* The family of icons to choose from. For Font Awesome Free (default), valid options include `classic` and `brands`.
|
||||
* For Font Awesome Pro subscribers, valid options include, `classic`, `sharp`, `duotone`, and `brands`. Custom icon
|
||||
* libraries may or may not use this property.
|
||||
*/
|
||||
@property({ reflect: true }) family: string;
|
||||
@property({ cssProperty: true }) family: string;
|
||||
|
||||
/**
|
||||
* The name of the icon's variant. For Font Awesome, valid options include `thin`, `light`, `regular`, and `solid` for
|
||||
* the `classic` and `sharp` families. Some variants require a Font Awesome Pro subscription. Custom icon libraries
|
||||
* may or may not use this property.
|
||||
*/
|
||||
@property({ reflect: true }) variant: string;
|
||||
@property({ cssProperty: true }) variant: string;
|
||||
|
||||
/** Draws the icon in a fixed-width both. */
|
||||
@property({ attribute: 'fixed-width', type: Boolean, reflect: true }) fixedWidth: false;
|
||||
@@ -80,10 +80,11 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
@property() label = '';
|
||||
|
||||
/** The name of a registered custom icon library. */
|
||||
@property({ reflect: true }) library = 'default';
|
||||
@property({ cssProperty: true }) library = 'default';
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
watchIcon(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import type { CSSResult, CSSResultGroup, PropertyValues } from 'lit';
|
||||
import { CSSStyleObserver } from '@bramus/style-observer';
|
||||
import type { CSSResult, CSSResultGroup, PropertyDeclaration, PropertyValues } from 'lit';
|
||||
import { LitElement, isServer, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import componentStyles from '../styles/shadow/component.css';
|
||||
import { getComputedStyle } from './computedStyle.js';
|
||||
|
||||
// Augment Lit's module
|
||||
declare module 'lit' {
|
||||
interface PropertyDeclaration {
|
||||
/**
|
||||
* Indicates whether the property should reflect to a CSS custom property.
|
||||
*/
|
||||
cssProperty?: true | string;
|
||||
}
|
||||
}
|
||||
|
||||
export default class WebAwesomeElement extends LitElement {
|
||||
constructor() {
|
||||
@@ -52,6 +64,18 @@ export default class WebAwesomeElement extends LitElement {
|
||||
|
||||
internals: ElementInternals;
|
||||
|
||||
#computedStyle: CSSStyleDeclaration | null;
|
||||
#setVia: Record<PropertyKey, 'css' | 'attribute' | 'js'> = {};
|
||||
#setting = new Set<PropertyKey>();
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// FIXME this is currently static.
|
||||
// It will only update when the element is connected, not when a relevant CSS property changes.
|
||||
this.updateCSSProperties();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
||||
if (!this.#hasRecordedInitialProperties) {
|
||||
(this.constructor as typeof WebAwesomeElement).elementProperties.forEach(
|
||||
@@ -111,6 +135,21 @@ export default class WebAwesomeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
let Self = this.constructor as typeof WebAwesomeElement;
|
||||
|
||||
if (Self.cssAttributeProperties.size > 0) {
|
||||
for (let [name] of changedProperties) {
|
||||
if (typeof name === 'string' && this.#setVia[name] === 'css' && !this.#setting.has(name)) {
|
||||
// A property is being set via JS and it’s NOT because we're reflecting a CSS property
|
||||
this.#setVia[name] = 'js';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if states are supported by the element */
|
||||
private hasStatesSupport(): boolean {
|
||||
return Boolean(this.internals?.states);
|
||||
@@ -148,4 +187,69 @@ export default class WebAwesomeElement extends LitElement {
|
||||
hasCustomState(state: string): boolean {
|
||||
return this.hasStatesSupport() ? this.internals.states.has(state) : false;
|
||||
}
|
||||
|
||||
protected updateCSSProperties() {
|
||||
const Self = this.constructor as typeof WebAwesomeElement;
|
||||
if (Self.cssAttributeProperties.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Self.styleObserver) {
|
||||
// First time, init stuff
|
||||
// First, replace `true` with actual CSS property names
|
||||
for (let [name, cssProperty] of Self.cssAttributeProperties) {
|
||||
if (cssProperty === true) {
|
||||
// Default name
|
||||
cssProperty = `--${this.tagName.toLowerCase()}-${name}`;
|
||||
Self.cssAttributeProperties.set(name, cssProperty);
|
||||
}
|
||||
}
|
||||
// Then we observe them
|
||||
let cssProperties = [...Self.cssAttributeProperties.values()] as string[];
|
||||
|
||||
Self.styleObserver = new CSSStyleObserver(cssProperties, (...args) => {
|
||||
console.log(...args);
|
||||
this.updateCSSProperties();
|
||||
});
|
||||
}
|
||||
|
||||
this.#computedStyle ??= getComputedStyle(this);
|
||||
|
||||
const tagName = this.tagName.toLowerCase();
|
||||
for (let [name, cssProperty] of Self.cssAttributeProperties) {
|
||||
if (typeof name === 'string' && !this.hasAttribute(name) && this.#setVia[name] !== 'js') {
|
||||
// Check if supplied as a CSS custom property
|
||||
// TODO !important should override attribute values
|
||||
cssProperty = cssProperty === true ? `--${tagName}-${name}` : cssProperty;
|
||||
const value = this.#computedStyle?.getPropertyValue(cssProperty);
|
||||
|
||||
if (value && value !== 'auto') {
|
||||
this.#setVia[name] = 'css';
|
||||
this.#setting.add(name);
|
||||
// @ts-ignore
|
||||
this[name] = value.trim();
|
||||
this.updateComplete.then(() => {
|
||||
this.#setting.delete(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses will get their own copy automagically (see below)
|
||||
protected static cssAttributeProperties = new Map<PropertyKey, true | string>();
|
||||
protected static styleObserver: CSSStyleObserver | undefined;
|
||||
|
||||
static createProperty(name: PropertyKey, options?: PropertyDeclaration): void {
|
||||
super.createProperty(name, options);
|
||||
|
||||
if (options?.cssProperty) {
|
||||
if (this.cssAttributeProperties === WebAwesomeElement.cssAttributeProperties) {
|
||||
// Each class needs its own, otherwise they'd share the same object
|
||||
this.cssAttributeProperties = new Map();
|
||||
}
|
||||
|
||||
this.cssAttributeProperties.set(name, options.cssProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user