@pg-labs-incubator/cobweb 0.0.6 → 0.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pg-labs-incubator/cobweb",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Cobweb components library, based on Material 3 design, for SolidJS.",
5
5
  "license": "MPL-2.0",
6
6
  "homepage": "https://pglabs.ai/projects/cobweb",
@@ -104,7 +104,7 @@
104
104
  },
105
105
  "devDependencies": {
106
106
  "@pg-labs-incubator/cobweb-design": "^0.0.1",
107
- "@pg-labs-incubator/lightningcss-plugin-cobweb": "^0.0.2",
107
+ "@pg-labs-incubator/lightningcss-plugin-cobweb": "^0.0.3",
108
108
  "@solidjs/testing-library": "^0.8.10",
109
109
  "@vitest/browser": "^3.2.4",
110
110
  "playwright": "1.62.0",
@@ -42,6 +42,10 @@ roles:
42
42
  description: Outline width of outlined styles.
43
43
  type: length
44
44
  value: 1px
45
+ progressIndicatorSize:
46
+ description: Width and height of the loading indicator of a button showing progress.
47
+ type: length
48
+ value: 48px
45
49
  iconButton:
46
50
  description: Icon-button roles. The container width is the icon size plus twice a space.
47
51
  roles:
@@ -100,6 +104,7 @@ themes:
100
104
  uncorrected `ButtonXSmallTokens` values.
101
105
  values:
102
106
  containerHeight: 32px
107
+ progressIndicatorSize: 28px
103
108
  squareShape: $cw-CornerRadius-medium
104
109
  pressedShape: $cw-CornerRadius-small
105
110
  iconSize: 20px
@@ -115,6 +120,7 @@ themes:
115
120
  description: Small buttons (`ButtonSmallTokens`, `SmallIconButtonTokens`).
116
121
  values:
117
122
  containerHeight: 40px
123
+ progressIndicatorSize: 36px
118
124
  squareShape: $cw-CornerRadius-medium
119
125
  pressedShape: $cw-CornerRadius-small
120
126
  iconSize: 20px
@@ -130,6 +136,7 @@ themes:
130
136
  description: Medium buttons (`ButtonMediumTokens`, `MediumIconButtonTokens`).
131
137
  values:
132
138
  containerHeight: 56px
139
+ progressIndicatorSize: 48px
133
140
  squareShape: $cw-CornerRadius-large
134
141
  pressedShape: $cw-CornerRadius-medium
135
142
  iconSize: 24px
@@ -145,6 +152,7 @@ themes:
145
152
  description: Large buttons (`ButtonLargeTokens`, `LargeIconButtonTokens`).
146
153
  values:
147
154
  containerHeight: 96px
155
+ progressIndicatorSize: 80px
148
156
  squareShape: $cw-CornerRadius-xlarge
149
157
  pressedShape: $cw-CornerRadius-large
150
158
  iconSize: 32px
@@ -160,6 +168,7 @@ themes:
160
168
  description: Extra large buttons (`ButtonXLargeTokens`, `XLargeIconButtonTokens`).
161
169
  values:
162
170
  containerHeight: 136px
171
+ progressIndicatorSize: 116px
163
172
  squareShape: $cw-CornerRadius-xlarge
164
173
  pressedShape: $cw-CornerRadius-large
165
174
  iconSize: 40px
@@ -1 +1 @@
1
- import{createContext as l,untrack as c}from"solid-js";import{isAnchorElement as d,preventDisabledLinkKey as u}from"../navigation/base";import{FocusState as a,HoverState as h,PressState as p}from"../state";import{InteractableController as g}from"../state/controller";import{deferAfterDelegatedCancellation as f}from"../util/event";import"./action.css";const x=/* @__PURE__ */l();function P(s){return(s.nodeType===Node.DOCUMENT_NODE?s:s.ownerDocument)?.defaultView?.MutationObserver??MutationObserver;}const b=a|h|p,C=a;class T extends g{#s;#n;#r;#d;#o;#i;#e;constructor(e,t){const n="checked"in e,o="attr:aria-disabled"in e,i="attr:aria-pressed"in e;super(e,{baseInteractionCssClassName:t,styleElementIsFunction:!0}),this.#s=t,this.#n=n,this.#r=o,this.#d=i,this.#o=r=>{this.isDisabled()&&r.preventDefault();},this.#i=r=>u(r,super.functionElement),this.#e=!n&&c(()=>e.defaultChecked===!0)?4:0;}isDisabled(){return(this.#e&1)!==0;}isToggle(){return(this.#e&2)!==0;}isChecked(){return this.isToggle()&&(this.#n?super.readProp("checked",!0)[0]===!0:(this.#e&4)!==0);}actionClass(e){return`${this.#s}-${e}`;}#t(e,t){this.#e=t?this.#e|e:this.#e&~e;}#a(){this.#d||super.setAttribute("aria-pressed",this.isToggle()?this.isChecked():void 0),super.setClass(this.actionClass("checked"),this.isChecked());}applyComponentProps(){const e=super.readProps(["checked","disabled","href","readOnly","tabIndex","toggle","type"]),t=e.disabled===!0,n=e.href!==void 0,o=e.toggle===!0,i=o&&e.readOnly===!0;e.disabledChanged&&(this.#t(1,t),super.setClass(this.actionClass("disabled"),t)),(e.disabledChanged||e.hrefChanged)&&super.setAttribute("disabled",t&&!n),e.hrefChanged&&super.setAttribute("href",e.href),e.toggleChanged&&(this.#t(2,o),super.setClass(this.actionClass("toggle"),o)),(e.toggleChanged||e.readOnlyChanged)&&this.#t(8,i),!this.#r&&(e.disabledChanged||e.hrefChanged||e.readOnlyChanged||e.toggleChanged)&&super.setAttribute("aria-disabled",i||t&&n?"true":void 0),(e.toggleChanged||e.checkedChanged)&&this.#a(),(e.disabledChanged||e.toggleChanged||e.readOnlyChanged)&&super.setEnabledInteractionStates(t?0:i?C:b),(e.typeChanged||e.hrefChanged)&&super.setAttribute("type",n?void 0:e.type??"button"),(e.tabIndexChanged||e.disabledChanged)&&super.setAttribute("tabindex",t?-1:e.tabIndex);}onFunctionElementChanged(e){const t=super.functionElement;super.onFunctionElementChanged(e),d(t)&&(t.removeEventListener("click",this.#o),t.removeEventListener("keydown",this.#i)),d(e)&&(e.addEventListener("click",this.#o),e.addEventListener("keydown",this.#i));}onEvent(e){if(this.isDisabled()||(e.type==="click"&&this.isToggle()&&(this.#e&8)!==0&&e.preventDefault(),super.onEvent(e),e.type!=="click"||e.defaultPrevented||!this.isToggle()))return;const t=this.functionElement;f(e,t,()=>{if(this.isControllerDisposed||e.defaultPrevented||t===null||t!==this.functionElement||this.isDisabled()||t.matches(":disabled")||!this.isToggle()||(this.#e&8)!==0)return;const n=!this.isChecked();this.props.onChange?.(n,this,e),!(this.#n||this.isControllerDisposed||!this.isToggle())&&(this.#t(4,n),this.#a());},!1);}}export{T as ActionButtonController,x as ActionLayoutContext,P as mutationObserverOf};
1
+ import{createContext as u,untrack as c}from"solid-js";import{isAnchorElement as d,preventDisabledLinkKey as h}from"../navigation/base";import{FocusState as l,HoverState as p,PressState as g}from"../state";import{InteractableController as f}from"../state/controller";import{deferAfterDelegatedCancellation as b}from"../util/event";import"./action.css";const v=/* @__PURE__ */u();function T(r){return(r.nodeType===Node.DOCUMENT_NODE?r:r.ownerDocument)?.defaultView?.MutationObserver??MutationObserver;}const C=l|p|g,y=l;class D extends f{#i;#n;#r;#a;#d;#s;#o;#e;constructor(e,t){const n="checked"in e,s="attr:aria-busy"in e,o="attr:aria-disabled"in e,i="attr:aria-pressed"in e;super(e,{baseInteractionCssClassName:t,styleElementIsFunction:!0}),this.#i=t,this.#n=n,this.#r=s,this.#a=o,this.#d=i,this.#s=a=>{this.isDisabled()&&a.preventDefault();},this.#o=a=>h(a,super.functionElement),this.#e=!n&&c(()=>e.defaultChecked===!0)?4:0;}isDisabled(){return(this.#e&1)!==0;}isToggle(){return(this.#e&2)!==0;}isChecked(){return this.isToggle()&&(this.#n?super.readProp("checked",!0)[0]===!0:(this.#e&4)!==0);}actionClass(e){return`${this.#i}-${e}`;}#t(e,t){this.#e=t?this.#e|e:this.#e&~e;}#l(){this.#d||super.setAttribute("aria-pressed",this.isToggle()?this.isChecked():void 0),super.setClass(this.actionClass("checked"),this.isChecked());}applyComponentProps(){const e=super.readProps(["checked","disabled","href","progress","readOnly","tabIndex","toggle","type"]),t=e.disabled===!0,n=e.href!==void 0,s=e.progress!==void 0,o=e.toggle===!0,i=o&&e.readOnly===!0;e.progressChanged&&(this.#t(16,s),super.setClass(this.actionClass("pending"),e.progress==="pending"),super.setClass(this.actionClass("done"),e.progress==="done"),this.#r||super.setAttribute("aria-busy",e.progress==="pending"?"true":void 0)),e.disabledChanged&&(this.#t(1,t),super.setClass(this.actionClass("disabled"),t)),(e.disabledChanged||e.hrefChanged)&&super.setAttribute("disabled",t&&!n),e.hrefChanged&&super.setAttribute("href",e.href),e.toggleChanged&&(this.#t(2,o),super.setClass(this.actionClass("toggle"),o)),(e.toggleChanged||e.readOnlyChanged)&&this.#t(8,i),!this.#a&&(e.disabledChanged||e.hrefChanged||e.progressChanged||e.readOnlyChanged||e.toggleChanged)&&super.setAttribute("aria-disabled",i||s||t&&n?"true":void 0),(e.toggleChanged||e.checkedChanged)&&this.#l(),(e.disabledChanged||e.progressChanged||e.toggleChanged||e.readOnlyChanged)&&super.setEnabledInteractionStates(t?0:i||s?y:C),(e.typeChanged||e.hrefChanged)&&super.setAttribute("type",n?void 0:e.type??"button"),(e.tabIndexChanged||e.disabledChanged)&&super.setAttribute("tabindex",t?-1:e.tabIndex);}onFunctionElementChanged(e){const t=super.functionElement;super.onFunctionElementChanged(e),d(t)&&(t.removeEventListener("click",this.#s),t.removeEventListener("keydown",this.#o)),d(e)&&(e.addEventListener("click",this.#s),e.addEventListener("keydown",this.#o));}onEvent(e){if(this.isDisabled())return;if(e.type==="click"&&(this.#e&16)!==0){e.preventDefault(),e.stopImmediatePropagation();return;}if(e.type==="click"&&this.isToggle()&&(this.#e&8)!==0&&e.preventDefault(),super.onEvent(e),e.type!=="click"||e.defaultPrevented||!this.isToggle())return;const t=this.functionElement;b(e,t,()=>{if(this.isControllerDisposed||e.defaultPrevented||t===null||t!==this.functionElement||this.isDisabled()||t.matches(":disabled")||!this.isToggle()||(this.#e&8)!==0)return;const n=!this.isChecked();this.props.onChange?.(n,this,e),!(this.#n||this.isControllerDisposed||!this.isToggle())&&(this.#t(4,n),this.#l());},!1);}}export{D as ActionButtonController,v as ActionLayoutContext,T as mutationObserverOf};
@@ -1,12 +1,13 @@
1
- import{createMemo as u,mergeProps as f,useContext as p}from"solid-js";import{defineInteractableComponent as A}from"../state";import{ActionButtonController as b,ActionLayoutContext as d}from"./base";const h={toggle:!0};function l(n){return`_cw-Button-${n??"filled"}`;}function B(n,t){return`_cw-Button-${t?.size??n??"md"}`;}function g(n,t){return(t?.shape??n)==="square";}function I(n,t){const e=p(d),o=t.toggle===!0,a=o&&("checked"in t?t.checked===!0:t.defaultChecked===!0),s=t.disabled===!0,i=t.href!==void 0,r={"aria-disabled":o&&t.readOnly===!0||s&&i?"true":void 0,"aria-pressed":o?a?"true":"false":void 0},c={...n.classList,"cw-Button":!0,"cw-Button-checked":a,"cw-Button-disabled":s,"cw-Button-toggle":o,"_cw-Button-square":g(t.shape,e),[B(t.size,e)]:!0,[l(t.variant)]:!0};return i?<a{...r}{...n}classList={c}href={t.href}tabindex={s?-1:t.tabIndex}>
1
+ import{createMemo as p,mergeProps as b,useContext as d}from"solid-js";import{defineInteractableComponent as h}from"../state";import{ActionButtonController as I,ActionLayoutContext as l}from"./base";const P={toggle:!0};function B(n){return`_cw-Button-${n??"filled"}`;}function g(n,t){return`_cw-Button-${t?.size??n??"md"}`;}function m(n,t){return(t?.shape??n)==="square";}function w(n,t){const e=d(l),o=t.toggle===!0,r=o&&("checked"in t?t.checked===!0:t.defaultChecked===!0),s=t.disabled===!0,a=t.href!==void 0,i=t.progress,A=o&&t.readOnly===!0,c={"aria-busy":i==="pending"?"true":void 0,"aria-disabled":A||i!==void 0||s&&a?"true":void 0,"aria-pressed":o?r?"true":"false":void 0},u={...n.classList,"cw-Button":!0,"cw-Button-checked":r,"cw-Button-disabled":s,"cw-Button-done":i==="done","cw-Button-pending":i==="pending","cw-Button-toggle":o,"_cw-Button-square":m(t.shape,e),[g(t.size,e)]:!0,[B(t.variant)]:!0};return a?<a{...c}{...n}classList={u}href={t.href}tabindex={s?-1:t.tabIndex}>
2
2
  <span class="cw-ActionButton-leadingIcon">{t.leadingIcon}</span>
3
3
  <span class="cw-ActionButton-label">{n.children}</span>
4
4
  <span class="cw-ActionButton-trailingIcon">{t.trailingIcon}</span>
5
- </a>:<button{...r}{...n}classList={c}disabled={s}tabindex={s?-1:t.tabIndex}type={t.type??"button"}>
5
+ </a>:<button{...c}{...n}classList={u}disabled={s}tabindex={s?-1:t.tabIndex}type={t.type??"button"}>
6
6
  <span class="cw-ActionButton-leadingIcon">{t.leadingIcon}</span>
7
7
  <span class="cw-ActionButton-label">{n.children}</span>
8
8
  <span class="cw-ActionButton-trailingIcon">{t.trailingIcon}</span>
9
- </button>;}const m=/* @__PURE__ */A(()=>I,()=>class extends b{#t;#n;#e;constructor(n){const t=p(d);super(n,"cw-Button"),this.#t=t,this.#n=void 0,this.#e=void 0;}applyComponentProps(){super.applyComponentProps();const n=super.readProps(["shape","size","variant"]),t=B(n.size,this.#t),e=g(n.shape,this.#t);n.variantChanged&&super.setClass("variant",l(n.variant)),t!==this.#n&&(this.#n=t,super.setClass("size",t)),e!==this.#e&&(this.#e=e,super.setClass("_cw-Button-square",e));}render(n){const t=u(()=>this.props.href!==void 0),e=()=>this.props.leadingIcon,o=()=>this.props.trailingIcon;return u(()=>t()?<a class="cw-Button">
9
+ {t.progressSlot?.(()=>t.progress)}
10
+ </button>;}const f=/* @__PURE__ */h(()=>w,()=>class extends I{#t;#n;#e;constructor(n){const t=d(l);super(n,"cw-Button"),this.#t=t,this.#n=void 0,this.#e=void 0;}applyComponentProps(){super.applyComponentProps();const n=super.readProps(["shape","size","variant"]),t=g(n.size,this.#t),e=m(n.shape,this.#t);n.variantChanged&&super.setClass("variant",B(n.variant)),t!==this.#n&&(this.#n=t,super.setClass("size",t)),e!==this.#e&&(this.#e=e,super.setClass("_cw-Button-square",e));}render(n){const t=p(()=>this.props.href!==void 0),e=()=>this.props.leadingIcon,o=()=>this.props.trailingIcon,r=()=>this.props.progress;return p(()=>t()?<a class="cw-Button">
10
11
  <span class="cw-ActionButton-leadingIcon">{e()}</span>
11
12
  <span class="cw-ActionButton-label">{n}</span>
12
13
  <span class="cw-ActionButton-trailingIcon">{o()}</span>
@@ -14,4 +15,5 @@ import{createMemo as u,mergeProps as f,useContext as p}from"solid-js";import{def
14
15
  <span class="cw-ActionButton-leadingIcon">{e()}</span>
15
16
  <span class="cw-ActionButton-label">{n}</span>
16
17
  <span class="cw-ActionButton-trailingIcon">{o()}</span>
17
- </button>);}}),E=m;function T(n){const t=f(n,h);return m(t);}export{E as Button,T as ToggleButton};
18
+ {this.props.progressSlot?.(r)}
19
+ </button>);}}),E=f;function T(n){const t=b(n,P);return f(t);}export{E as Button,f as ButtonBase,T as ToggleButton};
@@ -1 +1 @@
1
- import{Button as n,ToggleButton as r}from"./button";import{IconButton as u,IconToggleButton as p}from"./icon-button";import{FAB as i}from"./fab";import{ButtonGroup as g}from"./group";import{SplitButton as m}from"./split-button";import{FABMenu as f,FABMenuItem as s,FABMenuTrigger as l}from"./fab-menu";export{n as Button,g as ButtonGroup,i as FAB,f as FABMenu,s as FABMenuItem,l as FABMenuTrigger,u as IconButton,p as IconToggleButton,m as SplitButton,r as ToggleButton};
1
+ import{Button as r,ToggleButton as n}from"./button";import{ProgressButton as p}from"./progress-button";import{IconButton as B,IconToggleButton as i}from"./icon-button";import{FAB as s}from"./fab";import{ButtonGroup as m}from"./group";import{SplitButton as c}from"./split-button";import{FABMenu as P,FABMenuItem as x,FABMenuTrigger as l}from"./fab-menu";export{r as Button,m as ButtonGroup,s as FAB,P as FABMenu,x as FABMenuItem,l as FABMenuTrigger,B as IconButton,i as IconToggleButton,p as ProgressButton,c as SplitButton,n as ToggleButton};
@@ -0,0 +1 @@
1
+ @layer cw-style{.cw-Button:has(>.cw-ActionButton-progress){--_CW-BUTTON-PROGRESS_SIZE: cw-var(--cw-Button-progressIndicatorSize);max-inline-size:100%}.cw-Button:has(>.cw-ActionButton-progress)>:is(.cw-ActionButton-label,.cw-ActionButton-leadingIcon,.cw-ActionButton-trailingIcon){transition-delay:calc(cw-var(--cw-Motion-spring-effect-fast-duration)/2);transition-duration:cw-var(--cw-Motion-spring-effect-default-duration);transition-property:opacity;transition-timing-function:cw-var(--cw-Motion-spring-effect-default-easing)}.cw-Button:has(>.cw-ActionButton-progress)>.cw-ActionButton-label{text-overflow:clip}.cw-Button:is(.cw-Button-pending,.cw-Button-done){--_CW-ACTION-SHAPE: calc(cw-var(--cw-Button-containerHeight)/2) ;--cw-interactable-ripple-opacity: 0}.cw-Button:is(.cw-Button-pending,.cw-Button-done)>:is(.cw-ActionButton-label,.cw-ActionButton-leadingIcon,.cw-ActionButton-trailingIcon){opacity:0;transition-delay:0s;transition-duration:cw-var(--cw-Motion-spring-effect-fast-duration);transition-timing-function:cw-var(--cw-Motion-spring-effect-fast-easing)}@media (prefers-reduced-motion: no-preference){.cw-Button:has(>.cw-ActionButton-progress){--_CW-ACTION-SIZE_DURATION: cw-var(--cw-Motion-theme-standard--spring-spatial-fast-duration);--_CW-ACTION-SIZE_EASING: cw-var(--cw-Motion-theme-standard--spring-spatial-fast-easing)}.cw-Button:is(.cw-Button-pending,.cw-Button-done){inline-size:cw-var(--cw-Button-containerHeight);min-inline-size:cw-var(--cw-Button-containerHeight);padding-inline:0}}.cw-ActionButton-progress{display:grid;inset:0;place-items:center;pointer-events:none;position:absolute}.cw-ActionButton-progress>*{grid-area:1 / 1;opacity:0;scale:.5;transition-delay:0s,0s,cw-var(--cw-Motion-spring-effect-fast-duration);transition-duration:cw-var(--cw-Motion-spring-effect-fast-duration),cw-var(--cw-Motion-spring-spatial-fast-duration),0s;transition-property:opacity,scale,visibility;transition-timing-function:cw-var(--cw-Motion-spring-effect-fast-easing),cw-var(--cw-Motion-spring-spatial-fast-easing),linear;visibility:hidden}.cw-ActionButton-progress>.cw-Icon{color:var(--_CW-ACTION-ICON_COLOR)}.cw-ActionButton-progress>.cw-LoadingIndicator{block-size:var(--_CW-BUTTON-PROGRESS_SIZE);color:var(--_CW-ACTION-LABEL_COLOR);inline-size:var(--_CW-BUTTON-PROGRESS_SIZE)}.cw-Button-pending>.cw-ActionButton-progress>.cw-LoadingIndicator,.cw-Button-done>.cw-ActionButton-progress>.cw-Icon{opacity:1;scale:1;transition-delay:calc(cw-var(--cw-Motion-spring-effect-fast-duration)/2),calc(cw-var(--cw-Motion-spring-effect-fast-duration)/2),0s;transition-duration:cw-var(--cw-Motion-spring-effect-default-duration),cw-var(--cw-Motion-spring-spatial-fast-duration),0s;transition-timing-function:cw-var(--cw-Motion-spring-effect-default-easing),cw-var(--cw-Motion-spring-spatial-fast-easing),linear;visibility:visible}}
@@ -0,0 +1,4 @@
1
+ import{mergeProps as t}from"solid-js";import{LoadingIndicator as P}from"../feedback/loading";import{Icon as B}from"../icon";import{ButtonBase as l}from"./button";import"./progress-button.css";function d(o){return<span aria-hidden="true"class="cw-ActionButton-progress">
2
+ <P paused={o()!=="pending"}/>
3
+ <B name="check"/>
4
+ </span>;}const f={progressSlot:d};function E(o){const r=t(o,f);return l(r);}export{E as ProgressButton};
@@ -1 +1 @@
1
- import{Text as o}from"./text";import{Typography as r}from"./typography";export{o as Text,r as Typography};
1
+ import{Text as o}from"./text";import{Typography as t}from"./typography";import{VisuallyHidden as x}from"./visually-hidden";export{o as Text,t as Typography,x as VisuallyHidden};
@@ -0,0 +1 @@
1
+ @layer cw-style{.cw-VisuallyHidden{block-size:1px;clip-path:inset(50%);inline-size:1px;margin:-1px;overflow:hidden;position:absolute;white-space:nowrap}}
@@ -0,0 +1 @@
1
+ import{insert as n}from"solid-js/web";import{defineDomComponent as t}from"..";import"./visually-hidden.css";const p=/* @__PURE__ */t(()=>e=><span{...e}classList={{...e.classList,"cw-VisuallyHidden":!0}}/>,e=>class extends e{constructor(s){super(s,{styleElementIsFunction:!0});}render(s){const o=<span class="cw-VisuallyHidden"/>;return n(o,s),o;}});export{p as VisuallyHidden};
@@ -53,6 +53,21 @@ export type ActionButtonVariant = "elevated" | "filled" | "filled-tonal" | "outl
53
53
  * Action button sizes, from extra small (32px) to extra large (136px).
