@timmo001/groundwork-ui 0.9.20 → 0.9.21

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.
@@ -3722,9 +3722,17 @@
3722
3722
  "description": "The heading.",
3723
3723
  "name": "heading"
3724
3724
  },
3725
+ {
3726
+ "description": "The line under the heading.",
3727
+ "name": "description"
3728
+ },
3725
3729
  {
3726
3730
  "description": "The content.",
3727
3731
  "name": "body"
3732
+ },
3733
+ {
3734
+ "description": "The area under the divider.",
3735
+ "name": "footer"
3728
3736
  }
3729
3737
  ],
3730
3738
  "slots": [
@@ -3739,6 +3747,10 @@
3739
3747
  {
3740
3748
  "description": "Buttons beside the heading.",
3741
3749
  "name": "actions"
3750
+ },
3751
+ {
3752
+ "description": "Content under a divider at the bottom, such as a control for the whole card.",
3753
+ "name": "footer"
3742
3754
  }
3743
3755
  ],
3744
3756
  "members": [
@@ -3749,7 +3761,7 @@
3749
3761
  "text": "Properties"
3750
3762
  },
3751
3763
  "static": true,
3752
- "default": "{ heading: {}, href: {}, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
3764
+ "default": "{ heading: {}, description: {}, level: { type: \"number\" }, href: {}, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
3753
3765
  "inheritedFrom": {
3754
3766
  "name": "GwElement",
3755
3767
  "module": "src/element.ts"
@@ -3762,7 +3774,7 @@
3762
3774
  "text": "readonly CSSStyleSheet[]"
3763
3775
  },
3764
3776
  "static": true,
3765
- "default": "[ base, css` :host { display: block; } .card { display: flex; flex-direction: column; gap: var(--gw-space-3); height: 100%; padding: var(--gw-space-4); color: inherit; text-decoration: none; background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-s); } :host([flush]) .card { padding: 0; overflow: hidden; } a.card { transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } a.card:hover, .card.linked:hover { border-color: var(--gw-color-border-accent); } /* A card following a link in its heading slot */ .card.linked { cursor: pointer; transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } .card.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } ::slotted([slot=\"heading\"]) { font: inherit; color: inherit; } ::slotted(a[slot=\"heading\"]) { text-decoration: none; outline: none; } header { display: flex; gap: var(--gw-space-2); align-items: center; } h2 { flex: 1; margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body:not([hidden]) { display: flex; flex-direction: column; gap: inherit; } `, ]",
3777
+ "default": "[ base, css` :host { display: block; } .card { display: flex; flex-direction: column; gap: var(--gw-space-3); height: 100%; padding: var(--gw-space-4); color: inherit; text-decoration: none; background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-s); } :host([flush]) .card { padding: 0; overflow: hidden; } a.card { transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } a.card:hover, .card.linked:hover { border-color: var(--gw-color-border-accent); } /* A card following a link in its heading slot */ .card.linked { cursor: pointer; transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } .card.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } ::slotted([slot=\"heading\"]) { font: inherit; color: inherit; } ::slotted(a[slot=\"heading\"]) { text-decoration: none; outline: none; } header { display: flex; gap: var(--gw-space-2); align-items: center; } .titles { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } .title { margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } /* The divider runs to the card's edges */ footer:not([hidden]) { display: flex; flex-direction: column; gap: inherit; margin-inline: calc(-1 * var(--gw-space-4)); padding: var(--gw-space-3) var(--gw-space-4) 0; border-top: var(--gw-border-width) solid var(--gw-color-border-normal); } :host([flush]) footer:not([hidden]) { margin-inline: 0; padding-bottom: var(--gw-space-3); } .body:not([hidden]) { display: flex; flex-direction: column; gap: inherit; } `, ]",
3766
3778
  "inheritedFrom": {
3767
3779
  "name": "GwElement",
3768
3780
  "module": "src/element.ts"
@@ -3776,6 +3788,23 @@
3776
3788
  },
3777
3789
  "default": "\"\""
3778
3790
  },
3791
+ {
3792
+ "kind": "field",
3793
+ "name": "description",
3794
+ "type": {
3795
+ "text": "string"
3796
+ },
3797
+ "default": "\"\""
3798
+ },
3799
+ {
3800
+ "kind": "field",
3801
+ "name": "level",
3802
+ "type": {
3803
+ "text": "number"
3804
+ },
3805
+ "description": "The heading's level for assistive tech.",
3806
+ "default": "2"
3807
+ },
3779
3808
  {
3780
3809
  "kind": "field",
3781
3810
  "name": "href",
@@ -3827,6 +3856,18 @@
3827
3856
  "privacy": "private",
3828
3857
  "readonly": true
3829
3858
  },
