@crowdstrike/glide-core 0.9.0 → 0.9.2

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 (148) hide show
  1. package/dist/accordion.d.ts +7 -3
  2. package/dist/accordion.styles.js +2 -4
  3. package/dist/button-group.button.d.ts +1 -4
  4. package/dist/button-group.button.styles.js +4 -8
  5. package/dist/button-group.d.ts +3 -0
  6. package/dist/button-group.styles.js +2 -2
  7. package/dist/button.d.ts +4 -0
  8. package/dist/button.js +1 -1
  9. package/dist/button.styles.js +2 -4
  10. package/dist/button.test.events.js +86 -10
  11. package/dist/checkbox-group.d.ts +6 -2
  12. package/dist/checkbox-group.stories.d.ts +1 -1
  13. package/dist/checkbox.d.ts +5 -4
  14. package/dist/checkbox.js +1 -1
  15. package/dist/checkbox.stories.d.ts +1 -1
  16. package/dist/checkbox.styles.js +43 -6
  17. package/dist/checkbox.test.basics.js +15 -6
  18. package/dist/checkbox.test.events.js +12 -4
  19. package/dist/checkbox.test.focus.js +1 -1
  20. package/dist/checkbox.test.form.js +17 -0
  21. package/dist/checkbox.test.interactions.js +52 -7
  22. package/dist/drawer.d.ts +5 -5
  23. package/dist/drawer.js +1 -1
  24. package/dist/drawer.stories.d.ts +0 -1
  25. package/dist/dropdown.d.ts +7 -4
  26. package/dist/dropdown.js +1 -1
  27. package/dist/dropdown.option.js +1 -1
  28. package/dist/dropdown.option.styles.js +1 -0
  29. package/dist/dropdown.styles.js +47 -26
  30. package/dist/dropdown.test.focus.filterable.js +20 -0
  31. package/dist/dropdown.test.focus.js +1 -0
  32. package/dist/dropdown.test.form.js +23 -112
  33. package/dist/dropdown.test.interactions.filterable.js +121 -17
  34. package/dist/dropdown.test.interactions.multiple.js +15 -22
  35. package/dist/dropdown.test.interactions.single.js +44 -22
  36. package/dist/icon-button.d.ts +2 -0
  37. package/dist/icon-button.styles.js +2 -4
  38. package/dist/icons/checked.d.ts +5 -0
  39. package/dist/icons/checked.js +1 -1
  40. package/dist/input.d.ts +5 -4
  41. package/dist/input.js +1 -1
  42. package/dist/input.stories.d.ts +0 -4
  43. package/dist/input.styles.d.ts +1 -1
  44. package/dist/input.styles.js +93 -93
  45. package/dist/input.test.basics.js +45 -45
  46. package/dist/input.test.form.js +17 -0
  47. package/dist/label.styles.js +11 -13
  48. package/dist/library/localize.d.ts +1 -0
  49. package/dist/library/localize.test.js +45 -0
  50. package/dist/menu.button.styles.js +1 -0
  51. package/dist/menu.js +1 -1
  52. package/dist/menu.link.styles.js +1 -0
  53. package/dist/menu.styles.js +3 -1
  54. package/dist/menu.test.events.js +101 -7
  55. package/dist/menu.test.focus.js +26 -3
  56. package/dist/menu.test.interactions.js +5 -2
  57. package/dist/modal.d.ts +0 -7
  58. package/dist/modal.icon-button.test.basics.js +9 -9
  59. package/dist/modal.stories.d.ts +1 -0
  60. package/dist/modal.styles.js +2 -4
  61. package/dist/modal.tertiary-icon.test.basics.js +15 -15
  62. package/dist/modal.test.accessibility.js +16 -27
  63. package/dist/modal.test.basics.js +64 -68
  64. package/dist/modal.test.close.js +12 -16
  65. package/dist/modal.test.events.js +32 -44
  66. package/dist/modal.test.lock-scroll.js +15 -25
  67. package/dist/modal.test.methods.js +8 -12
  68. package/dist/modal.test.scrollbars.js +2 -4
  69. package/dist/radio-group.d.ts +4 -3
  70. package/dist/radio-group.js +1 -1
  71. package/dist/radio-group.stories.d.ts +1 -1
  72. package/dist/radio-group.test.basics.js +3 -3
  73. package/dist/radio-group.test.events.js +6 -6
  74. package/dist/radio-group.test.form.js +19 -0
  75. package/dist/radio.d.ts +1 -2
  76. package/dist/radio.js +1 -1
  77. package/dist/radio.styles.js +2 -6
  78. package/dist/split-button.styles.js +2 -4
  79. package/dist/split-container.d.ts +1 -1
  80. package/dist/split-container.styles.js +2 -4
  81. package/dist/status-indicator.d.ts +1 -1
  82. package/dist/styles/focus-outline.d.ts +1 -1
  83. package/dist/styles/focus-outline.js +7 -1
  84. package/dist/styles/menu-opening-animation.d.ts +2 -0
  85. package/dist/styles/menu-opening-animation.js +26 -0
  86. package/dist/styles/variables.css +1 -1
  87. package/dist/styles/visually-hidden.d.ts +1 -1
  88. package/dist/styles/visually-hidden.js +14 -1
  89. package/dist/tab.group.d.ts +6 -6
  90. package/dist/tab.group.js +1 -1
  91. package/dist/tab.group.styles.js +46 -5
  92. package/dist/tab.group.test.basics.js +9 -2
  93. package/dist/tab.group.test.interactions.js +70 -93
  94. package/dist/tab.js +1 -1
  95. package/dist/tab.panel.styles.js +3 -9
  96. package/dist/tab.styles.js +6 -13
  97. package/dist/tab.test.basics.js +15 -17
  98. package/dist/tabs.stories.d.ts +1 -0
  99. package/dist/tag.d.ts +3 -6
  100. package/dist/tag.js +1 -1
  101. package/dist/tag.styles.js +2 -4
  102. package/dist/tag.test.basics.js +28 -27
  103. package/dist/tag.test.events.js +3 -3
  104. package/dist/tag.test.focus.js +4 -4
  105. package/dist/textarea.d.ts +5 -4
  106. package/dist/textarea.stories.d.ts +0 -4
  107. package/dist/textarea.styles.d.ts +1 -1
  108. package/dist/textarea.styles.js +63 -67
  109. package/dist/textarea.test.basics.js +52 -52
  110. package/dist/toasts.d.ts +5 -0
  111. package/dist/toasts.styles.js +1 -1
  112. package/dist/toggle.d.ts +3 -3
  113. package/dist/toggle.js +1 -1
  114. package/dist/toggle.stories.d.ts +1 -1
  115. package/dist/toggle.styles.js +2 -1
  116. package/dist/toggle.test.interactions.js +37 -0
  117. package/dist/tooltip.d.ts +2 -2
  118. package/dist/tooltip.js +1 -1
  119. package/dist/tooltip.styles.js +22 -18
  120. package/dist/tooltip.test.interactions.js +6 -6
  121. package/dist/translations/en.js +1 -1
  122. package/dist/translations/fr.d.ts +3 -1
  123. package/dist/translations/fr.js +1 -1
  124. package/dist/translations/ja.d.ts +3 -1
  125. package/dist/translations/ja.js +1 -1
  126. package/dist/tree.d.ts +1 -1
  127. package/dist/tree.item.d.ts +0 -3
  128. package/dist/tree.item.icon-button.d.ts +1 -0
  129. package/dist/tree.item.icon-button.js +1 -1
  130. package/dist/tree.item.icon-button.test.basics.js +9 -0
  131. package/dist/tree.item.js +1 -1
  132. package/dist/tree.item.menu.d.ts +2 -0
  133. package/dist/tree.item.menu.js +1 -1
  134. package/dist/tree.item.menu.test.basics.js +15 -0
  135. package/dist/tree.item.styles.js +13 -3
  136. package/dist/tree.item.test.basics.d.ts +2 -1
  137. package/dist/tree.item.test.basics.js +16 -4
  138. package/dist/tree.js +1 -1
  139. package/dist/tree.test.focus.js +91 -4
  140. package/package.json +2 -1
  141. package/dist/button.test.form.d.ts +0 -1
  142. package/dist/button.test.form.js +0 -50
  143. package/dist/input.test.translations.js +0 -38
  144. package/dist/tag.test.translations.d.ts +0 -1
  145. package/dist/tag.test.translations.js +0 -25
  146. package/dist/textarea.test.translations.d.ts +0 -1
  147. package/dist/textarea.test.translations.js +0 -34
  148. /package/dist/{input.test.translations.d.ts → library/localize.test.d.ts} +0 -0
