@crowdstrike/glide-core 0.9.0 → 0.9.1

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.
Files changed (60) hide show
  1. package/dist/accordion.d.ts +7 -3
  2. package/dist/button-group.button.d.ts +1 -4
  3. package/dist/button-group.button.styles.js +1 -0
  4. package/dist/button-group.d.ts +3 -0
  5. package/dist/button.d.ts +3 -0
  6. package/dist/checkbox-group.d.ts +6 -2
  7. package/dist/checkbox-group.stories.d.ts +1 -1
  8. package/dist/checkbox.d.ts +5 -4
  9. package/dist/checkbox.js +1 -1
  10. package/dist/checkbox.stories.d.ts +1 -1
  11. package/dist/checkbox.test.basics.js +15 -6
  12. package/dist/checkbox.test.events.js +12 -4
  13. package/dist/checkbox.test.focus.js +1 -1
  14. package/dist/checkbox.test.form.js +1 -0
  15. package/dist/checkbox.test.interactions.js +44 -7
  16. package/dist/drawer.d.ts +5 -5
  17. package/dist/drawer.stories.d.ts +0 -1
  18. package/dist/dropdown.d.ts +3 -2
  19. package/dist/icon-button.d.ts +2 -0
  20. package/dist/input.d.ts +4 -3
  21. package/dist/input.js +1 -1
  22. package/dist/label.styles.js +7 -4
  23. package/dist/library/localize.d.ts +1 -0
  24. package/dist/menu.js +1 -1
  25. package/dist/menu.test.events.js +197 -7
  26. package/dist/menu.test.interactions.js +2 -2
  27. package/dist/modal.stories.d.ts +1 -0
  28. package/dist/modal.tertiary-icon.test.basics.js +2 -2
  29. package/dist/radio-group.d.ts +4 -3
  30. package/dist/radio-group.stories.d.ts +1 -1
  31. package/dist/radio.d.ts +1 -2
  32. package/dist/radio.js +1 -1
  33. package/dist/split-container.d.ts +1 -1
  34. package/dist/status-indicator.d.ts +1 -1
  35. package/dist/tabs.stories.d.ts +1 -0
  36. package/dist/tag.d.ts +3 -6
  37. package/dist/textarea.d.ts +4 -3
  38. package/dist/toggle.d.ts +3 -3
  39. package/dist/toggle.js +1 -1
  40. package/dist/toggle.stories.d.ts +1 -1
  41. package/dist/toggle.test.interactions.js +26 -0
  42. package/dist/tooltip.d.ts +2 -2
  43. package/dist/translations/en.js +1 -1
  44. package/dist/translations/fr.js +1 -1
  45. package/dist/translations/ja.js +1 -1
  46. package/dist/tree.d.ts +1 -1
  47. package/dist/tree.item.d.ts +0 -3
  48. package/dist/tree.item.icon-button.d.ts +1 -0
  49. package/dist/tree.item.icon-button.js +1 -1
  50. package/dist/tree.item.icon-button.test.basics.js +9 -0
  51. package/dist/tree.item.js +1 -1
  52. package/dist/tree.item.menu.d.ts +2 -0
  53. package/dist/tree.item.menu.js +1 -1
  54. package/dist/tree.item.menu.test.basics.js +15 -0
  55. package/dist/tree.item.styles.js +2 -0
  56. package/dist/tree.item.test.basics.d.ts +2 -1
  57. package/dist/tree.item.test.basics.js +46 -4
  58. package/dist/tree.js +1 -1
  59. package/dist/tree.test.focus.js +91 -4
  60. package/package.json +2 -1
@@ -129,11 +129,13 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
129
129
  display: flex;
130
130
  flex-direction: column;
131
131
  overflow: hidden;
132
+ visibility: hidden;
132
133
  }
133
134
 
134
135
  .expanded {
135
136
  .child-items {
136
137
  block-size: auto;
138
+ visibility: visible;
137
139
  }
138
140
  }