54
54
  */
55
55
  export type ActionButtonSize = "lg" | "md" | "sm" | "xl" | "xs";
56
+ /**
57
+ * Progress properties of action controls.
58
+ */
59
+ export type ActionProgressProps = {
60
+ /**
61
+ * Progress of the action: `"pending"` while it is under way, and `"done"` once it has finished,
62
+ * or `undefined` at rest.
63
+ *
64
+ * An action showing progress remains focusable, unlike a disabled action, and is exposed with
65
+ * `aria-disabled="true"`, and `aria-busy="true"` while pending. Its click is cancelled and its
66
+ * propagation stopped, so that it neither repeats the action nor submits a form: it reaches no
67
+ * `onClick` or `event:click` handler, and no listener of an ancestor.
68
+ */
69
+ progress?: "done" | "pending" | undefined;
70
+ };
56
71
  /**
57
72
  * Toggle state properties of action controls.
58
73
  *
@@ -136,18 +151,20 @@ export type ActionChangeHandler = (checked: boolean, controller: ActionButtonCon
136
151
  * | `<base>-disabled` | the button is disabled |
137
152
  * | `<base>-toggle` | the button is a toggle |
138
153
  * | `<base>-checked` | the toggle is checked |
154
+ * | `<base>-{pending,done}` | the action shows that progress |
139
155
  * | `<base>-{hover,focus,press}` | an interaction state is entered |
140
156
  *
141
157
  * Disabled buttons enter no interaction state. Read-only toggles enter only the focus state, so
142
- * that keyboard focus remains visible. A link has no `type`, and cannot be disabled natively: a
143
- * disabled link keeps its `href`, as a disabled tab or navigation destination does, is exposed with
144
- * `aria-disabled="true"`, and its activation by a click or <kbd>Enter</kbd> is cancelled. An
145
- * authored `attr:aria-pressed` or `attr:aria-disabled` replaces the respective managed attribute.
158
+ * that keyboard focus remains visible, and so do actions showing progress. A link has no `type`,
159
+ * and cannot be disabled natively: a disabled link keeps its `href`, as a disabled tab or
160
+ * navigation destination does, is exposed with `aria-disabled="true"`, and its activation by a
161
+ * click or <kbd>Enter</kbd> is cancelled. An authored `attr:aria-pressed` or `attr:aria-disabled`
162
+ * replaces the respective managed attribute.
146
163
  *
147
164
  * @typeParam P - component properties type, whose `ref` the bound omits, since its type is the type
148
165
  * of the function element
149
166
  */
