Skip to content

Commit

Permalink
Improve clips property setters
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade committed Sep 22, 2024
1 parent 9f6aaa1 commit 85d3eb1
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-terms-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@watching/clips': patch
---

Improve clips property setters
37 changes: 37 additions & 0 deletions packages/clips/source/elements/ClipsElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,43 @@ export class ClipsElement<
readonly __attributes?: Attributes;
}

export function formatAttributeValue<T extends string>(
value: string | boolean | null | undefined,
options: {truthy?: NoInfer<T>; false?: NoInfer<T>; allowed: Set<T>},
) {
if (value === true || value === '') return options.truthy;
if (value === false) return options.false;
if (value == null) return undefined;
return restrictToAllowedValues(value, options.allowed);
}

export function formatAutoAttributeValue<T extends string>(
value: string | boolean | null | undefined,
options: {truthy?: NoInfer<T>; false?: NoInfer<T>; allowed: Set<T>},
) {
return formatAttributeValue<T>(value, {
truthy: 'auto' as T,
...options,
});
}

export function formatAutoOrNoneAttributeValue<T extends string>(
value: string | boolean | null | undefined,
options: {truthy?: NoInfer<T>; allowed: Set<T>},
) {
return formatAutoAttributeValue<T>(value, {
...options,
false: 'none' as T,
});
}

export type AttributeValueAsPropertySetter<T extends string> =
| T
| ''
| boolean
| null
| undefined;

export function isAllowedValue<T extends string>(
value: string | null,
allowed: Set<T>,
Expand Down
76 changes: 54 additions & 22 deletions packages/clips/source/elements/Grid/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
} from '@watching/design';

import {
attributeRestrictedToAllowedValues,
backedByAttribute,
restrictToAllowedValues,
attributeRestrictedToAllowedValues,
formatAutoOrNoneAttributeValue,
type AttributeValueAsPropertySetter,
} from '../ClipsElement.ts';