139
141
 
@@ -1 +1,2 @@
1
- export {};
1
+ import './menu.link.js';
2
+ import './tree.item.menu.js';
@@ -1,7 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
+ import './menu.link.js';
3
+ import './tree.item.menu.js';
2
4
  import { expect, fixture, html } from '@open-wc/testing';
5
+ import Menu from './menu.js';
3
6
  import TreeItem from './tree.item.js';
7
+ import TreeItemMenu from './tree.item.menu.js';
4
8
  TreeItem.shadowRootOptions.mode = 'open';
9
+ TreeItemMenu.shadowRootOptions.mode = 'open';
10
+ Menu.shadowRootOptions.mode = 'open';
5
11
  it('registers', () => {
6
12
  expect(window.customElements.get('glide-core-tree-item')).to.equal(TreeItem);
7
13
  });
@@ -32,13 +38,49 @@ it('renders with a prefix slot', async () => {
32
38
  `);
33
39
  expect(document.querySelector('[data-prefix]')).to.be.ok;
34
40
  });
35
- it('renders with a menu slot', async () => {
36
- await fixture(html `
41
+ it('adds label to menu target', async () => {
42
+ const treeItem = await fixture(html `
43
+ <glide-core-tree-item label="Item">
44
+ <glide-core-tree-item-menu slot="menu" data-menu>
45
+ <glide-core-menu-link label="Move" url="/move"> </glide-core-menu-link>
46
+ </glide-core-tree-item-menu>
47
+ </glide-core-tree-item>
48
+ `);
49
+ const menuTarget = treeItem
50
+ .querySelector('glide-core-tree-item-menu')
51
+ ?.shadowRoot?.querySelector('glide-core-menu')
52
+ ?.querySelector('glide-core-icon-button');
53
+ expect(menuTarget?.label).to.equal('Actions for Item');
54
+ });
55
+ it('adds Japanese label to menu', async () => {
56
+ document.documentElement.setAttribute('lang', 'ja');
57
+ const treeItem = await fixture(html `
58
+ <glide-core-tree-item label="Item">
59
+ <glide-core-tree-item-menu slot="menu" data-menu>
60
+ <glide-core-menu-link label="Move" url="/move"> </glide-core-menu-link>
61
+ </glide-core-tree-item-menu>
62
+ </glide-core-tree-item>
63
+ `);
64
+ const menuTarget = treeItem
65
+ .querySelector('glide-core-tree-item-menu')
66
+ ?.shadowRoot?.querySelector('glide-core-menu')
67
+ ?.querySelector('glide-core-icon-button');
68
+ expect(menuTarget?.label).to.equal('Actions for Item');
69
+ });
70
+ it('adds French label to menu', async () => {
71
+ document.documentElement.setAttribute('lang', 'fr');
72
+ const treeItem = await fixture(html `
37
73
  <glide-core-tree-item label="Item">
38
- <span slot="menu" data-menu>menu</span>
74
+ <glide-core-tree-item-menu slot="menu" data-menu>
75
+ <glide-core-menu-link label="Move" url="/move"> </glide-core-menu-link>
76
+ </glide-core-tree-item-menu>
39
77
  </glide-core-tree-item>
40
78
  `);
41
- expect(document.querySelector('[data-menu]')).to.be.ok;
79
+ const menuTarget = treeItem
80
+ .querySelector('glide-core-tree-item-menu')
81
+ ?.shadowRoot?.querySelector('glide-core-menu')
82
+ ?.querySelector('glide-core-icon-button');
83
+ expect(menuTarget?.label).to.equal('Actions for Item');
42
84
  });
43
85
  it('renders with a suffix slot', async () => {
44
86
  await fixture(html `
package/dist/tree.js CHANGED
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,s){var l,r=arguments.length,i=r<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,o):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,s);else for(var n=e.length-1;n>=0;n--)(l=e[n])&&(i=(r<3?l(i):r>3?l(t,o,i):l(t,o))||i);return r>3&&i&&Object.defineProperty(t,o,i),i};import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,queryAssignedElements,state}from"lit/decorators.js";import{owSlot,owSlotType}from"./library/ow.js";import GlideCoreTreeItem from"./tree.item.js";import styles from"./tree.styles.js";let GlideCoreTree=class GlideCoreTree extends LitElement{static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("focusin",this.#e),this.removeEventListener("focusout",this.#t)}firstUpdated(){owSlot(this.#o.value),owSlotType(this.#o.value,[GlideCoreTreeItem])}render(){return html`<div class="component" role="tree" tabindex="${this.privateTabIndex}" @click="${this.#s}" @keydown="${this.#l}"><slot @slotchange="${this.#r}" ${ref(this.#o)}></slot></div>`}selectItem(e){for(const t of this.slotElements){e===t?(t.setAttribute("selected","true"),this.selectedItem=t):t.removeAttribute("selected");const o=t.selectItem(e);o&&(this.selectedItem=o)}this.dispatchEvent(new CustomEvent("item-selected",{bubbles:!0,detail:e}))}constructor(){super(),this.privateTabIndex=0,this.#o=createRef(),this.addEventListener("focusin",this.#e),this.addEventListener("focusout",this.#t)}#o;#i(e){e?.focus(),this.focusedItem=e}#n(){return[...this.querySelectorAll("glide-core-tree-item")]}#d(){const e=this.#n(),t=new Set;return e.filter((e=>{const o=e.parentElement?.closest("glide-core-tree-item");return!o||o.expanded&&!t.has(o)||t.add(e),!t.has(e)}))}#s(e){const t=e.target;if(t.closest("glide-core-tree-item-icon-button")??t.closest("glide-core-tree-item-menu"))return;const o=t.closest("glide-core-tree-item");o&&(o.hasChildTreeItems&&!o.nonCollapsible?o.toggleExpand():this.selectItem(o))}#e(e){let t;e.target===this?t=this.selectedItem??this.slotElements[0]:e.target instanceof GlideCoreTreeItem&&(t=e.target,this.privateTabIndex=-1),this.#i(t)}#t(e){const t=e.relatedTarget;t&&this.contains(t)||(this.privateTabIndex=0,this.focusedItem=void 0)}#l(e){if(!["ArrowRight","ArrowLeft","ArrowDown","ArrowUp","Home","End","Enter"].includes(e.key))return;const t=this.#d(),{focusedItem:o}=this,s=t.findIndex((e=>e.matches(":focus")));if("ArrowRight"===e.key&&o?.hasChildTreeItems&&(o.expanded?this.#i(o.slotElements[0]):o.toggleExpand()),"ArrowLeft"===e.key)if(o?.expanded&&!o.nonCollapsible)o.toggleExpand();else{const e=o?.parentElement?.closest("glide-core-tree-item");this.#i(e)}"ArrowDown"===e.key&&-1!==s&&s<t.length-1&&this.#i(t[s+1]),"ArrowUp"===e.key&&s>0&&this.#i(t[s-1]),"Home"===e.key&&this.#i(t[0]),"End"===e.key&&this.#i(t.at(-1)),"Enter"===e.key&&o&&(o.hasChildTreeItems&&!o.nonCollapsible?o.toggleExpand():this.selectItem(o))}#r(){owSlot(this.#o.value),owSlotType(this.#o.value,[GlideCoreTreeItem])}};__decorate([state()],GlideCoreTree.prototype,"selectedItem",void 0),__decorate([state()],GlideCoreTree.prototype,"focusedItem",void 0),__decorate([state()],GlideCoreTree.prototype,"privateTabIndex",void 0),__decorate([queryAssignedElements()],GlideCoreTree.prototype,"slotElements",void 0),GlideCoreTree=__decorate([customElement("glide-core-tree")],GlideCoreTree);export default GlideCoreTree;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,s){var r,l=arguments.length,i=l<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,o):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,s);else for(var n=e.length-1;n>=0;n--)(r=e[n])&&(i=(l<3?r(i):l>3?r(t,o,i):r(t,o))||i);return l>3&&i&&Object.defineProperty(t,o,i),i};import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,queryAssignedElements,state}from"lit/decorators.js";import{owSlot,owSlotType}from"./library/ow.js";import GlideCoreTreeItem from"./tree.item.js";import styles from"./tree.styles.js";let GlideCoreTree=class GlideCoreTree extends LitElement{static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("focusin",this.#e),this.removeEventListener("focusout",this.#t)}firstUpdated(){owSlot(this.#o.value),owSlotType(this.#o.value,[GlideCoreTreeItem])}render(){return html`<div class="component" role="tree" tabindex="${this.privateTabIndex}" @click="${this.#s}" @keydown="${this.#r}"><slot @slotchange="${this.#l}" ${ref(this.#o)}></slot></div>`}selectItem(e){for(const t of this.slotElements){e===t?(t.setAttribute("selected","true"),this.selectedItem=t):t.removeAttribute("selected");const o=t.selectItem(e);o&&(this.selectedItem=o)}this.dispatchEvent(new CustomEvent("item-selected",{bubbles:!0,detail:e}))}constructor(){super(),this.privateTabIndex=0,this.#o=createRef(),this.addEventListener("focusin",this.#e),this.addEventListener("focusout",this.#t)}#o;#i(e){e?.focus(),this.focusedItem=e}#n(){return[...this.querySelectorAll("glide-core-tree-item")]}#d(){const e=this.#n(),t=new Set;return e.filter((e=>{const o=e.parentElement?.closest("glide-core-tree-item");return!o||o.expanded&&!t.has(o)||t.add(e),!t.has(e)}))}#s(e){const t=e.target;if(t.closest("glide-core-tree-item-icon-button")??t.closest("glide-core-tree-item-menu"))return;const o=t.closest("glide-core-tree-item");o&&(o.hasChildTreeItems&&!o.nonCollapsible?o.toggleExpand():this.selectItem(o))}#e(e){let t;e.target===this?t=this.selectedItem?.checkVisibility({visibilityProperty:!0})?this.selectedItem:this.slotElements[0]:e.target instanceof GlideCoreTreeItem&&(t=e.target,this.privateTabIndex=-1),this.#i(t)}#t(e){e.relatedTarget&&e.relatedTarget instanceof HTMLElement&&this.contains(e.relatedTarget)||(this.privateTabIndex=0,this.focusedItem=void 0)}#r(e){if(!["ArrowRight","ArrowLeft","ArrowDown","ArrowUp","Home","End","Enter"].includes(e.key))return;if(e.target&&e.target instanceof HTMLElement&&(e.target.closest("glide-core-tree-item-icon-button")??e.target.closest("glide-core-tree-item-menu")))return;const t=this.#d(),{focusedItem:o}=this,s=t.findIndex((e=>e.matches(":focus")));if("ArrowRight"===e.key&&o?.hasChildTreeItems&&(o.expanded?this.#i(o.slotElements[0]):o.toggleExpand()),"ArrowLeft"===e.key)if(o?.expanded&&!o.nonCollapsible)o.toggleExpand();else{const e=o?.parentElement?.closest("glide-core-tree-item");this.#i(e)}"ArrowDown"===e.key&&-1!==s&&s<t.length-1&&this.#i(t[s+1]),"ArrowUp"===e.key&&s>0&&this.#i(t[s-1]),"Home"===e.key&&this.#i(t[0]),"End"===e.key&&this.#i(t.at(-1)),"Enter"===e.key&&o&&(o.hasChildTreeItems&&!o.nonCollapsible?o.toggleExpand():this.selectItem(o))}#l(){owSlot(this.#o.value),owSlotType(this.#o.value,[GlideCoreTreeItem])}};__decorate([state()],GlideCoreTree.prototype,"selectedItem",void 0),__decorate([state()],GlideCoreTree.prototype,"focusedItem",void 0),__decorate([state()],GlideCoreTree.prototype,"privateTabIndex",void 0),__decorate([queryAssignedElements()],GlideCoreTree.prototype,"slotElements",void 0),GlideCoreTree=__decorate([customElement("glide-core-tree")],GlideCoreTree);export default GlideCoreTree;
@@ -34,6 +34,23 @@ it('focuses the selected tree item on `focus()`, if there is one', async () => {
34
34
  assert(document.activeElement instanceof GlideCoreTreeItem);
35
35
  expect(document.activeElement?.label).to.equal(childItems[1].label);
36
36
  });
37
+ it('does not focus the selected tree item on `focus()` if collapsed', async () => {
38
+ const tree = await fixture(html `
39
+ <glide-core-tree>
40
+ <glide-core-tree-item label="Child Item 1"> </glide-core-tree-item>
41
+ <glide-core-tree-item label="Child Item 2">
42
+ <glide-core-tree-item
43
+ label="Grandchild Item 1"
44
+ selected
45
+ ></glide-core-tree-item>
46
+ </glide-core-tree-item>
47
+ </glide-core-tree>
48
+ `);
49
+ const childItems = tree.slotElements;
50
+ tree.dispatchEvent(new Event('focusin'));
51
+ await tree.updateComplete;
52
+ expect(document.activeElement === childItems[0]).to.equal(true);
53
+ });
37
54
  it('expands a tree item if right arrow is pressed', async () => {
38
55
  const tree = await fixture(html `
39
56
  <glide-core-tree>
@@ -100,8 +117,12 @@ it(`focuses on a non-collapsible tree item's parent if left arrow is pressed`, a
100
117
  it(`focuses on a collapsed tree item's parent if left arrow is pressed`, async () => {
101
118
  const tree = await fixture(html `
102
119
  <glide-core-tree>
103
- <glide-core-tree-item label="Child Item 1">
104
- <glide-core-tree-item label="Grandchild Item 1"></glide-core-tree-item>
120
+ <glide-core-tree-item expanded label="Child Item 1">
121
+ <glide-core-tree-item label="Grandchild Item 1">
122
+ <glide-core-tree-item
123
+ label="Great Grandchild Item 1"
124
+ ></glide-core-tree-item>
125
+ </glide-core-tree-item>
105
126
  </glide-core-tree-item>
106
127
  <glide-core-tree-item label="Child Item 2"></glide-core-tree-item>
107
128
  </glide-core-tree>
@@ -110,8 +131,7 @@ it(`focuses on a collapsed tree item's parent if left arrow is pressed`, async (
110
131
  const grandchildItems = childItems[0].slotElements;
111
132
  grandchildItems[0].focus();
112
133
  await sendKeys({ press: 'ArrowLeft' });
113
- assert(document.activeElement instanceof GlideCoreTreeItem);
114
- expect(document.activeElement?.label).to.equal(childItems[0].label);
134
+ expect(document.activeElement === childItems[0]).to.equal(true);
115
135
  });
116
136
  it('moves down the non-expanded tree items with down arrow', async () => {
117
137
  const tree = await fixture(html `
@@ -291,3 +311,70 @@ it('can use the keyboard to navigate to a tree item menu', async () => {
291
311
  await sendKeys({ press: 'Tab' });
292
312
  assert(document.activeElement instanceof GlideCoreTreeItemMenu);
293
313
  });
314
+ it('does not focus on a tree item icon button unless that tree item is focused', async () => {
315
+ const tree = await fixture(html `
316
+ <glide-core-tree>
317
+ <glide-core-tree-item label="Child Item 1"> </glide-core-tree-item>
318
+ <glide-core-tree-item label="Child Item 2">
319
+ <glide-core-tree-item-icon-button slot="suffix">
320
+ <svg viewBox="0 0 24 24">
321
+ <path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
322
+ </svg>
323
+ </glide-core-tree-item-icon-button>
324
+ </glide-core-tree-item>
325
+ </glide-core-tree>
326
+ `);
327
+ tree.dispatchEvent(new Event('focusin'));
328
+ const childItems = tree.slotElements;
329
+ childItems[0].focus();
330
+ await sendKeys({ press: 'Tab' });
331
+ expect(document.activeElement === document.body).to.equal(true);
332
+ childItems[1].focus();
333
+ await sendKeys({ press: 'Tab' });
334
+ expect(document.activeElement instanceof GlideCoreTreeItemIconButton).to.equal(true);
335
+ await sendKeys({ down: 'Shift' });
336
+ await sendKeys({ press: 'Tab' });
337
+ await sendKeys({ up: 'Shift' });
338
+ expect(document.activeElement === childItems[1]).to.equal(true, 'can keyboard navigate back to the parent Tree Item');
339
+ });
340
+ it('does not focus on a tree item menu unless that tree item is focused', async () => {
341
+ const tree = await fixture(html `
342
+ <glide-core-tree>
343
+ <glide-core-tree-item label="Child Item 1"> </glide-core-tree-item>
344
+ <glide-core-tree-item label="Child Item 2">
345
+ <glide-core-tree-item-menu slot="menu">
346
+ <glide-core-menu-link label="Edit" url="/edit">
347
+ </glide-core-menu-link>
348
+ </glide-core-tree-item-menu>
349
+ </glide-core-tree-item>
350
+ </glide-core-tree>
351
+ `);
352
+ tree.dispatchEvent(new Event('focusin'));
353
+ const childItems = tree.slotElements;
354
+ childItems[0].focus();
355
+ await sendKeys({ press: 'Tab' });
356
+ expect(document.activeElement === document.body).to.equal(true);
357
+ childItems[1].focus();
358
+ await sendKeys({ press: 'Tab' });
359
+ expect(document.activeElement instanceof GlideCoreTreeItemMenu).to.equal(true);
360
+ });
361
+ it('does not select a tree item if Enter is pressed while its tree item icon button is focused', async () => {
362
+ const tree = await fixture(html `
363
+ <glide-core-tree>
364
+ <glide-core-tree-item label="Child Item 1">
365
+ <glide-core-tree-item-icon-button slot="suffix">
366
+ <svg viewBox="0 0 24 24">
367
+ <path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
368
+ </svg>
369
+ </glide-core-tree-item-icon-button>
370
+ </glide-core-tree-item>
371
+ </glide-core-tree>
372
+ `);
373
+ tree.dispatchEvent(new Event('focusin'));
374
+ const childItems = tree.slotElements;
375
+ childItems[0].focus();
376
+ await sendKeys({ press: 'Tab' });
377
+ expect(document.activeElement instanceof GlideCoreTreeItemIconButton).to.equal(true);
378
+ await sendKeys({ press: 'Enter' });
379
+ expect(childItems[0].hasAttribute('selected')).to.equal(false);
380
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdstrike/glide-core",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "CrowdStrike's Glide Design System components package for providing web components",
5
5
  "author": "CrowdStrike UX Team",
6
6
  "license": "Apache-2.0",
@@ -70,6 +70,7 @@
70
70
  "@web/test-runner-commands": "^0.9.0",
71
71
  "@web/test-runner-playwright": "^0.11.0",
72
72
  "chalk": "^5.3.0",
73
+ "cheerio": "^1.0.0",
73
74
  "esbuild": "^0.19.12",
74
75
  "eslint": "^8.56.0",
75
76
  "eslint-config-prettier": "^9.1.0",