3859
+ {
3860
+ "kind": "field",
3861
+ "name": "#heading",
3862
+ "privacy": "private",
3863
+ "readonly": true
3864
+ },
3865
+ {
3866
+ "kind": "field",
3867
+ "name": "#description",
3868
+ "privacy": "private",
3869
+ "readonly": true
3870
+ },
3830
3871
  {
3831
3872
  "kind": "field",
3832
3873
  "name": "#header",
@@ -3836,6 +3877,21 @@
3836
3877
  },
3837
3878
  "readonly": true
3838
3879
  },
3880
+ {
3881
+ "kind": "field",
3882
+ "name": "#footerSlot",
3883
+ "privacy": "private",
3884
+ "readonly": true
3885
+ },
3886
+ {
3887
+ "kind": "field",
3888
+ "name": "#footer",
3889
+ "privacy": "private",
3890
+ "type": {
3891
+ "text": "HTMLElement"
3892
+ },
3893
+ "readonly": true
3894
+ },
3839
3895
  {
3840
3896
  "kind": "field",
3841
3897
  "name": "#skeleton",
@@ -4208,6 +4264,20 @@
4208
4264
  "description": "The card's heading.",
4209
4265
  "name": "heading"
4210
4266
  },
4267
+ {
4268
+ "type": {
4269
+ "text": "string"
4270
+ },
4271
+ "description": "A line under the heading.",
4272
+ "name": "description"
4273
+ },
4274
+ {
4275
+ "type": {
4276
+ "text": "number"
4277
+ },
4278
+ "description": "The heading's level for assistive tech, 2 by default. Use 3 for a card under a section heading.",
4279
+ "name": "level"
4280
+ },
4211
4281
  {
4212
4282
  "type": {
4213
4283
  "text": "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.20",
3
+ "version": "0.9.21",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/gw-card.ts CHANGED
@@ -25,6 +25,8 @@ import { base } from "./styles.js";
25
25
  * @summary A surface for one group of content, or a link to more.
26
26
  *
27
27
  * @attr {string} heading - The card's heading.
28
+ * @attr {string} description - A line under the heading.
29
+ * @attr {number} level - The heading's level for assistive tech, 2 by default. Use 3 for a card under a section heading.
28
30
  * @attr {string} href - Makes the whole card a link to this address.
29
31
  * @attr {boolean} flush - Drops the padding, for content that runs to the edges.
30
32
  * @attr {boolean} loading - Shows skeleton lines for the content and any unknown heading.
@@ -33,15 +35,20 @@ import { base } from "./styles.js";
33
35
  * @slot - The content.
34
36
  * @slot heading - The heading, in place of `heading`. An `<a>` here makes the whole card follow it.
35
37
  * @slot actions - Buttons beside the heading.
38
+ * @slot footer - Content under a divider at the bottom, such as a control for the whole card.
36
39
  *
37
40
  * @csspart card - The card's surface.
38
41
  * @csspart header - The row with the heading and actions.
39
42
  * @csspart heading - The heading.
43
+ * @csspart description - The line under the heading.
40
44
  * @csspart body - The content.
45
+ * @csspart footer - The area under the divider.
41
46
  */
42
47
  export class GwCard extends GwElement {
43
48
  static override properties = {
44
49
  heading: {},
50
+ description: {},
51
+ level: { type: "number" },
45
52
  href: {},
46
53
  flush: { type: "boolean", reflect: true },
47
54
  loading: { type: "boolean", reflect: true },
@@ -110,14 +117,41 @@ export class GwCard extends GwElement {
110
117
  align-items: center;
111
118
  }
112
119
 
113
- h2 {
120
+ .titles {
121
+ display: flex;
114
122
  flex: 1;
123
+ flex-direction: column;
124
+ gap: var(--gw-space-0-5);
125
+ min-width: 0;
126
+ }
127
+
128
+ .title {
115
129
  margin: 0;
116
130
  font-size: var(--gw-font-size-m);
117
131
  font-weight: var(--gw-font-weight-strong);
118
132
  color: var(--gw-color-text-strong);
119
133
  }
120
134
 
135
+ .description {
136
+ font-size: var(--gw-font-size-xs);
137
+ color: var(--gw-color-text-secondary);
138
+ }
139
+
140
+ /* The divider runs to the card's edges */
141
+ footer:not([hidden]) {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: inherit;
145
+ margin-inline: calc(-1 * var(--gw-space-4));
146
+ padding: var(--gw-space-3) var(--gw-space-4) 0;
147
+ border-top: var(--gw-border-width) solid var(--gw-color-border-normal);
148
+ }
149
+
150
+ :host([flush]) footer:not([hidden]) {
151
+ margin-inline: 0;
152
+ padding-bottom: var(--gw-space-3);
153
+ }
154
+
121
155
  .body:not([hidden]) {
122
156
  display: flex;
123
157
  flex-direction: column;
@@ -127,6 +161,9 @@ export class GwCard extends GwElement {
127
161
  ];
128
162
 
129
163
  declare heading: string;
164
+ declare description: string;
165
+ /** The heading's level for assistive tech. */
166
+ declare level: number;
130
167
  declare href: string;
131
168
  declare flush: boolean;
132
169
  declare loading: boolean;
@@ -139,19 +176,34 @@ export class GwCard extends GwElement {
139
176
 
140
177
  readonly #headingSlot = h("slot", { name: "heading" });
141
178
 
179
+ readonly #heading = h(
180
+ "div",
181
+ { class: "title", part: "heading", role: "heading", "aria-level": "2" },
182
+ this.#title,
183
+ this.#headingSlot,
184
+ this.#titleSkeleton,
185
+ );
186
+
187
+ readonly #description = h("span", {
188
+ class: "description",
189
+ part: "description",
190
+ });
191
+
142
192
  readonly #header: HTMLElement = h(
143
193
  "header",
144
194
  { part: "header" },
145
- h(
146
- "h2",
147
- { part: "heading" },
148
- this.#title,
149
- this.#headingSlot,
150
- this.#titleSkeleton,
151
- ),
195
+ h("div", { class: "titles" }, this.#heading, this.#description),
152
196
  h("slot", { name: "actions" }),
153
197
  );
154
198
 
199
+ readonly #footerSlot = h("slot", { name: "footer" });
200
+
201
+ readonly #footer: HTMLElement = h(
202
+ "footer",
203
+ { part: "footer", hidden: "" },
204
+ this.#footerSlot,
205
+ );
206
+
155
207
  readonly #skeleton = h("gw-skeleton-lines");
156
208
 
157
209
  readonly #body: HTMLDivElement = h(
@@ -169,6 +221,8 @@ export class GwCard extends GwElement {
169
221
  constructor() {
170
222
  super();
171
223
  this.heading = "";
224
+ this.description = "";
225
+ this.level = 2;
172
226
  this.href = "";
173
227
  this.flush = false;
174
228
  this.loading = false;
@@ -215,6 +269,11 @@ export class GwCard extends GwElement {
215
269
  this.#headingSlot.addEventListener("slotchange", () => {
216
270
  this.requestUpdate("heading");
217
271
  });
272
+
273
+ this.#footerSlot.addEventListener("slotchange", () => {
274
+ this.#footer.hidden =
275
+ this.#footerSlot.assignedNodes({ flatten: true }).length === 0;
276
+ });
218
277
  }
219
278
 
220
279
  /** The `<a>` in the heading slot, or holding it, when there is one. */
@@ -227,7 +286,7 @@ export class GwCard extends GwElement {
227
286
  }
228
287
 
229
288
  protected override create(root: ShadowRoot) {
230
- this.#card.append(this.#header, this.#skeleton, this.#body);
289
+ this.#card.append(this.#header, this.#skeleton, this.#body, this.#footer);
231
290
  root.append(this.#card);
232
291
  }
233
292
 
@@ -260,9 +319,14 @@ export class GwCard extends GwElement {
260
319
 
261
320
  this.#card.classList.toggle("linked", this.#link() !== null && !this.href);
262
321
  this.#title.textContent = this.heading;
322
+ this.#heading.setAttribute("aria-level", String(this.level || 2));
323
+ this.#description.textContent = this.description;
324
+ this.#description.hidden = !this.description;
263
325
  this.#titleSkeleton.hidden =
264
326
  this.heading !== "" || slotted || !this.loading;
265
- this.#header.hidden = !this.heading && !slotted && !this.loading;
327
+ this.#header.hidden =
328
+ !this.heading && !this.description && !slotted && !this.loading;
329
+ this.#heading.hidden = !this.heading && !slotted && !this.loading;
266
330
  this.#skeleton.lines = this.lines;
267
331
  this.#skeleton.hidden = !this.loading;
268
332
  this.#body.hidden = this.loading;