import {
Expand Down Expand Up @@ -49,6 +50,8 @@ export interface GridProperties extends ViewProperties {

export interface GridEvents extends ViewEvents {}

const DEFAULT_SPACING_VALUE = 'none';

/**
* A `Grid` is a container component that lays out sibling elements
* using predetermined sizes. First, children are laid out along the
Expand Down Expand Up @@ -90,37 +93,66 @@ export class Grid<

get spacing(): SpacingKeyword {
return (
restrictToAllowedValues(this.getAttribute('spacing'), SPACING_KEYWORDS) ??
'none'
formatAutoOrNoneAttributeValue(this.getAttribute('spacing'), {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE
);
}

set spacing(value: SpacingKeyword | boolean) {
set spacing(value: AttributeValueAsPropertySetter<SpacingKeyword>) {
const resolvedValue =
value === true
? 'auto'
: value === false || value == null
? 'none'
: restrictToAllowedValues(value, SPACING_KEYWORDS);
formatAutoOrNoneAttributeValue(value, {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE;

if (resolvedValue === 'none') {
if (resolvedValue === DEFAULT_SPACING_VALUE) {
this.removeAttribute('spacing');
} else if (resolvedValue) {
} else {
this.setAttribute('spacing', resolvedValue);
}
}

@backedByAttribute<SpacingKeyword | undefined>({
name: 'inline-spacing',
...attributeRestrictedToAllowedValues(SPACING_KEYWORDS),
})
accessor inlineSpacing: SpacingKeyword | undefined;
get inlineSpacing(): SpacingKeyword {
return (
formatAutoOrNoneAttributeValue(this.getAttribute('inline-spacing'), {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE
);
}

@backedByAttribute<SpacingKeyword | undefined>({
name: 'block-spacing',
...attributeRestrictedToAllowedValues(SPACING_KEYWORDS),
})
accessor blockSpacing: SpacingKeyword | undefined;
set inlineSpacing(value: AttributeValueAsPropertySetter<SpacingKeyword>) {
const resolvedValue =
formatAutoOrNoneAttributeValue(value, {
allowed: SPACING_KEYWORDS,
});

if (resolvedValue == null) {
this.removeAttribute('inline-spacing');
} else {
this.setAttribute('inline-spacing', resolvedValue);
}
}

get blockSpacing(): SpacingKeyword {
return (
formatAutoOrNoneAttributeValue(this.getAttribute('block-spacing'), {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE
);
}

set blockSpacing(value: AttributeValueAsPropertySetter<SpacingKeyword>) {
const resolvedValue =
formatAutoOrNoneAttributeValue(value, {
allowed: SPACING_KEYWORDS,
});

if (resolvedValue == null) {
this.removeAttribute('block-spacing');
} else {
this.setAttribute('block-spacing', resolvedValue);
}
}

@backedByAttribute({
...attributeRestrictedToAllowedValues(DIRECTION_KEYWORDS),
Expand Down
22 changes: 11 additions & 11 deletions packages/clips/source/elements/Image/Image.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {
ClipsElement,
backedByAttribute,
formatAutoOrNoneAttributeValue,
attributeRestrictedToAllowedValues,
restrictToAllowedValues,
type AttributeValueAsPropertySetter,
} from '../ClipsElement.ts';
import {
CORNER_RADIUS_KEYWORDS,
Expand Down Expand Up @@ -97,6 +98,8 @@ export interface ImageProperties {

export interface ImageEvents {}

const DEFAULT_CORNER_RADIUS_VALUE = 'none';

/**
* Image is used to visually style and provide semantic value for a small piece of image
* content.
Expand Down Expand Up @@ -155,20 +158,17 @@ export class Image

get cornerRadius(): CornerRadiusKeyword {
return (
restrictToAllowedValues(
this.getAttribute('corner-radius'),
CORNER_RADIUS_KEYWORDS,
) ?? 'none'
formatAutoOrNoneAttributeValue(this.getAttribute('corner-radius'), {
allowed: CORNER_RADIUS_KEYWORDS,
}) ?? DEFAULT_CORNER_RADIUS_VALUE
);
}

set cornerRadius(value: CornerRadiusKeyword | boolean) {
set cornerRadius(value: AttributeValueAsPropertySetter<CornerRadiusKeyword>) {
const resolvedValue =
value === true
? 'auto'
: value === false || value == null
? 'none'
: restrictToAllowedValues(value, CORNER_RADIUS_KEYWORDS);
formatAutoOrNoneAttributeValue(value, {
allowed: CORNER_RADIUS_KEYWORDS,
}) ?? DEFAULT_CORNER_RADIUS_VALUE;

if (resolvedValue === 'none') {
this.removeAttribute('corner-radius');
Expand Down
33 changes: 19 additions & 14 deletions packages/clips/source/elements/Modal/Modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import {
SPACING_OR_NONE_KEYWORDS,
type SpacingOrNoneKeyword,
} from '@watching/design';
import {ClipsElement, restrictToAllowedValues} from '../ClipsElement.ts';
import {
ClipsElement,
formatAutoOrNoneAttributeValue,
type AttributeValueAsPropertySetter,
} from '../ClipsElement.ts';

export interface ModalAttributes {
/**
Expand All @@ -25,6 +29,8 @@ export interface ModalProperties {

export interface ModalEvents {}

const DEFAULT_PADDING_VALUE = 'none';

/**
* A Modal is an overlay that blocks interaction with the rest of the page. The
* user must take an action to dismiss the modal, either by pressing on the backdrop,
Expand All @@ -44,23 +50,22 @@ export class Modal

get padding(): SpacingOrNoneKeyword {
return (
restrictToAllowedValues(
this.getAttribute('padding'),
SPACING_OR_NONE_KEYWORDS,
) ?? 'none'
formatAutoOrNoneAttributeValue(this.getAttribute('padding'), {
allowed: SPACING_OR_NONE_KEYWORDS,
}) ?? DEFAULT_PADDING_VALUE
);
}

set padding(value: SpacingOrNoneKeyword | boolean | undefined) {
if (value == null || value === 'none' || value === false) {
this.removeAttribute('padding');
} else {
const resolvedValue =
value === true
? 'auto'
: restrictToAllowedValues(value, SPACING_OR_NONE_KEYWORDS);
set padding(value: AttributeValueAsPropertySetter<SpacingOrNoneKeyword>) {
const resolvedValue =
formatAutoOrNoneAttributeValue(value, {
allowed: SPACING_OR_NONE_KEYWORDS,
}) ?? DEFAULT_PADDING_VALUE;

if (resolvedValue) this.setAttribute('padding', resolvedValue);
if (resolvedValue === 'none') {
this.removeAttribute('padding');
} else if (resolvedValue) {
this.setAttribute('padding', resolvedValue);
}
}
}
Expand Down
29 changes: 17 additions & 12 deletions packages/clips/source/elements/SkeletonText/SkeletonText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import type {CSSLiteralValue} from '../../styles.ts';
import {
ClipsElement,
backedByAttribute,
formatAutoAttributeValue,
restrictToAllowedValues,
type AttributeValueAsPropertySetter,
} from '../ClipsElement.ts';

export interface SkeletonTextAttributes {
Expand Down Expand Up @@ -42,6 +44,8 @@ export interface SkeletonTextProperties {

export interface SkeletonTextEvents {}

const DEFAULT_EMPHASIS_VALUE = 'auto';

/**
* Text is used to visually style and provide semantic value for a small piece of text
* content.
Expand All @@ -64,23 +68,24 @@ export class SkeletonText
*/
get emphasis(): TextEmphasisKeyword {
return (
restrictToAllowedValues(
this.getAttribute('emphasis'),
TEXT_EMPHASIS_KEYWORDS,
) ?? 'auto'
formatAutoAttributeValue(this.getAttribute('emphasis'), {
allowed: TEXT_EMPHASIS_KEYWORDS,
}) ?? DEFAULT_EMPHASIS_VALUE
);
}

set emphasis(value: TextEmphasisKeyword | boolean | undefined) {
if (value == null || value === false) {
set emphasis(value: AttributeValueAsPropertySetter<TextEmphasisKeyword>) {
const resolvedValue =
formatAutoAttributeValue(value, {
allowed: TEXT_EMPHASIS_KEYWORDS,
truthy: 'strong',
false: 'auto',
}) ?? DEFAULT_EMPHASIS_VALUE;

if (resolvedValue === DEFAULT_EMPHASIS_VALUE) {
this.removeAttribute('emphasis');
} else {
const resolvedValue =
value === true
? 'strong'
: restrictToAllowedValues(value, TEXT_EMPHASIS_KEYWORDS);

if (resolvedValue) this.setAttribute('emphasis', resolvedValue);
this.setAttribute('emphasis', resolvedValue);
}
}

Expand Down
19 changes: 12 additions & 7 deletions packages/clips/source/elements/Stack/Stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
} from '@watching/design';

import {
attributeRestrictedToAllowedValues,
backedByAttribute,
restrictToAllowedValues,
attributeRestrictedToAllowedValues,
formatAutoOrNoneAttributeValue,
} from '../ClipsElement.ts';

import {
Expand Down Expand Up @@ -41,6 +41,8 @@ export interface StackProperties extends ViewProperties {

export interface StackEvents extends ViewEvents {}

const DEFAULT_SPACING_VALUE = 'none';

/**
* A `Stack` is a container component that lays out sibling elements
* beside one another. Children of a `Stack` keep their intrinsic size,
Expand Down Expand Up @@ -79,16 +81,19 @@ export class Stack<

get spacing(): SpacingKeyword {
return (
restrictToAllowedValues(this.getAttribute('spacing'), SPACING_KEYWORDS) ??
'none'
formatAutoOrNoneAttributeValue(this.getAttribute('spacing'), {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE
);
}

set spacing(value: SpacingKeyword | boolean) {
set spacing(value: SpacingKeyword | '' | boolean | null | undefined) {
const resolvedValue =
value === true ? 'auto' : value === false ? 'none' : value;
formatAutoOrNoneAttributeValue(value, {
allowed: SPACING_KEYWORDS,
}) ?? DEFAULT_SPACING_VALUE;

if (resolvedValue === 'none') {
if (resolvedValue === DEFAULT_SPACING_VALUE) {
this.removeAttribute('spacing');
} else {
this.setAttribute('spacing', resolvedValue);
Expand Down
Loading

0 comments on commit 85d3eb1

Please sign in to comment.