@@ -117,7 +117,7 @@ it('is visible on "mouseover"', async () => {
117
117
  assert(tooltip);
118
118
  tooltip.dataset.openDelay = '0';
119
119
  component.shadowRoot
120
- ?.querySelector('.component')
120
+ ?.querySelector('[data-test="component"')
121
121
  ?.dispatchEvent(new MouseEvent('mouseover'));
122
122
  // Wait for Floating UI and the open delay.
123
123
  await aTimeout(0);
@@ -132,7 +132,7 @@ it('is hidden on "mouseover" when disabled', async () => {
132
132
  assert(tooltip);
133
133
  tooltip.dataset.openDelay = '0';
134
134
  component.shadowRoot
135
- ?.querySelector('.component')
135
+ ?.querySelector('[data-test="component"')
136
136
  ?.dispatchEvent(new MouseEvent('mouseover'));
137
137
  // Wait for Floating UI.
138
138
  await aTimeout(0);
@@ -147,13 +147,13 @@ it('is hidden on "mouseout"', async () => {
147
147
  assert(tooltip);
148
148
  tooltip.dataset.openDelay = '0';
149
149
  component.shadowRoot
150
- ?.querySelector('.component')
150
+ ?.querySelector('[data-test="component"')
151
151
  ?.dispatchEvent(new MouseEvent('mouseover'));
152
152
  // Wait for Floating UI and the open delay.
153
153
  await aTimeout(0);
154
154
  tooltip.dataset.closeDelay = '0';
155
155
  component.shadowRoot
156
- ?.querySelector('.component')
156
+ ?.querySelector('[data-test="component"')
157
157
  ?.dispatchEvent(new MouseEvent('mouseout'));
158
158
  // Wait for the close delay.
159
159
  await aTimeout(0);
@@ -169,11 +169,11 @@ it('remains hidden if "mouseout" fires before the "mouseover" delay', async () =
169
169
  tooltip.dataset.openDelay = '1';
170
170
  tooltip.dataset.closeDelay = '0';
171
171
  component.shadowRoot
172
- ?.querySelector('.component')
172
+ ?.querySelector('[data-test="component"')
173
173
  ?.dispatchEvent(new MouseEvent('mouseover'));
174
174
  expect(tooltip?.checkVisibility()).to.be.false;
175
175
  component.shadowRoot
176
- ?.querySelector('.component')
176
+ ?.querySelector('[data-test="component"')
177
177
  ?.dispatchEvent(new MouseEvent('mouseout'));
178
178
  await aTimeout(1);
179
179
  expect(tooltip.checkVisibility()).to.be.false;
@@ -1 +1 @@
1
- const translation={$code:"en",$name:"English",$dir:"ltr",close:"Close",dismiss:"Dismiss",open:"Open",selectAll:"Select all",moreInformation:"More information",nextTab:"Next tab",notifications:"Notifications",previousTab:"Previous tab",announcedCharacterCount:(e,t)=>`Character count ${e} of ${t}`,displayedCharacterCount:(e,t)=>`${e}/${t}`,clearEntry:e=>`Clear ${e} entry`,removeTag:e=>`Remove tag: ${e}`};export default translation;
1
+ const translation={$code:"en",$name:"English",$dir:"ltr",close:"Close",dismiss:"Dismiss",open:"Open",selectAll:"Select all",moreInformation:"More information",notifications:"Notifications",nextTab:"Next tab",previousTab:"Previous tab",announcedCharacterCount:(e,o)=>`Character count ${e} of ${o}`,displayedCharacterCount:(e,o)=>`${e}/${o}`,clearEntry:e=>`Clear ${e} entry`,removeTag:e=>`Remove tag: ${e}`,actionsFor:e=>`Actions for ${e}`};export default translation;
@@ -1,3 +1,5 @@
1
1
  import type { Translation } from '../library/localize.js';
2
- declare const translation: Translation;
2
+ export declare const PENDING_STRINGS: readonly [];
3
+ type PendingTranslation = (typeof PENDING_STRINGS)[number];
4
+ declare const translation: Omit<Translation, PendingTranslation>;
3
5
  export default translation;
@@ -1 +1 @@
1
- const translation={$code:"fr",$name:"French",$dir:"ltr",close:"Fermer",dismiss:"Congédier",open:"Ouvrir",selectAll:"Tout sélectionner",moreInformation:"Plus d’informations",notifications:"Notifications",nextTab:"Next tab",previousTab:"Previous tab",announcedCharacterCount:(e,r)=>`Character count ${e} of ${r}`,displayedCharacterCount:(e,r)=>`${e}/${r}`,clearEntry:e=>`Clear ${e} entry`,removeTag:e=>`Supprimer la balise : ${e}`};export default translation;
1
+ export const PENDING_STRINGS=[];const translation={$code:"fr",$name:"French",$dir:"ltr",close:"Fermer",dismiss:"Congédier",open:"Ouvrir",selectAll:"Tout sélectionner",moreInformation:"Plus d’informations",notifications:"Notifications",nextTab:"Onglet suivant",previousTab:"Onglet précédent",announcedCharacterCount:(e,r)=>`Nombre de caractères ${e} de ${r}`,displayedCharacterCount:(e,r)=>`${e}/${r}`,clearEntry:e=>`Effacer l'entrée ${e}`,removeTag:e=>`Supprimer la balise : ${e}`,actionsFor:e=>`Actions pour ${e}`};export default translation;
@@ -1,3 +1,5 @@
1
1
  import type { Translation } from '../library/localize.js';
2
- declare const translation: Translation;
2
+ export declare const PENDING_STRINGS: readonly [];
3
+ type PendingTranslation = (typeof PENDING_STRINGS)[number];
4
+ declare const translation: Omit<Translation, PendingTranslation>;
3
5
  export default translation;
@@ -1 +1 @@
1
- const translation={$code:"ja",$name:"Japanese",$dir:"ltr",close:"閉じる",dismiss:"無視",open:"オープン",selectAll:"すべて選択",moreInformation:"詳細情報",notifications:"通知",nextTab:"Next tab",previousTab:"Previous tab",announcedCharacterCount:(a,e)=>`Character count ${a} of ${e}`,displayedCharacterCount:(a,e)=>`${a}/${e}`,clearEntry:a=>`Clear ${a} entry`,removeTag:a=>`タグを削除: ${a}`};export default translation;
1
+ export const PENDING_STRINGS=[];const translation={$code:"ja",$name:"Japanese",$dir:"ltr",close:"閉じる",dismiss:"無視",open:"オープン",selectAll:"すべて選択",moreInformation:"詳細情報",notifications:"通知",nextTab:"Onglet suivant",previousTab:"Onglet précédent",announcedCharacterCount:(n,t)=>`${t} 文字数の${n}`,displayedCharacterCount:(n,t)=>`${n}/${t}`,clearEntry:n=>`${n}エントリのクリア`,removeTag:n=>`タグを削除: ${n}`,actionsFor:n=>`${n}のアクション`};export default translation;
package/dist/tree.d.ts CHANGED
@@ -6,7 +6,7 @@ declare global {
6
6
  }
7
7
  }
8
8
  /**
9
- * @description A tree element, containing a hierarchy of tree items
9
+ * @description A tree containing a hierarchy of Tree Items.
10
10
  *
11
11
  * @slot - One or more of <glide-core-tree-item>
12
12
  */
@@ -1,6 +1,5 @@
1
1
  import './tree.item.menu.js';
2
2
  import { LitElement } from 'lit';
3
- import type GlideCoreMenu from './menu.js';
4
3
  declare global {
5
4
  interface HTMLElementTagNameMap {
6
5
  'glide-core-tree-item': GlideCoreTreeItem;
@@ -24,8 +23,6 @@ export default class GlideCoreTreeItem extends LitElement {
24
23
  selected: boolean;
25
24
  removeIndentation: boolean;
26
25
  nonCollapsible: boolean;
27
- menuSlotAssignedElements: GlideCoreMenu[];
28
- prefixSlotAssignedElements: HTMLElement[];
29
26
  slotElements: GlideCoreTreeItem[];
30
27
  suffixSlotAssignedElements: HTMLElement[];
31
28
  firstUpdated(): void;
@@ -15,6 +15,7 @@ export default class GlideCoreTreeItemIconButton extends LitElement {
15
15
  #private;
16
16
  static shadowRootOptions: ShadowRootInit;
17
17
  static styles: import("lit").CSSResult[];
18
+ label: string;
18
19
  firstUpdated(): void;
19
20
  render(): import("lit").TemplateResult<1>;
20
21
  }
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,r){var l,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var s=e.length-1;s>=0;s--)(l=e[s])&&(i=(n<3?l(i):n>3?l(t,o,i):l(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};import"./icon-button.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement}from"lit/decorators.js";import{owSlot}from"./library/ow.js";import styles from"./tree.item.icon-button.styles.js";let GlideCoreTreeItemIconButton=class GlideCoreTreeItemIconButton extends LitElement{static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}firstUpdated(){owSlot(this.#e.value)}render(){return html`<glide-core-icon-button class="component" variant="tertiary"><slot @slotchange="${this.#t}" ${ref(this.#e)}></slot></glide-core-icon-button>`}#e=createRef();#t(){owSlot(this.#e.value)}};GlideCoreTreeItemIconButton=__decorate([customElement("glide-core-tree-item-icon-button")],GlideCoreTreeItemIconButton);export default GlideCoreTreeItemIconButton;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,r){var l,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var s=e.length-1;s>=0;s--)(l=e[s])&&(i=(n<3?l(i):n>3?l(t,o,i):l(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};import"./icon-button.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property}from"lit/decorators.js";import{owSlot}from"./library/ow.js";import styles from"./tree.item.icon-button.styles.js";let GlideCoreTreeItemIconButton=class GlideCoreTreeItemIconButton extends LitElement{constructor(){super(...arguments),this.label="",this.#e=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}firstUpdated(){owSlot(this.#e.value)}render(){return html`<glide-core-icon-button class="component" variant="tertiary" tabindex="-1" label="${this.label}"><slot @slotchange="${this.#t}" ${ref(this.#e)}></slot></glide-core-icon-button>`}#e;#t(){owSlot(this.#e.value)}};__decorate([property()],GlideCoreTreeItemIconButton.prototype,"label",void 0),GlideCoreTreeItemIconButton=__decorate([customElement("glide-core-tree-item-icon-button")],GlideCoreTreeItemIconButton);export default GlideCoreTreeItemIconButton;
@@ -1,6 +1,7 @@
1
1
  import { expect, fixture, html } from '@open-wc/testing';
2
2
  import GlideCoreTreeItemIconButton from './tree.item.icon-button.js';
3
3
  import expectArgumentError from './library/expect-argument-error.js';
4
+ GlideCoreTreeItemIconButton.shadowRootOptions.mode = 'open';
4
5
  it('registers', async () => {
5
6
  expect(window.customElements.get('glide-core-tree-item-icon-button')).to.equal(GlideCoreTreeItemIconButton);
6
7
  });
@@ -11,3 +12,11 @@ it('throws if it does not have a default slot', async () => {
11
12
  `);
12
13
  });
13
14
  });
15
+ it('passes its label to the icon button', async () => {
16
+ const component = await fixture(html `
17
+ <glide-core-tree-item-icon-button label="My label"
18
+ >Hello</glide-core-tree-item-icon-button
19
+ >
20
+ `);
21
+ expect(component.shadowRoot?.querySelector('glide-core-icon-button')?.label).to.equal('My label');
22
+ });
package/dist/tree.item.js CHANGED
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,i,o){var r,s=arguments.length,l=s<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,i):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,i,o);else for(var d=e.length-1;d>=0;d--)(r=e[d])&&(l=(s<3?r(l):s>3?r(t,i,l):r(t,i))||l);return s>3&&l&&Object.defineProperty(t,i,l),l};import"./tree.item.menu.js";import{LitElement,html}from"lit";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,queryAssignedElements,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import{when}from"lit/directives/when.js";import styles from"./tree.item.styles.js";let GlideCoreTreeItem=class GlideCoreTreeItem extends LitElement{constructor(){super(...arguments),this.expanded=!1,this.label="",this.level=1,this.selected=!1,this.removeIndentation=!1,this.nonCollapsible=!1,this.childTreeItems=[],this.#e=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,delegatesFocus:!0,mode:"closed"}}static{this.styles=styles}firstUpdated(){this.#t()}focus(e){this.#e.value?.focus(e)}get hasChildTreeItems(){return this.childTreeItems.length>0}get hasExpandIcon(){return this.hasChildTreeItems&&!this.nonCollapsible}render(){return html`<div class="${classMap({component:!0,expanded:this.expanded,selected:this.selected})}" role="treeitem" aria-label="${this.label}" aria-selected="${ifDefined(this.#i)}" aria-expanded="${ifDefined(this.#o)}"><div class="${classMap({"label-container":!0})}" tabindex="-1" ${ref(this.#e)}><div style="flex-shrink: 0; width:${this.#r};"></div>${when(!this.removeIndentation||this.hasExpandIcon,(()=>html`<div class="expand-icon-container">${when(this.hasExpandIcon,(()=>html`<div><svg aria-hidden="true" class="${classMap({"expand-icon":!0,"expand-icon-expanded":this.expanded})}" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`))}</div>`))}<slot name="prefix"></slot><div class="label">${this.label}</div><div class="icon-container"><slot name="menu"></slot><slot name="suffix"></slot></div></div><div class="child-items" role="group"><slot></slot></div></div>`}selectItem(e){let t;for(const i of this.slotElements)if(e===i)i.setAttribute("selected","true"),t=i;else{i.removeAttribute("selected");const o=i.selectItem(e);o&&(t=o)}return t}toggleExpand(){this.expanded=!this.expanded}#e;get#o(){return this.hasChildTreeItems?this.expanded?"true":"false":void 0}get#i(){return this.hasChildTreeItems?void 0:this.selected?"true":"false"}get#r(){return 20*(this.level-1)+"px"}#t(){const e=[];for(const t of this.slotElements)t.level=this.level+1,e.push(t);this.childTreeItems=e}};__decorate([property({type:Boolean})],GlideCoreTreeItem.prototype,"expanded",void 0),__decorate([property({reflect:!0})],GlideCoreTreeItem.prototype,"label",void 0),__decorate([property({type:Number})],GlideCoreTreeItem.prototype,"level",void 0),__decorate([property({type:Boolean})],GlideCoreTreeItem.prototype,"selected",void 0),__decorate([property({type:Boolean,attribute:"remove-indentation"})],GlideCoreTreeItem.prototype,"removeIndentation",void 0),__decorate([property({type:Boolean,attribute:"non-collapsible"})],GlideCoreTreeItem.prototype,"nonCollapsible",void 0),__decorate([queryAssignedElements({slot:"menu"})],GlideCoreTreeItem.prototype,"menuSlotAssignedElements",void 0),__decorate([queryAssignedElements({slot:"prefix"})],GlideCoreTreeItem.prototype,"prefixSlotAssignedElements",void 0),__decorate([queryAssignedElements()],GlideCoreTreeItem.prototype,"slotElements",void 0),__decorate([queryAssignedElements({slot:"suffix"})],GlideCoreTreeItem.prototype,"suffixSlotAssignedElements",void 0),__decorate([state()],GlideCoreTreeItem.prototype,"childTreeItems",void 0),GlideCoreTreeItem=__decorate([customElement("glide-core-tree-item")],GlideCoreTreeItem);export default GlideCoreTreeItem;
1
+ var _a,GlideCoreTreeItem_1,__decorate=this&&this.__decorate||function(e,t,o,i){var r,s=arguments.length,l=s<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,o):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,o,i);else for(var n=e.length-1;n>=0;n--)(r=e[n])&&(l=(s<3?r(l):s>3?r(t,o,l):r(t,o))||l);return s>3&&l&&Object.defineProperty(t,o,l),l};import"./tree.item.menu.js";import{LitElement,html}from"lit";import{LocalizeController}from"./library/localize.js";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,queryAssignedElements,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import{when}from"lit/directives/when.js";import GlideCoreIconButton from"./icon-button.js";import GlideCoreTreeItemMenu from"./tree.item.menu.js";import ow,{owSlotType}from"./library/ow.js";import styles from"./tree.item.styles.js";let GlideCoreTreeItem=class GlideCoreTreeItem extends LitElement{constructor(){super(...arguments),this.expanded=!1,this.label="",this.level=1,this.selected=!1,this.removeIndentation=!1,this.nonCollapsible=!1,this.childTreeItems=[],this.#e=createRef(),this.#t=new LocalizeController(this),this.#o=createRef()}static{GlideCoreTreeItem_1=this}static{this.shadowRootOptions={...LitElement.shadowRootOptions,delegatesFocus:!0,mode:"closed"}}static{this.styles=styles}firstUpdated(){this.#i()}focus(e){this.#e.value?.focus(e),this.#r(0)}get hasChildTreeItems(){return this.childTreeItems.length>0}get hasExpandIcon(){return this.hasChildTreeItems&&!this.nonCollapsible}render(){return html`<div class="${classMap({component:!0,expanded:this.expanded,selected:this.selected})}" role="treeitem" aria-label="${this.label}" aria-selected="${ifDefined(this.#s)}" aria-expanded="${ifDefined(this.#l)}"><div class="${classMap({"label-container":!0})}" tabindex="-1" @focusout="${this.#n}" @focusin="${this.#a}" ${ref(this.#e)}><div style="flex-shrink: 0; width:${this.#d};"></div>${when(!this.removeIndentation||this.hasExpandIcon,(()=>html`<div class="expand-icon-container">${when(this.hasExpandIcon,(()=>html`<div><svg aria-hidden="true" class="${classMap({"expand-icon":!0,"expand-icon-expanded":this.expanded})}" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`))}</div>`))}<slot name="prefix"></slot><div class="label">${this.label}</div><div class="icon-container"><slot name="menu" ${ref(this.#o)} @slotchange="${this.#c}"></slot><slot name="suffix"></slot></div></div><div class="child-items" role="group"><slot></slot></div></div>`}selectItem(e){let t;for(const o of this.slotElements)if(e===o)o.setAttribute("selected","true"),t=o;else{o.removeAttribute("selected");const i=o.selectItem(e);i&&(t=i)}return t}toggleExpand(){this.expanded=!this.expanded}#e;#t;#o;get#l(){return this.hasChildTreeItems?this.expanded?"true":"false":void 0}get#s(){return this.hasChildTreeItems?void 0:this.selected?"true":"false"}#a(e){this.#m(e.target)&&e.stopPropagation()}#n(e){this.#m(e.relatedTarget)?e.stopPropagation():this.#r(-1)}get#d(){return 20*(this.level-1)+"px"}#m(e){return e&&e instanceof HTMLElement&&!(e instanceof GlideCoreTreeItem_1)&&this.contains(e)}#c(){owSlotType(this.#o.value,[GlideCoreTreeItemMenu]);for(const e of this.#o.value.assignedElements())e instanceof GlideCoreTreeItemMenu&&(e.label=this.#t.term("actionsFor",this.label))}#r(e){ow(this.#e.value,ow.object.instanceOf(HTMLElement)),this.#e.value.tabIndex=e;for(const t of this.querySelectorAll("& > glide-core-tree-item-icon-button"))t.tabIndex=e}#i(){const e=[];for(const t of this.slotElements)t.level=this.level+1,e.push(t);this.childTreeItems=e}};__decorate([property({type:Boolean})],GlideCoreTreeItem.prototype,"expanded",void 0),__decorate([property({reflect:!0})],GlideCoreTreeItem.prototype,"label",void 0),__decorate([property({type:Number})],GlideCoreTreeItem.prototype,"level",void 0),__decorate([property({type:Boolean})],GlideCoreTreeItem.prototype,"selected",void 0),__decorate([property({type:Boolean,attribute:"remove-indentation"})],GlideCoreTreeItem.prototype,"removeIndentation",void 0),__decorate([property({type:Boolean,attribute:"non-collapsible"})],GlideCoreTreeItem.prototype,"nonCollapsible",void 0),__decorate([queryAssignedElements()],GlideCoreTreeItem.prototype,"slotElements",void 0),__decorate([queryAssignedElements({slot:"suffix"})],GlideCoreTreeItem.prototype,"suffixSlotAssignedElements",void 0),__decorate([state()],GlideCoreTreeItem.prototype,"childTreeItems",void 0),GlideCoreTreeItem=GlideCoreTreeItem_1=__decorate([customElement("glide-core-tree-item")],GlideCoreTreeItem);export default GlideCoreTreeItem;
@@ -19,6 +19,8 @@ export default class GlideCoreTreeItemMenu extends LitElement {
19
19
  static shadowRootOptions: ShadowRootInit;
20
20
  static styles: import("lit").CSSResult[];
21
21
  placement: Placement;
22
+ label: string;
23
+ click(): void;
22
24
  firstUpdated(): void;
23
25
  render(): import("lit").TemplateResult<1>;
24
26
  }
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,r){var l,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var s=e.length-1;s>=0;s--)(l=e[s])&&(i=(n<3?l(i):n>3?l(t,o,i):l(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};import"./icon-button.js";import"./menu.js";import"./menu.options.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property}from"lit/decorators.js";import{owSlot,owSlotType}from"./library/ow.js";import GlideCoreMenuButton from"./menu.button.js";import GlideCoreMenuLink from"./menu.link.js";import styles from"./tree.item.menu.styles.js";let GlideCoreTreeItemMenu=class GlideCoreTreeItemMenu extends LitElement{constructor(){super(...arguments),this.placement="bottom-start",this.#e=createRef(),this.#t=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}firstUpdated(){owSlot(this.#e.value),owSlotType(this.#e.value,[GlideCoreMenuButton,GlideCoreMenuLink])}render(){return html`<glide-core-menu class="component" placement="${this.placement}" ${ref(this.#t)}><glide-core-menu-options><slot @slotchange="${this.#o}" ${ref(this.#e)}></slot></glide-core-menu-options><glide-core-icon-button slot="target" variant="tertiary"><svg width="4" height="14" viewBox="0 0 4 18" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 15C2.55228 15 3 15.4477 3 16C3 16.5523 2.55228 17 2 17C1.44772 17 1 16.5523 1 16C1 15.4477 1.44772 15 2 15Z"/><path d="M2 8C2.55228 8 3 8.44772 3 9C3 9.55228 2.55228 10 2 10C1.44772 10 1 9.55228 1 9C1 8.44772 1.44772 8 2 8Z"/><path d="M2 1C2.55228 1 3 1.44772 3 2C3 2.55228 2.55228 3 2 3C1.44772 3 1 2.55228 1 2C1 1.44772 1.44772 1 2 1Z"/></svg></glide-core-icon-button></glide-core-menu>`}#e;#t;#o(){owSlot(this.#e.value),owSlotType(this.#e.value,[GlideCoreMenuButton,GlideCoreMenuLink])}};__decorate([property({reflect:!0})],GlideCoreTreeItemMenu.prototype,"placement",void 0),GlideCoreTreeItemMenu=__decorate([customElement("glide-core-tree-item-menu")],GlideCoreTreeItemMenu);export default GlideCoreTreeItemMenu;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,l){var n,r=arguments.length,i=r<3?t:null===l?l=Object.getOwnPropertyDescriptor(t,o):l;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,l);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(i=(r<3?n(i):r>3?n(t,o,i):n(t,o))||i);return r>3&&i&&Object.defineProperty(t,o,i),i};import"./icon-button.js";import"./menu.js";import"./menu.options.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property}from"lit/decorators.js";import GlideCoreIconButton from"./icon-button.js";import GlideCoreMenuButton from"./menu.button.js";import GlideCoreMenuLink from"./menu.link.js";import ow,{owSlot,owSlotType}from"./library/ow.js";import styles from"./tree.item.menu.styles.js";let GlideCoreTreeItemMenu=class GlideCoreTreeItemMenu extends LitElement{constructor(){super(...arguments),this.placement="bottom-start",this.label="",this.#e=createRef(),this.#t=createRef(),this.#o=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}click(){ow(this.#t.value,ow.object.instanceOf(GlideCoreIconButton)),this.#t.value.click()}firstUpdated(){owSlot(this.#e.value),owSlotType(this.#e.value,[GlideCoreMenuButton,GlideCoreMenuLink])}render(){return html`<glide-core-menu class="component" placement="${this.placement}" ${ref(this.#o)}><glide-core-menu-options><slot @slotchange="${this.#l}" ${ref(this.#e)}></slot></glide-core-menu-options><glide-core-icon-button slot="target" variant="tertiary" label="${this.label}" ${ref(this.#t)}><svg aria-hidden="true" width="4" height="14" viewBox="0 0 4 18" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 15C2.55228 15 3 15.4477 3 16C3 16.5523 2.55228 17 2 17C1.44772 17 1 16.5523 1 16C1 15.4477 1.44772 15 2 15Z"/><path d="M2 8C2.55228 8 3 8.44772 3 9C3 9.55228 2.55228 10 2 10C1.44772 10 1 9.55228 1 9C1 8.44772 1.44772 8 2 8Z"/><path d="M2 1C2.55228 1 3 1.44772 3 2C3 2.55228 2.55228 3 2 3C1.44772 3 1 2.55228 1 2C1 1.44772 1.44772 1 2 1Z"/></svg></glide-core-icon-button></glide-core-menu>`}#e;#t;#o;#l(){owSlot(this.#e.value),owSlotType(this.#e.value,[GlideCoreMenuButton,GlideCoreMenuLink])}};__decorate([property({reflect:!0})],GlideCoreTreeItemMenu.prototype,"placement",void 0),__decorate([property()],GlideCoreTreeItemMenu.prototype,"label",void 0),GlideCoreTreeItemMenu=__decorate([customElement("glide-core-tree-item-menu")],GlideCoreTreeItemMenu);export default GlideCoreTreeItemMenu;
@@ -48,3 +48,18 @@ it('can set placement of the menu', async () => {
48
48
  `);
49
49
  expect(treeItemMenu.shadowRoot?.querySelector('glide-core-menu')?.placement).to.equal('bottom-end');
50
50
  });
51
+ it('can be opened programmatically', async () => {
52
+ const treeItemMenu = await fixture(html `
53
+ <glide-core-tree-item-menu>
54
+ <glide-core-menu-link label="One" url="/one"> </glide-core-menu-link>
55
+ </glide-core-tree-item-menu>
56
+ `);
57
+ expect(treeItemMenu.shadowRoot
58
+ ?.querySelector('glide-core-menu')
59
+ ?.getAttribute('open')).to.equal(null);
60
+ treeItemMenu.click();
61
+ await treeItemMenu.updateComplete;
62
+ expect(treeItemMenu.shadowRoot
63
+ ?.querySelector('glide-core-menu')
64
+ ?.getAttribute('open')).to.equal('');
65
+ });
@@ -1,4 +1,6 @@
1
1
  import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export default[css`
2
+ ${focusOutline(".label-container:focus-visible")}
3
+ `,css`
2
4
  :host {
3
5
  cursor: pointer;
4
6
  display: flex;
@@ -50,6 +52,7 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
50
52
  font-size: var(--glide-core-body-sm-font-size);
51
53
  padding-block: var(--glide-core-spacing-xxs);
52
54
  padding-inline: var(--glide-core-spacing-xs);
55
+ transition: background-color 150ms ease-in-out;
53
56
 
54
57
  &:hover {
55
58
  background-color: var(--glide-core-surface-hover);
@@ -64,13 +67,18 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
64
67
  }
65
68
 
66
69
  &:focus-visible {
67
- ${focusOutline};
68
-
69
70
  /* The outline is inside the component since children have overflow hidden */
70
71
  outline-offset: -2px;
71
72
 
72
73
  .component.selected & {
73
- outline: 1px solid var(--glide-core-icon-selected);
74
+ /*
75
+ We need !important here as we must override the focusVisible mixin
76
+ that also includes !important.
77
+ This component is a bit of a unique case as we want to override the
78
+ default outline styles when a tree item is also focus-visible, but
79
+ also selected.
80
+ */
81
+ outline: 1px solid var(--glide-core-icon-selected) !important;
74
82
  outline-offset: -3px;
75
83
  }
76
84
  }
@@ -129,11 +137,13 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
129
137
  display: flex;
130
138
  flex-direction: column;
131
139
  overflow: hidden;
140
+ visibility: hidden;
132
141
  }
133
142
 
134
143
  .expanded {
135
144
  .child-items {
136
145
  block-size: auto;
146
+ visibility: visible;
137
147
  }
138
148
  }
139
149
 
@@ -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,19 @@ 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 `
37
43
  <glide-core-tree-item label="Item">
38
- <span slot="menu" data-menu>menu</span>
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>
39
47
  </glide-core-tree-item>
40
48
  `);
41
- expect(document.querySelector('[data-menu]')).to.be.ok;
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');
42
54
  });
43
55
  it('renders with a suffix slot', async () => {
44
56
  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.2",
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",
@@ -1 +0,0 @@
1
- import './button.js';
@@ -1,50 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import './button.js';
3
- import { expect, fixture, html, oneEvent } from '@open-wc/testing';
4
- import { sendKeys } from '@web/test-runner-commands';
5
- import GlideCoreButton from './button.js';
6
- GlideCoreButton.shadowRootOptions.mode = 'open';
7
- it('participates in a form when type="reset"', async () => {
8
- const form = document.createElement('form');
9
- const component = await fixture(html ` <glide-core-button type="reset">Button</glide-core-button> `, {
10
- parentNode: form,
11
- });
12
- const formResetEvent = oneEvent(form, 'reset');
13
- component.shadowRoot?.querySelector('button')?.click();
14
- const event = await formResetEvent;
15
- expect(event instanceof Event).to.be.true;
16
- });
17
- it('participates in a form when hitting "enter" and type="reset"', async () => {
18
- const form = document.createElement('form');
19
- const component = await fixture(html ` <glide-core-button type="reset">Button</glide-core-button> `, {
20
- parentNode: form,
21
- });
22
- const formResetEvent = oneEvent(form, 'reset');
23
- component.focus();
24
- await sendKeys({ press: 'Enter' });
25
- const event = await formResetEvent;
26
- expect(event instanceof Event).to.be.true;
27
- });
28
- it('participates in a form when type="submit"', async () => {
29
- const form = document.createElement('form');
30
- const component = await fixture(html ` <glide-core-button type="submit">Button</glide-core-button> `, {
31
- parentNode: form,
32
- });
33
- form.addEventListener('submit', (event) => event.preventDefault());
34
- const formSubmitEvent = oneEvent(form, 'submit');
35
- component.shadowRoot?.querySelector('button')?.click();
36
- const event = await formSubmitEvent;
37
- expect(event instanceof Event).to.be.true;
38
- });
39
- it('participates in a form when hitting "enter" and type="submit"', async () => {
40
- const form = document.createElement('form');
41
- const component = await fixture(html ` <glide-core-button type="submit">Button</glide-core-button> `, {
42
- parentNode: form,
43
- });
44
- form.addEventListener('submit', (event) => event.preventDefault());
45
- const formSubmitEvent = oneEvent(form, 'submit');
46
- component.focus();
47
- await sendKeys({ press: 'Enter' });
48
- const event = await formSubmitEvent;
49
- expect(event instanceof Event).to.be.true;
50
- });
@@ -1,38 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
3
- import GlideCoreInput from './input.js';
4
- GlideCoreInput.shadowRootOptions.mode = 'open';
5
- it('renders dynamic strings in Japanese', async () => {
6
- const element = await fixture(html `
7
- <glide-core-input
8
- label="Test"
9
- value="lorem"
10
- maxlength="40"
11
- clearable
12
- ></glide-core-input>
13
- `);
14
- document.documentElement.setAttribute('lang', 'ja');
15
- await elementUpdated(element);
16
- const maxCharacterCountText = element.shadowRoot?.querySelector('[data-test="character-count-text"]');
17
- expect(maxCharacterCountText?.textContent?.trim()).to.equal('5/40');
18
- const maxCharacterCountAnnouncement = element.shadowRoot?.querySelector('[data-test="character-count-announcement"]');
19
- expect(maxCharacterCountAnnouncement?.textContent?.trim()).to.equal('Character count 5 of 40');
20
- expect(element.shadowRoot?.querySelector('[data-test="clear-button"]')?.label).to.equal('Clear Test entry');
21
- });
22
- it('renders dynamic strings in French', async () => {
23
- const element = await fixture(html `
24
- <glide-core-input
25
- label="Test"
26
- value="lorem"
27
- maxlength="40"
28
- clearable
29
- ></glide-core-input>
30
- `);
31
- document.documentElement.setAttribute('lang', 'fr');
32
- await elementUpdated(element);
33
- const maxCharacterCountText = element.shadowRoot?.querySelector('[data-test="character-count-text"]');
34
- expect(maxCharacterCountText?.textContent?.trim()).to.equal('5/40');
35
- const maxCharacterCountAnnouncement = element.shadowRoot?.querySelector('[data-test="character-count-announcement"]');
36
- expect(maxCharacterCountAnnouncement?.textContent?.trim()).to.equal('Character count 5 of 40');
37
- expect(element.shadowRoot?.querySelector('[data-test="clear-button"]')?.label).to.equal('Clear Test entry');
38
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
2
- import GlideCoreTag from './tag.js';
3
- GlideCoreTag.shadowRootOptions.mode = 'open';
4
- it('renders dynamic string in Japanese', async () => {
5
- const element = await fixture(html `<glide-core-tag removable-label="test-aria-label"
6
- ><span slot="prefix">Prefix</span
7
- ><span data-content>Tag</span></glide-core-tag
8
- >`);
9
- document.documentElement.setAttribute('lang', 'ja');
10
- await elementUpdated(element);
11
- const iconButton = element.shadowRoot?.querySelector('button');
12
- expect(iconButton).to.have.attribute('aria-label', `タグを削除: test-aria-label`);
13
- expect(iconButton).to.have.attribute('type', 'button');
14
- });
15
- it('renders dynamic string in French', async () => {
16
- const element = await fixture(html `<glide-core-tag removable-label="test-aria-label"
17
- ><span slot="prefix">Prefix</span
18
- ><span data-content>Tag</span></glide-core-tag
19
- >`);
20
- document.documentElement.setAttribute('lang', 'fr');
21
- await elementUpdated(element);
22
- const iconButton = element.shadowRoot?.querySelector('button');
23
- expect(iconButton).to.have.attribute('aria-label', `Supprimer la balise : test-aria-label`);
24
- expect(iconButton).to.have.attribute('type', 'button');
25
- });
@@ -1 +0,0 @@
1
- export {};