mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Support inheritance and initial values
This commit is contained in:
69
docs/docs/experimental/inherit.md
Normal file
69
docs/docs/experimental/inherit.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Inheritance & Default value tests
|
||||
---
|
||||
|
||||
Button variant should default to `neutral`:
|
||||
|
||||
```html {.example}
|
||||
<wa-button>Neutral</wa-button>
|
||||
<wa-button variant="neutral">Neutral</wa-button>
|
||||
<wa-button variant="brand">Brand</wa-button>
|
||||
```
|
||||
|
||||
Callout variant should default to `brand`.
|
||||
Buttons within an element with a variant should inherit that variant unless they have a variant of their own.
|
||||
|
||||
```html {.example}
|
||||
<wa-callout>
|
||||
Brand
|
||||
<wa-button>Brand</wa-button>
|
||||
<wa-button variant="neutral">Neutral</wa-button>
|
||||
<wa-button variant="brand">Brand</wa-button>
|
||||
<button>Brand</button>
|
||||
<button class="wa-neutral">Neutral</button>
|
||||
<button class="wa-brand">Brand</button>
|
||||
</wa-callout>
|
||||
<wa-callout variant="neutral">
|
||||
Neutral
|
||||
<wa-button>Neutral</wa-button>
|
||||
<wa-button variant="neutral">Neutral</wa-button>
|
||||
<wa-button variant="brand">Brand</wa-button>
|
||||
<button>Neutral</button>
|
||||
<button class="wa-neutral">Neutral</button>
|
||||
<button class="wa-brand">Brand</button>
|
||||
</wa-callout>
|
||||
```
|
||||
|
||||
Nested callouts:
|
||||
|
||||
|
||||
```html {.example}
|
||||
<wa-callout>
|
||||
Brand
|
||||
<wa-callout>Brand</wa-callout>
|
||||
</wa-callout>
|
||||
<wa-callout variant="neutral">
|
||||
Neutral
|
||||
<wa-callout>Neutral</wa-callout>
|
||||
</wa-callout>
|
||||
```
|
||||
|
||||
|
||||
```html {.example}
|
||||
<wa-callout>
|
||||
Brand
|
||||
<wa-button>Brand</wa-button>
|
||||
<wa-button variant="neutral">Neutral</wa-button>
|
||||
<button>Brand</button>
|
||||
<button class="wa-neutral">Neutral</button>
|
||||
<br>
|
||||
<br>
|
||||
<wa-callout variant="neutral">
|
||||
Neutral
|
||||
<wa-button>Neutral</wa-button>
|
||||
<wa-button variant="brand">Brand</wa-button>
|
||||
<button>Neutral</button>
|
||||
<button class="wa-brand">Brand</button>
|
||||
</wa-callout>
|
||||
</wa-callout>
|
||||
```
|
||||
@@ -69,7 +69,8 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
@property() title = ''; // make reactive to pass through
|
||||
|
||||
/** The button's theme variant. */
|
||||
@property({ reflect: true }) variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral';
|
||||
@property({ reflect: true, initial: 'neutral' })
|
||||
variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'inherit' = 'inherit';
|
||||
|
||||
/** The button's visual appearance. */
|
||||
@property({ reflect: true, default: 'accent' })
|
||||
|
||||
@@ -28,7 +28,13 @@ export default class WaCallout extends WebAwesomeElement {
|
||||
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, styles];
|
||||
|
||||
/** The callout's theme variant. */
|
||||
@property({ reflect: true }) variant: 'brand' | 'success' | 'neutral' | 'warning' | 'danger' = 'brand';
|
||||
@property({ reflect: true, initial: 'brand' }) variant:
|
||||
| 'brand'
|
||||
| 'success'
|
||||
| 'neutral'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'inherit' = 'inherit';
|
||||
|
||||
/** The callout's visual appearance. */
|
||||
@property({ reflect: true }) appearance:
|
||||
|
||||
@@ -10,6 +10,7 @@ declare module 'lit' {
|
||||
* Specifies the property’s default value
|
||||
*/
|
||||
default?: any;
|
||||
initial?: any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +25,13 @@ export default class WebAwesomeElement extends LitElement {
|
||||
/* eslint-disable-next-line */
|
||||
console.error('Element internals are not supported in your browser. Consider using a polyfill');
|
||||
}
|
||||
|
||||
let Self = this.constructor as typeof WebAwesomeElement;
|
||||
for (let [property, spec] of Self.elementProperties) {
|
||||
if (spec.default === 'inherit' && spec.initial !== undefined && typeof property === 'string') {
|
||||
this.toggleCustomState(`initial-${property}-${spec.initial}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make localization attributes reactive
|
||||
@@ -160,36 +168,69 @@ export default class WebAwesomeElement extends LitElement {
|
||||
}
|
||||
|
||||
static createProperty(name: PropertyKey, options?: PropertyDeclaration): void {
|
||||
if (options && options.default !== undefined && options.converter === undefined) {
|
||||
// Wrap the default converter to remove the attribute if the value is the default
|
||||
// This effectively prevents the component sprouting attributes that have not been specified
|
||||
let converter = {
|
||||
...defaultConverter,
|
||||
toAttribute(value: string, type: unknown): unknown {
|
||||
if (value === options!.default) {
|
||||
return null;
|
||||
}
|
||||
return defaultConverter.toAttribute!(value, type);
|
||||
},
|
||||
};
|
||||
options = { ...options, converter };
|
||||
if (options) {
|
||||
if (options.initial !== undefined && options.default === undefined) {
|
||||
// Set "inherit" value as default if no default is specified but the initial value is
|
||||
// This saves us having to tediously specify default: "inherit", initial: "foo" for every property
|
||||
options.default = 'inherit';
|
||||
}
|
||||
|
||||
if (options.default !== undefined && options.converter === undefined) {
|
||||
// Wrap the default converter to remove the attribute if the value is the default
|
||||
// This effectively prevents the component sprouting attributes that have not been specified
|
||||
let converter = {
|
||||
...defaultConverter,
|
||||
toAttribute(value: string, type: unknown): unknown {
|
||||
if (value === options!.default) {
|
||||
return null;
|
||||
}
|
||||
return defaultConverter.toAttribute!(value, type);
|
||||
},
|
||||
};
|
||||
options = { ...options, converter };
|
||||
}
|
||||
}
|
||||
|
||||
super.createProperty(name, options);
|
||||
|
||||
// Wrap the default accessor with logic to return the default value if the value is null
|
||||
if (options && options.default !== undefined) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(this.prototype, name as string);
|
||||
if (options) {
|
||||
if (options.default !== undefined) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(this.prototype, name as string);
|
||||
|
||||
if (descriptor?.get) {
|
||||
const getter = descriptor.get;
|
||||
if (descriptor?.get) {
|
||||
const getter = descriptor.get;
|
||||
|
||||
Object.defineProperty(this.prototype, name, {
|
||||
...descriptor,
|
||||
get() {
|
||||
return getter.call(this) ?? options.default;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(this.prototype, name, {
|
||||
...descriptor,
|
||||
get() {
|
||||
return getter.call(this) ?? options.default;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.default === 'inherit') {
|
||||
// Add getter for "computed" value (taking ancestors into account)
|
||||
let capitalizedName = name.toString().replace(/^\w/, c => c.toUpperCase());
|
||||
Object.defineProperty(this.prototype, `computed${capitalizedName}`, {
|
||||
get() {
|
||||
let value;
|
||||
let element = this;
|
||||
|
||||
do {
|
||||
value = element[name as string];
|
||||
element = element.parentElement;
|
||||
} while (value === 'inherit' && element.parentElement);
|
||||
|
||||
if (value === 'inherit') {
|
||||
// If we've reached this point and we still have `inherit`, we just ran out of parents
|
||||
return options.initial;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user