150
- export declare abstract class ActionButtonController<P extends Omit<BaseActionButtonProps, "ref"> & ActionLinkProps> extends InteractableController<P> {
167
+ export declare abstract class ActionButtonController<P extends ActionLinkProps & ActionProgressProps & Omit<BaseActionButtonProps, "ref">> extends InteractableController<P> {
151
168
  #private;
152
169
  /**
153
170
  * Construct a new `ActionButtonController`.
@@ -201,9 +218,11 @@ export declare abstract class ActionButtonController<P extends Omit<BaseActionBu
201
218
  /**
202
219
  * Handle an interaction event, and request a new toggle state after a click.
203
220
  *
204
- * Events received by a disabled button are ignored. A click on a read-only toggle is cancelled.
205
- * Otherwise a click on a toggle requests the next state once the click has finished propagating
206
- * and was not cancelled; an uncontrolled toggle then commits that state.
221
+ * Events received by a disabled button are ignored. A click on an action showing progress is
222
+ * cancelled and stopped, as this runs in the capture phase, before any handler of the button. A
223
+ * click on a read-only toggle is cancelled. Otherwise a click on a toggle requests the next
224
+ * state once the click has finished propagating and was not cancelled; an uncontrolled toggle
225
+ * then commits that state.
207
226
  *
208
227
  * @param event - interaction event received
209
228
  */
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @packageDocumentation
10
10
  */
11
- export type { ActionButtonController, ActionButtonElementProps, ActionButtonSize, ActionButtonVariant, ActionChangeHandler, ActionLinkProps, ActionToggleProps, BaseActionButtonProps } from "./base";
11
+ export type { ActionButtonController, ActionButtonElementProps, ActionButtonSize, ActionButtonVariant, ActionChangeHandler, ActionLinkProps, ActionProgressProps, ActionToggleProps, BaseActionButtonProps } from "./base";
12
12
  export type { ButtonProps, ToggleButtonProps, ToggleButtonVariant } from "./button";
13
13
  export type { IconButtonProps, IconButtonSize, IconButtonVariant, IconToggleButtonProps } from "./icon-button";
14
14
  export type { FABProps, FABSize, FABVariant } from "./fab";
@@ -17,6 +17,8 @@ export type { SplitButtonProps } from "./split-button";
17
17
  export type { FABMenuItemProps, FABMenuProps, FABMenuTriggerFactory, FABMenuTriggerProps } from "./fab-menu";
18
18
  export type { FABMenuTriggerContextValue } from "./fab-menu-context";
19
19
  export { Button, ToggleButton } from "./button";
20
+ export type { ProgressButtonProps } from "./progress-button";
21
+ export { ProgressButton } from "./progress-button";
20
22
  export { IconButton, IconToggleButton } from "./icon-button";
21
23
  export { FAB } from "./fab";
22
24
  export { ButtonGroup } from "./group";
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ export {};
@@ -0,0 +1,46 @@
1
+ import type { JSX } from "solid-js";
2
+ import type { InteractableProps } from "../state";
3
+ import type { ActionButtonElementProps, ActionProgressProps } from "./base";
4
+ import type { ButtonProps } from "./button";
5
+ import "./progress-button.css";
6
+ /**
7
+ * Progress button properties.
8
+ *
9
+ * A progress button is always a native button, as a `ToggleButton` is: it stays where it is while
10
+ * its action runs, so it has neither an `href` nor the native attributes of a link.
11
+ */
12
+ export type ProgressButtonProps = ActionButtonElementProps & ActionProgressProps & Pick<ButtonProps, "leadingIcon" | "shape" | "size" | "trailingIcon" | "variant">;
13
+ /**
14
+ * Common button which shows the progress of the action it started.
15
+ *
16
+ * The button transitions to a circle holding a loading indicator while its {@link
17
+ * ActionProgressProps.progress | progress} is `"pending"`, and holding a check while it is
18
+ * `"done"`. Its label and icons fade out. Importing this component is what brings the loading
19
+ * indicator into a page: a page with common buttons alone has no use for it.
20
+ *
21
+ * The button has to have a definite inline size at rest to transition from, since a transition
22
+ * does not start from `auto`; a button sized by its content changes to the circle at once. Assign
23
+ * the size by a rule which does not apply to `cw-Button-pending` and `cw-Button-done`, and which
24
+ * does apply to them where motion is reduced, as the button then keeps the size assigned to it. A
25
+ * progress button in a connected `ButtonGroup` or a `SplitButton` is not supported, as its
26
+ * neighbors would move with it.
27
+ *
28
+ * # Design
29
+ *
30
+ * The button is designed as a {@link ./button#Button | common button}. The
31
+ * `cw-Button-progressIndicatorSize` role sizes the loading indicator.
32
+ *
33
+ * # Elements
34
+ *
35
+ * The button renders the elements of a common button, and the following element.
36
+ *
37
+ * | Class | Element |
38
+ * | -------------------------- | ------------- |
39
+ * | `cw-ActionButton-progress` | progress slot |
40
+ *
41
+ * The button applies `cw-Button-pending` and `cw-Button-done` while showing that progress.
42
+ *
43
+ * @param props - component properties
44
+ * @returns rendered component elements
45
+ */
46
+ export declare function ProgressButton(props: InteractableProps<ProgressButtonProps>): JSX.Element;
@@ -2,9 +2,10 @@
2
2
  * Typography and typescale definitions.
3
3
  *
4
4
  * This module defines the {@link Text} component, which renders text according to the baseline
5
- * and emphasized styles of the [`cw-Typescale`](./typescale.yaml) design attribute, and the {@link
5
+ * and emphasized styles of the [`cw-Typescale`](./typescale.yaml) design attribute, the {@link
6
6
  * Typography} component, which selects the [`cw-LineHeight`](./line-height.yaml) category of a
7
- * subtree by language.
7
+ * subtree by language, and the {@link VisuallyHidden} component, which holds text for assistive
8
+ * technology alone.
8
9
  *
9
10
  * Fonts are named by the [`cw-Typeface`](./typeface.yaml) design attribute; Cobweb does not
10
11
  * bundle them, so applications load the named fonts, for example with `@font-face` rules. The
@@ -16,3 +17,5 @@ export type { TextEmphasis, TextProps, TextRole, TextSize, Typescale } from "./t
16
17
  export { Text } from "./text";
17
18
  export type { TypographyHeight, TypographyProps } from "./typography";
18
19
  export { Typography } from "./typography";
20
+ export type { VisuallyHiddenProps } from "./visually-hidden";
21
+ export { VisuallyHidden } from "./visually-hidden";
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { JSX } from "solid-js";
2
+ import type { DomComponent, DomProps } from "..";
3
+ import "./visually-hidden.css";
4
+ /**
5
+ * {@link VisuallyHidden} component properties.
6
+ */
7
+ export type VisuallyHiddenProps = DomProps<HTMLSpanElement, JSX.HTMLElementTags["span"]>;
8
+ /**
9
+ * Content for assistive technology alone.
10
+ *
11
+ * The content is clipped to a single pixel and positioned out of flow, so that it takes no room
12
+ * and is not painted, and unlike with `display: none` it remains in the accessibility tree. Use
13
+ * this for content which a page conveys visually in another way, such as a status region. Native
14
+ * attributes, such as `attr:role="status"`, apply to the rendered element.
15
+ *
16
+ * ```tsx
17
+ * import type { JSX, VoidProps } from "solid-js";
18
+ *
19
+ * import { VisuallyHidden } from "@pg-labs-incubator/cobweb/text";
20
+ *
21
+ * export default function(_props: VoidProps): JSX.Element {
22
+ * return <VisuallyHidden attr:role="status">Saved.</VisuallyHidden>;
23
+ * }
24
+ * ```
25
+ *
26
+ * # Design
27
+ *
28
+ * The rendered element applies `cw-VisuallyHidden`, and has no design roles.
29
+ *
30
+ * @param props - text properties
31
+ * @returns rendered element
32
+ */
33
+ export declare const VisuallyHidden: DomComponent<VisuallyHiddenProps>;