From 23a8c84cca06fdf211ad1e4cd81bf52cad673bd4 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 24 Feb 2024 02:00:33 +0100 Subject: [PATCH 01/68] Add vscode-list component --- dev/vscode-list.html | 2113 +++++++++++++++++ src/main.ts | 2 + src/vscode-list-item/helpers.ts | 16 + src/vscode-list-item/index.ts | 1 + .../vscode-list-item.styles.ts | 63 + src/vscode-list-item/vscode-list-item.ts | 142 ++ src/vscode-list/index.ts | 1 + src/vscode-list/vscode-list.styles.ts | 19 + src/vscode-list/vscode-list.ts | 53 + 9 files changed, 2410 insertions(+) create mode 100644 dev/vscode-list.html create mode 100644 src/vscode-list-item/helpers.ts create mode 100644 src/vscode-list-item/index.ts create mode 100644 src/vscode-list-item/vscode-list-item.styles.ts create mode 100644 src/vscode-list-item/vscode-list-item.ts create mode 100644 src/vscode-list/index.ts create mode 100644 src/vscode-list/vscode-list.styles.ts create mode 100644 src/vscode-list/vscode-list.ts diff --git a/dev/vscode-list.html b/dev/vscode-list.html new file mode 100644 index 000000000..35497017b --- /dev/null +++ b/dev/vscode-list.html @@ -0,0 +1,2113 @@ + + + + + + + <vscode-list> Demo + + + + + + + + + +
+
+

Basic example

+
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + lorem + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + ipsum + + + + + + + + dolor + + + +
+
+
+ + + diff --git a/src/main.ts b/src/main.ts index 880d8a69f..4a8b6afe0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,8 @@ export {VscodeFormGroup} from './vscode-form-group/index.js'; export {VscodeFormHelper} from './vscode-form-helper/index.js'; export {VscodeIcon} from './vscode-icon/index.js'; export {VscodeLabel} from './vscode-label/index.js'; +export {VscodeList} from './vscode-list/index.js'; +export {VscodeListItem} from './vscode-list-item/index.js'; export {VscodeMultiSelect} from './vscode-multi-select/index.js'; export {VscodeOption} from './vscode-option/index.js'; export {VscodeProgressRing} from './vscode-progress-ring/index.js'; diff --git a/src/vscode-list-item/helpers.ts b/src/vscode-list-item/helpers.ts new file mode 100644 index 000000000..4838aa361 --- /dev/null +++ b/src/vscode-list-item/helpers.ts @@ -0,0 +1,16 @@ +import {VscodeListItem} from './vscode-list-item'; + +export const updateChildrenProps = ( + list: VscodeListItem[], + { + parentLevel, + parentIndent, + arrow, + }: {parentLevel: number; parentIndent: number; arrow: boolean} +) => { + list.forEach((li) => { + li.level = parentLevel + 1; + li.indent = parentIndent; + li.arrow = arrow; + }); +}; diff --git a/src/vscode-list-item/index.ts b/src/vscode-list-item/index.ts new file mode 100644 index 000000000..15686013e --- /dev/null +++ b/src/vscode-list-item/index.ts @@ -0,0 +1 @@ +export {VscodeListItem} from './vscode-list-item.js'; diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts new file mode 100644 index 000000000..0755713e9 --- /dev/null +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -0,0 +1,63 @@ +import {CSSResultGroup, css} from 'lit'; +import defaultStyles from '../includes/default.styles'; + +const styles: CSSResultGroup = [ + defaultStyles, + css` + :host { + cursor: pointer; + display: block; + user-select: none; + } + + .wrapper { + display: block; + } + + .content { + align-items: flex-start; + display: flex; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + font-weight: var(--vscode-font-weight); + outline-offset: -1px; + padding-right: 12px; + } + + .arrow-container { + align-items: center; + display: var(--vsc-list-item-arrow-display); + height: 22px; + justify-content: center; + padding-left: 8px; + padding-right: 6px; + width: 16px; + } + + .arrow-container svg { + display: block; + fill: currentColor; + } + + .arrow-container.icon-rotated svg { + transform: rotate(90deg); + } + + .icon-container { + align-items: center; + display: flex; + height: 22px; + margin-right: 6px; + } + + .text-content { + line-height: 22px; + } + + :host([closed]) ::slotted(vscode-list-item) { + display: none; + } + `, +]; + +export default styles; diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts new file mode 100644 index 000000000..f90efd497 --- /dev/null +++ b/src/vscode-list-item/vscode-list-item.ts @@ -0,0 +1,142 @@ +import {PropertyValues, TemplateResult, html, nothing} from 'lit'; +import { + customElement, + property, + queryAssignedElements, +} from 'lit/decorators.js'; +import {styleMap} from 'lit/directives/style-map.js'; +import {VscElement} from '../includes/VscElement'; +import styles from './vscode-list-item.styles'; +import {updateChildrenProps} from './helpers'; +import {classMap} from 'lit/directives/class-map.js'; + +const BASE_INDENT = 3; +const ARROW_CONTAINER_WIDTH = 30; + +const arrowIcon = html` + +`; + +@customElement('vscode-list-item') +export class VscodeListItem extends VscElement { + static styles = styles; + + @property({type: Boolean, reflect: true}) + arrow = false; + + @property({type: Boolean, reflect: true}) + branch = false; + + @property({type: Boolean, reflect: true}) + closed = false; + + @property({type: Number, reflect: true}) + indent = 8; + + @property({type: Number, reflect: true}) + level = 0; + + @queryAssignedElements({selector: 'vscode-list-item'}) + _initiallyAssignedListItems!: VscodeListItem[]; + + @queryAssignedElements({selector: 'vscode-list-item', slot: 'children'}) + _childrenListItems!: VscodeListItem[]; + + private _mainSlotChange() { + this._initiallyAssignedListItems.forEach((li) => { + li.setAttribute('slot', 'children'); + }); + } + + private _childrenSlotChange() { + updateChildrenProps(this._childrenListItems, { + parentIndent: this.indent, + parentLevel: this.level, + arrow: this.arrow, + }); + + this.branch = this._childrenListItems.length > 0; + } + + private _handleMainSlotChange = () => { + this._mainSlotChange(); + }; + + private _onContentClick = (ev: MouseEvent) => { + ev.stopPropagation(); + this.closed = !this.branch ? false : !this.closed; + }; + + connectedCallback(): void { + super.connectedCallback(); + this._mainSlotChange(); + } + + protected willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has('arrow') || changedProperties.has('indent')) { + this._childrenListItems.forEach((li) => { + li.arrow = this.arrow; + li.indent = this.indent; + }); + } + } + + render(): TemplateResult { + let indentation = BASE_INDENT + this.level * this.indent; + + if (!this.branch && this.arrow) { + indentation += ARROW_CONTAINER_WIDTH; + } + + return html`
+
+ ${this.branch + ? html`
+ ${arrowIcon} +
` + : nothing} +
+ + ${this.branch && this.closed + ? html`` + : nothing} + ${this.branch && !this.closed + ? html`` + : nothing} + ${!this.branch ? html`` : nothing} +
+
+ +
+
+
+
+ +
+
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'vscode-list-item': VscodeListItem; + } +} diff --git a/src/vscode-list/index.ts b/src/vscode-list/index.ts new file mode 100644 index 000000000..7954e891c --- /dev/null +++ b/src/vscode-list/index.ts @@ -0,0 +1 @@ +export {VscodeList} from './vscode-list.js'; diff --git a/src/vscode-list/vscode-list.styles.ts b/src/vscode-list/vscode-list.styles.ts new file mode 100644 index 000000000..31203cacd --- /dev/null +++ b/src/vscode-list/vscode-list.styles.ts @@ -0,0 +1,19 @@ +import {CSSResultGroup, css} from 'lit'; +import defaultStyles from '../includes/default.styles'; + +const styles: CSSResultGroup = [ + defaultStyles, + css` + :host { + --vsc-list-item-arrow-display: none; + + display: block; + } + + :host([arrows]) { + --vsc-list-item-arrow-display: flex; + } + `, +]; + +export default styles; diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts new file mode 100644 index 000000000..8155e43c2 --- /dev/null +++ b/src/vscode-list/vscode-list.ts @@ -0,0 +1,53 @@ +import {PropertyValueMap, PropertyValues, TemplateResult, html} from 'lit'; +import { + customElement, + property, + queryAssignedElements, +} from 'lit/decorators.js'; +import {VscElement} from '../includes/VscElement'; +import {VscodeListItem} from '../vscode-list-item'; +import styles from './vscode-list.styles'; +import {updateChildrenProps} from '../vscode-list-item/helpers'; + +@customElement('vscode-list') +export class VscodeList extends VscElement { + static styles = styles; + + @property({type: Boolean, reflect: true}) + arrows = false; + + @property({type: Number}) + indent = 8; + + @queryAssignedElements({selector: 'vscode-list-item'}) + private _assignedListItems!: VscodeListItem[]; + + private _handleSlotChange = () => { + updateChildrenProps(this._assignedListItems, { + parentLevel: -1, + parentIndent: this.indent, + arrow: this.arrows, + }); + }; + + protected willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has('arrows') || changedProperties.has('indent')) { + this._assignedListItems.forEach((li) => { + li.arrow = this.arrows; + li.indent = this.indent; + }); + } + } + + render(): TemplateResult { + return html`
+ +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'vscode-list': VscodeList; + } +} From 76bbc1354e21f874a5537d1a1def3d6fac54799b Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 24 Feb 2024 03:13:19 +0100 Subject: [PATCH 02/68] wip --- .../vscode-list-item.styles.ts | 21 ++++++++++++++++++- src/vscode-list-item/vscode-list-item.ts | 6 ++++-- src/vscode-list/vscode-list.ts | 12 ++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 0755713e9..1d3ac7912 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -5,6 +5,13 @@ const styles: CSSResultGroup = [ defaultStyles, css` :host { + --hover-outline-color: transparent; + --hover-outline-style: solid; + --hover-outline-width: 0; + --selected-outline-color: transparent; + --selected-outline-style: solid; + --selected-outline-width: 0; + cursor: pointer; display: block; user-select: none; @@ -24,6 +31,18 @@ const styles: CSSResultGroup = [ padding-right: 12px; } + .content:hover { + background-color: var(--vscode-list-hoverBackground); + color: var(--vscode-list-hoverForeground); + } + + .content:hover, + :host([selected]) .content:hover { + outline-color: var(--hover-outline-color); + outline-style: var(--hover-outline-style); + outline-width: var(--hover-outline-width); + } + .arrow-container { align-items: center; display: var(--vsc-list-item-arrow-display); @@ -36,7 +55,7 @@ const styles: CSSResultGroup = [ .arrow-container svg { display: block; - fill: currentColor; + fill: var(--vscode-icon-foreground); } .arrow-container.icon-rotated svg { diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index f90efd497..cc69e705d 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -30,7 +30,8 @@ const arrowIcon = html` i !== item); + } + private _handleSlotChange = () => { updateChildrenProps(this._assignedListItems, { parentLevel: -1, From d584cc5a94583f53abdf4154b203e5a65d433bb1 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 24 Feb 2024 23:00:07 +0100 Subject: [PATCH 03/68] Use context instead of prop drilling --- package-lock.json | 19 +++++++++++- package.json | 3 +- src/vscode-list-item/helpers.ts | 16 ---------- src/vscode-list-item/vscode-list-item.ts | 39 ++++++++---------------- src/vscode-list/list-context.ts | 8 +++++ src/vscode-list/vscode-list.ts | 30 ++++++++++-------- 6 files changed, 58 insertions(+), 57 deletions(-) delete mode 100644 src/vscode-list-item/helpers.ts create mode 100644 src/vscode-list/list-context.ts diff --git a/package-lock.json b/package-lock.json index c99a68321..a2ada703f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.17.0", "license": "MIT", "dependencies": { - "lit": "^3.2.1" + "lit": "^3.2.1", + "@lit/context": "^1.1.0" }, "devDependencies": { "@awmottaz/prettier-plugin-void-html": "^1.8.0", @@ -2666,6 +2667,14 @@ "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", "license": "BSD-3-Clause" }, + "node_modules/@lit/context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.0.tgz", + "integrity": "sha512-fCyv4dsH05wCNm3AKbB+PdYbXGJd/XT8OOwo4hVmD4COq5wOWJlQreGAMDvmHZ7osqxuu06Y4nmP6ooXpN7ErA==", + "dependencies": { + "@lit/reactive-element": "^1.6.2 || ^2.0.0" + } + }, "node_modules/@lit/reactive-element": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", @@ -13538,6 +13547,14 @@ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==" }, + "@lit/context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.0.tgz", + "integrity": "sha512-fCyv4dsH05wCNm3AKbB+PdYbXGJd/XT8OOwo4hVmD4COq5wOWJlQreGAMDvmHZ7osqxuu06Y4nmP6ooXpN7ErA==", + "requires": { + "@lit/reactive-element": "^1.6.2 || ^2.0.0" + } + }, "@lit/reactive-element": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", diff --git a/package.json b/package.json index 0e4daa65d..3700e0442 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,8 @@ }, "homepage": "https://vscode-elements.github.io", "dependencies": { - "lit": "^3.2.1" + "lit": "^3.2.1", + "@lit/context": "^1.1.0" }, "devDependencies": { "@awmottaz/prettier-plugin-void-html": "^1.8.0", diff --git a/src/vscode-list-item/helpers.ts b/src/vscode-list-item/helpers.ts deleted file mode 100644 index 4838aa361..000000000 --- a/src/vscode-list-item/helpers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {VscodeListItem} from './vscode-list-item'; - -export const updateChildrenProps = ( - list: VscodeListItem[], - { - parentLevel, - parentIndent, - arrow, - }: {parentLevel: number; parentIndent: number; arrow: boolean} -) => { - list.forEach((li) => { - li.level = parentLevel + 1; - li.indent = parentIndent; - li.arrow = arrow; - }); -}; diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index cc69e705d..8f7b54cdd 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -1,14 +1,16 @@ -import {PropertyValues, TemplateResult, html, nothing} from 'lit'; +import {TemplateResult, html, nothing} from 'lit'; +import {consume} from '@lit/context'; import { customElement, property, queryAssignedElements, + state, } from 'lit/decorators.js'; import {styleMap} from 'lit/directives/style-map.js'; import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; -import {updateChildrenProps} from './helpers'; import {classMap} from 'lit/directives/class-map.js'; +import {listContext, type ListContext} from '../vscode-list/list-context'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -30,9 +32,9 @@ const arrowIcon = html` 0; + this._childrenListItems.forEach((li) => (li.level = this.level + 1)); } private _handleMainSlotChange = () => { @@ -83,19 +76,11 @@ export class VscodeListItem extends VscElement { this._mainSlotChange(); } - protected willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has('arrow') || changedProperties.has('indent')) { - this._childrenListItems.forEach((li) => { - li.arrow = this.arrow; - li.indent = this.indent; - }); - } - } - render(): TemplateResult { - let indentation = BASE_INDENT + this.level * this.indent; + const {arrows, indent} = this.listData; + let indentation = BASE_INDENT + this.level * indent; - if (!this.branch && this.arrow) { + if (!this.branch && arrows) { indentation += ARROW_CONTAINER_WIDTH; } @@ -105,7 +90,7 @@ export class VscodeListItem extends VscElement { @click=${this._onContentClick} style=${styleMap({paddingLeft: `${indentation}px`})} > - ${this.branch + ${this.branch && arrows ? html`
('vscode-list'); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index f6cfb8cd7..a9245524d 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -1,4 +1,5 @@ -import {PropertyValueMap, PropertyValues, TemplateResult, html} from 'lit'; +import {PropertyValues, TemplateResult, html} from 'lit'; +import {provide} from '@lit/context'; import { customElement, property, @@ -7,7 +8,7 @@ import { import {VscElement} from '../includes/VscElement'; import type {VscodeListItem} from '../vscode-list-item'; import styles from './vscode-list.styles'; -import {updateChildrenProps} from '../vscode-list-item/helpers'; +import {listContext, type ListContext} from './list-context'; @customElement('vscode-list') export class VscodeList extends VscElement { @@ -19,6 +20,12 @@ export class VscodeList extends VscElement { @property({type: Number}) indent = 8; + @provide({context: listContext}) + private listData: ListContext = { + arrows: false, + indent: 8, + }; + @queryAssignedElements({selector: 'vscode-list-item'}) private _assignedListItems!: VscodeListItem[]; @@ -33,19 +40,18 @@ export class VscodeList extends VscElement { } private _handleSlotChange = () => { - updateChildrenProps(this._assignedListItems, { - parentLevel: -1, - parentIndent: this.indent, - arrow: this.arrows, - }); + this._assignedListItems.forEach((li) => (li.level = 0)); }; protected willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has('arrows') || changedProperties.has('indent')) { - this._assignedListItems.forEach((li) => { - li.arrow = this.arrows; - li.indent = this.indent; - }); + const {arrows, indent} = this; + + if (changedProperties.has('arrows')) { + this.listData = {...this.listData, arrows}; + } + + if (changedProperties.has('indent')) { + this.listData = {...this.listData, indent}; } } From 3cff111c1a0a8028e6e184f3d7355a9cfb6df70a Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 14:53:50 +0100 Subject: [PATCH 04/68] Add ability to select items --- .../vscode-list-item.styles.ts | 5 ++ src/vscode-list-item/vscode-list-item.ts | 48 +++++++++++++++++-- src/vscode-list/list-context.ts | 2 + src/vscode-list/vscode-list.ts | 1 + 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 1d3ac7912..28e8bcceb 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -43,6 +43,11 @@ const styles: CSSResultGroup = [ outline-width: var(--hover-outline-width); } + :host([selected]) .content { + color: var(--vscode-list-activeSelectionForeground); + background-color: var(--vscode-list-activeSelectionBackground); + } + .arrow-container { align-items: center; display: var(--vsc-list-item-arrow-display); diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 8f7b54cdd..f415b1aa9 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -11,6 +11,7 @@ import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; import {listContext, type ListContext} from '../vscode-list/list-context'; +import uniqueId from '../includes/uniqueId'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -34,7 +35,11 @@ export class VscodeListItem extends VscElement { @consume({context: listContext, subscribe: true}) @state() - private listData: ListContext = {arrows: false, indent: 8}; + private listData: ListContext = { + arrows: false, + indent: 8, + selectedItems: new Set(), + }; @property({type: Boolean, reflect: true}) branch = false; @@ -45,12 +50,36 @@ export class VscodeListItem extends VscElement { @property({type: Number, reflect: true}) level = 0; + @property({type: Boolean, reflect: true}) + selected = false; + + private _componentId: string; + @queryAssignedElements({selector: 'vscode-list-item'}) _initiallyAssignedListItems!: VscodeListItem[]; @queryAssignedElements({selector: 'vscode-list-item', slot: 'children'}) _childrenListItems!: VscodeListItem[]; + private _selectItem(isCtrlDown: boolean) { + const {selectedItems} = this.listData; + + if (isCtrlDown) { + if (this.selected) { + this.selected = false; + selectedItems.delete(this); + } else { + this.selected = true; + selectedItems.add(this); + } + } else { + selectedItems.forEach((li) => (li.selected = false)); + selectedItems.clear(); + this.selected = true; + selectedItems.add(this); + } + } + private _mainSlotChange() { this._initiallyAssignedListItems.forEach((li) => { li.setAttribute('slot', 'children'); @@ -66,11 +95,22 @@ export class VscodeListItem extends VscElement { this._mainSlotChange(); }; - private _onContentClick = (ev: MouseEvent) => { + private _handleContentClick = (ev: MouseEvent) => { ev.stopPropagation(); - this.closed = !this.branch ? false : !this.closed; + + const isCtrlDown = ev.ctrlKey; + this._selectItem(isCtrlDown); + + if (this.branch && !isCtrlDown) { + this.closed = !this.closed; + } }; + constructor() { + super(); + this._componentId = uniqueId('list-item-'); + } + connectedCallback(): void { super.connectedCallback(); this._mainSlotChange(); @@ -87,7 +127,7 @@ export class VscodeListItem extends VscElement { return html`
${this.branch && arrows diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index f7848b0c5..4a91f03c0 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -1,8 +1,10 @@ import {createContext} from '@lit/context'; +import {type VscodeListItem} from '../vscode-list-item'; export interface ListContext { indent: number; arrows: boolean; + selectedItems: Set; } export const listContext = createContext('vscode-list'); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index a9245524d..6a6ab4da4 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -24,6 +24,7 @@ export class VscodeList extends VscElement { private listData: ListContext = { arrows: false, indent: 8, + selectedItems: new Set(), }; @queryAssignedElements({selector: 'vscode-list-item'}) From 6c0ccc202c5da792099b197f589e8cf7b72a359a Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 16:03:44 +0100 Subject: [PATCH 05/68] Register selected item when selected manually --- src/vscode-list-item/vscode-list-item.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index f415b1aa9..d20aad658 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -116,6 +116,12 @@ export class VscodeListItem extends VscElement { this._mainSlotChange(); } + willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has('selected')) { + this.listData.selectedItems.add(this); + } + } + render(): TemplateResult { const {arrows, indent} = this.listData; let indentation = BASE_INDENT + this.level * indent; From 801f3f16a40eff9a2c5da4234715643366047c85 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 16:04:46 +0100 Subject: [PATCH 06/68] Fix icons colors in selected mode --- src/vscode-list-item/vscode-list-item.styles.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 28e8bcceb..29706c778 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -48,6 +48,10 @@ const styles: CSSResultGroup = [ background-color: var(--vscode-list-activeSelectionBackground); } + :host([selected]) ::slotted(vscode-icon) { + color: var(--vscode-list-activeSelectionForeground); + } + .arrow-container { align-items: center; display: var(--vsc-list-item-arrow-display); @@ -67,6 +71,10 @@ const styles: CSSResultGroup = [ transform: rotate(90deg); } + :host([selected]) .arrow-container svg { + fill: var(--vscode-list-activeSelectionForeground); + } + .icon-container { align-items: center; display: flex; From e0315858d37f1b678b5f49e41463a90894adfdcc Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 16:05:43 +0100 Subject: [PATCH 07/68] Remove unused things --- src/vscode-list-item/vscode-list-item.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index d20aad658..9fd0e0247 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -1,4 +1,4 @@ -import {TemplateResult, html, nothing} from 'lit'; +import {PropertyValues, TemplateResult, html, nothing} from 'lit'; import {consume} from '@lit/context'; import { customElement, @@ -11,7 +11,6 @@ import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; import {listContext, type ListContext} from '../vscode-list/list-context'; -import uniqueId from '../includes/uniqueId'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -53,8 +52,6 @@ export class VscodeListItem extends VscElement { @property({type: Boolean, reflect: true}) selected = false; - private _componentId: string; - @queryAssignedElements({selector: 'vscode-list-item'}) _initiallyAssignedListItems!: VscodeListItem[]; @@ -106,11 +103,6 @@ export class VscodeListItem extends VscElement { } }; - constructor() { - super(); - this._componentId = uniqueId('list-item-'); - } - connectedCallback(): void { super.connectedCallback(); this._mainSlotChange(); From 59f88cdcf30768d00ac25d26672a085a846b61e9 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 16:06:41 +0100 Subject: [PATCH 08/68] Reflect indent property --- src/vscode-list/vscode-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 6a6ab4da4..46d2b4c0f 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -17,7 +17,7 @@ export class VscodeList extends VscElement { @property({type: Boolean, reflect: true}) arrows = false; - @property({type: Number}) + @property({type: Number, reflect: true}) indent = 8; @provide({context: listContext}) From a9d0d15a0290322393f0c3271428e4a84d47e402 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 17:18:42 +0100 Subject: [PATCH 09/68] Add text content customizations ability --- dev/vscode-list.html | 2052 +---------------- .../vscode-list-item.styles.ts | 9 + src/vscode-list-item/vscode-list-item.ts | 9 +- 3 files changed, 34 insertions(+), 2036 deletions(-) diff --git a/dev/vscode-list.html b/dev/vscode-list.html index 35497017b..0817eb3e3 100644 --- a/dev/vscode-list.html +++ b/dev/vscode-list.html @@ -16,6 +16,7 @@
+

Basic example

@@ -61,2040 +62,6 @@

Basic example

ipsum - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - lorem - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - - - - - - ipsum - @@ -2107,6 +74,23 @@

Basic example

+ +
+

Basic example

+
+ + + + + + + Lorem ipsum dolor sit amet, deserunt excepteur velit. Small description + + + +
+
+
diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 29706c778..bba2857b3 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -84,6 +84,15 @@ const styles: CSSResultGroup = [ .text-content { line-height: 22px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .description { + font-size: 0.9em; + margin-left: 0.5em; + opacity: 0.95; } :host([closed]) ::slotted(vscode-list-item) { diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 9fd0e0247..80d08d7d9 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -148,8 +148,13 @@ export class VscodeListItem extends VscElement { : nothing} ${!this.branch ? html`` : nothing}
-
- +
+ +
From 1451b80adb27b3ebd4962834467891c963986209 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 17:40:32 +0100 Subject: [PATCH 10/68] Remove unused parts --- src/vscode-list/vscode-list.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 46d2b4c0f..0db8ae08e 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -30,16 +30,6 @@ export class VscodeList extends VscElement { @queryAssignedElements({selector: 'vscode-list-item'}) private _assignedListItems!: VscodeListItem[]; - private _selectedItems: VscodeListItem[] = []; - - public addSelectedItem(item: VscodeListItem) { - this._selectedItems.push(item); - } - - public removeSelectedItem(item: VscodeListItem) { - this._selectedItems = this._selectedItems.filter((i) => i !== item); - } - private _handleSlotChange = () => { this._assignedListItems.forEach((li) => (li.level = 0)); }; From 894f5dd1ba6fb7f122e003b74d9e14da10df77dc Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 17:42:26 +0100 Subject: [PATCH 11/68] Rename private members --- src/vscode-list-item/vscode-list-item.ts | 26 ++++++++++++------------ src/vscode-list/vscode-list.ts | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 80d08d7d9..dddfb9f82 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -32,14 +32,6 @@ const arrowIcon = html`): void { if (changedProperties.has('selected')) { - this.listData.selectedItems.add(this); + this._listData.selectedItems.add(this); } } render(): TemplateResult { - const {arrows, indent} = this.listData; + const {arrows, indent} = this._listData; let indentation = BASE_INDENT + this.level * indent; if (!this.branch && arrows) { diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 0db8ae08e..66363d986 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -21,7 +21,7 @@ export class VscodeList extends VscElement { indent = 8; @provide({context: listContext}) - private listData: ListContext = { + private _listData: ListContext = { arrows: false, indent: 8, selectedItems: new Set(), @@ -38,11 +38,11 @@ export class VscodeList extends VscElement { const {arrows, indent} = this; if (changedProperties.has('arrows')) { - this.listData = {...this.listData, arrows}; + this._listData = {...this._listData, arrows}; } if (changedProperties.has('indent')) { - this.listData = {...this.listData, indent}; + this._listData = {...this._listData, indent}; } } From c220cfc3314b554b98bfce5770440977b402009e Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 17:49:20 +0100 Subject: [PATCH 12/68] Rename event handler --- src/vscode-list-item/vscode-list-item.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index dddfb9f82..73ca02580 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -83,7 +83,7 @@ export class VscodeListItem extends VscElement { }); } - private _childrenSlotChange() { + private _handleChildrenSlotChange() { this.branch = this._childrenListItems.length > 0; this._childrenListItems.forEach((li) => (li.level = this.level + 1)); } @@ -159,7 +159,7 @@ export class VscodeListItem extends VscElement {
- +
`; } From 72840bb6755c521c33e365537415ba34b3933111 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 22:03:15 +0100 Subject: [PATCH 13/68] Save only the selected items to the context state --- src/vscode-list-item/vscode-list-item.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 73ca02580..544f57392 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -109,8 +109,8 @@ export class VscodeListItem extends VscElement { } willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has('selected')) { - this._listData.selectedItems.add(this); + if (changedProperties.has('selected') && this.selected) { + this.listData.selectedItems.add(this); } } From 7b61db57ccf506c69dd7664469ba69b57122f6c2 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 22:05:06 +0100 Subject: [PATCH 14/68] Do not ad extra padding to leaf items --- dev/vscode-list.html | 4 ++-- src/vscode-list-item/vscode-list-item.ts | 20 ++++++++++++++------ src/vscode-list/list-context.ts | 8 +++++++- src/vscode-list/vscode-list.ts | 20 +++++++++++++++++--- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/dev/vscode-list.html b/dev/vscode-list.html index 0817eb3e3..ffa84a753 100644 --- a/dev/vscode-list.html +++ b/dev/vscode-list.html @@ -21,14 +21,14 @@

Basic example

- + lorem - + diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 544f57392..91815b593 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -45,11 +45,12 @@ export class VscodeListItem extends VscElement { selected = false; @consume({context: listContext, subscribe: true}) - @state() - private _listData: ListContext = { + listData: ListContext = { arrows: false, indent: 8, selectedItems: new Set(), + hasBranchItem: false, + rootElement: null, }; @queryAssignedElements({selector: 'vscode-list-item'}) @@ -59,7 +60,7 @@ export class VscodeListItem extends VscElement { private _childrenListItems!: VscodeListItem[]; private _selectItem(isCtrlDown: boolean) { - const {selectedItems} = this._listData; + const {selectedItems} = this.listData; if (isCtrlDown) { if (this.selected) { @@ -86,6 +87,10 @@ export class VscodeListItem extends VscElement { private _handleChildrenSlotChange() { this.branch = this._childrenListItems.length > 0; this._childrenListItems.forEach((li) => (li.level = this.level + 1)); + + if (this.listData.rootElement) { + this.listData.rootElement.updateHasBranchItemFlag(); + } } private _handleMainSlotChange = () => { @@ -115,10 +120,10 @@ export class VscodeListItem extends VscElement { } render(): TemplateResult { - const {arrows, indent} = this._listData; + const {arrows, indent, hasBranchItem} = this.listData; let indentation = BASE_INDENT + this.level * indent; - if (!this.branch && arrows) { + if (!this.branch && arrows && hasBranchItem) { indentation += ARROW_CONTAINER_WIDTH; } @@ -159,7 +164,10 @@ export class VscodeListItem extends VscElement {
- +
`; } diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 4a91f03c0..d06374ee9 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -1,10 +1,16 @@ import {createContext} from '@lit/context'; -import {type VscodeListItem} from '../vscode-list-item'; +import type {VscodeListItem} from '../vscode-list-item'; +import type {VscodeList} from './vscode-list'; export interface ListContext { indent: number; arrows: boolean; selectedItems: Set; + /** If arrows are visible and `List` component has not any branch item, the + * extra padding should be removed in the leaf elements before the content + */ + hasBranchItem: boolean; + rootElement: VscodeList | null; } export const listContext = createContext('vscode-list'); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 66363d986..2832f287e 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -4,6 +4,7 @@ import { customElement, property, queryAssignedElements, + state, } from 'lit/decorators.js'; import {VscElement} from '../includes/VscElement'; import type {VscodeListItem} from '../vscode-list-item'; @@ -21,12 +22,25 @@ export class VscodeList extends VscElement { indent = 8; @provide({context: listContext}) - private _listData: ListContext = { + @property({attribute: false}) + listData: ListContext = { arrows: false, indent: 8, selectedItems: new Set(), + hasBranchItem: false, + rootElement: this, }; + /** + * @internal + * Updates `hasBranchItem` property in the context state in order to removing + * extra padding before the leaf elements, if it is required. + */ + updateHasBranchItemFlag() { + const hasBranchItem = this._assignedListItems.some((li) => li.branch); + this.listData = {...this.listData, hasBranchItem}; + } + @queryAssignedElements({selector: 'vscode-list-item'}) private _assignedListItems!: VscodeListItem[]; @@ -38,11 +52,11 @@ export class VscodeList extends VscElement { const {arrows, indent} = this; if (changedProperties.has('arrows')) { - this._listData = {...this._listData, arrows}; + this.listData = {...this.listData, arrows}; } if (changedProperties.has('indent')) { - this._listData = {...this._listData, indent}; + this.listData = {...this.listData, indent}; } } From 2f41e876bdf2f1997c570836dfa6738581742fb8 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 22:16:35 +0100 Subject: [PATCH 15/68] Rename context state --- src/vscode-list-item/vscode-list-item.ts | 12 ++++++------ src/vscode-list/vscode-list.ts | 9 ++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 91815b593..adfff0c46 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -45,7 +45,7 @@ export class VscodeListItem extends VscElement { selected = false; @consume({context: listContext, subscribe: true}) - listData: ListContext = { + private _listContextState: ListContext = { arrows: false, indent: 8, selectedItems: new Set(), @@ -60,7 +60,7 @@ export class VscodeListItem extends VscElement { private _childrenListItems!: VscodeListItem[]; private _selectItem(isCtrlDown: boolean) { - const {selectedItems} = this.listData; + const {selectedItems} = this._listContextState; if (isCtrlDown) { if (this.selected) { @@ -88,8 +88,8 @@ export class VscodeListItem extends VscElement { this.branch = this._childrenListItems.length > 0; this._childrenListItems.forEach((li) => (li.level = this.level + 1)); - if (this.listData.rootElement) { - this.listData.rootElement.updateHasBranchItemFlag(); + if (this._listContextState.rootElement) { + this._listContextState.rootElement.updateHasBranchItemFlag(); } } @@ -115,12 +115,12 @@ export class VscodeListItem extends VscElement { willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has('selected') && this.selected) { - this.listData.selectedItems.add(this); + this._listContextState.selectedItems.add(this); } } render(): TemplateResult { - const {arrows, indent, hasBranchItem} = this.listData; + const {arrows, indent, hasBranchItem} = this._listContextState; let indentation = BASE_INDENT + this.level * indent; if (!this.branch && arrows && hasBranchItem) { diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 2832f287e..6faeb6fd8 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -22,8 +22,7 @@ export class VscodeList extends VscElement { indent = 8; @provide({context: listContext}) - @property({attribute: false}) - listData: ListContext = { + private _listContextState: ListContext = { arrows: false, indent: 8, selectedItems: new Set(), @@ -38,7 +37,7 @@ export class VscodeList extends VscElement { */ updateHasBranchItemFlag() { const hasBranchItem = this._assignedListItems.some((li) => li.branch); - this.listData = {...this.listData, hasBranchItem}; + this._listContextState = {...this._listContextState, hasBranchItem}; } @queryAssignedElements({selector: 'vscode-list-item'}) @@ -52,11 +51,11 @@ export class VscodeList extends VscElement { const {arrows, indent} = this; if (changedProperties.has('arrows')) { - this.listData = {...this.listData, arrows}; + this._listContextState = {...this._listContextState, arrows}; } if (changedProperties.has('indent')) { - this.listData = {...this.listData, indent}; + this._listContextState = {...this._listContextState, indent}; } } From 3dd9896c7ee8e66ae023811cdad961f32ecd9134 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 25 Feb 2024 23:04:08 +0100 Subject: [PATCH 16/68] Add actions and decorations --- dev/vscode-list.html | 27 +++++++++++++++- .../vscode-list-item.styles.ts | 31 +++++++++++++++++++ src/vscode-list-item/vscode-list-item.ts | 5 ++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/dev/vscode-list.html b/dev/vscode-list.html index ffa84a753..14f37583a 100644 --- a/dev/vscode-list.html +++ b/dev/vscode-list.html @@ -8,6 +8,7 @@ + - - - -
- -
-

Basic example

-
- -
- - - - - - - lorem - - - - - - ipsum - - - - - lorem - - - - - - ipsum - - - - - - dolor - - - - - lorem - - - - - - ipsum - - - - - lorem - - - - - - lorem - - - - - - - - dolor - - - - - lorem - - - - - - ipsum - - - - - lorem - - - - - - lorem - - - - - - - - lorem - - - - - - - dolor - - - -
-
-
-
- - - - - -
- - - diff --git a/dev/vscode-list/basic-example.html b/dev/vscode-list/basic-example.html new file mode 100644 index 000000000..f378407f5 --- /dev/null +++ b/dev/vscode-list/basic-example.html @@ -0,0 +1,183 @@ + + + + + + VSCode Elements + + + + + + + +

Basic example

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + +
+
+
+ + From b2029735d5b2bb85c93257e16e3902e93aafbe40 Mon Sep 17 00:00:00 2001 From: bendera Date: Fri, 17 Jan 2025 22:27:16 +0100 Subject: [PATCH 29/68] Update lock file with npm i --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a2ada703f..832d60b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.17.0", "license": "MIT", "dependencies": { - "lit": "^3.2.1", - "@lit/context": "^1.1.0" + "@lit/context": "^1.1.0", + "lit": "^3.2.1" }, "devDependencies": { "@awmottaz/prettier-plugin-void-html": "^1.8.0", From b92d4317cea24bc1df4d0242d1845b7bce5586fd Mon Sep 17 00:00:00 2001 From: bendera Date: Fri, 17 Jan 2025 22:29:50 +0100 Subject: [PATCH 30/68] Upgrade @lit/context --- package-lock.json | 15 ++++++++------- package.json | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 832d60b7a..a8c0eada0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.17.0", "license": "MIT", "dependencies": { - "@lit/context": "^1.1.0", + "@lit/context": "^1.1.3", "lit": "^3.2.1" }, "devDependencies": { @@ -2668,9 +2668,10 @@ "license": "BSD-3-Clause" }, "node_modules/@lit/context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.0.tgz", - "integrity": "sha512-fCyv4dsH05wCNm3AKbB+PdYbXGJd/XT8OOwo4hVmD4COq5wOWJlQreGAMDvmHZ7osqxuu06Y4nmP6ooXpN7ErA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.3.tgz", + "integrity": "sha512-Auh37F4S0PZM93HTDfZWs97mmzaQ7M3vnTc9YvxAGyP3UItSK/8Fs0vTOGT+njuvOwbKio/l8Cx/zWL4vkutpQ==", + "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^1.6.2 || ^2.0.0" } @@ -13548,9 +13549,9 @@ "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==" }, "@lit/context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.0.tgz", - "integrity": "sha512-fCyv4dsH05wCNm3AKbB+PdYbXGJd/XT8OOwo4hVmD4COq5wOWJlQreGAMDvmHZ7osqxuu06Y4nmP6ooXpN7ErA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.3.tgz", + "integrity": "sha512-Auh37F4S0PZM93HTDfZWs97mmzaQ7M3vnTc9YvxAGyP3UItSK/8Fs0vTOGT+njuvOwbKio/l8Cx/zWL4vkutpQ==", "requires": { "@lit/reactive-element": "^1.6.2 || ^2.0.0" } diff --git a/package.json b/package.json index 3700e0442..78195d267 100644 --- a/package.json +++ b/package.json @@ -132,8 +132,8 @@ }, "homepage": "https://vscode-elements.github.io", "dependencies": { - "lit": "^3.2.1", - "@lit/context": "^1.1.0" + "@lit/context": "^1.1.3", + "lit": "^3.2.1" }, "devDependencies": { "@awmottaz/prettier-plugin-void-html": "^1.8.0", From 247181c7faae94a20fc0b09b6a40cfda4c7e1929 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 19 Jun 2025 22:46:47 +0200 Subject: [PATCH 31/68] FIx TS issues --- src/vscode-list-item/vscode-list-item.ts | 10 +++++----- src/vscode-list/vscode-list.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index c3e8557a6..06bc2e079 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -34,7 +34,7 @@ const arrowIcon = html`): void { + protected override willUpdate(changedProperties: PropertyValues): void { const {arrows, indent} = this; if (changedProperties.has('arrows')) { @@ -183,20 +183,20 @@ export class VscodeList extends VscElement { this.tabIndex = this._originalTabIndex; } - connectedCallback(): void { + override connectedCallback(): void { super.connectedCallback(); this._originalTabIndex = this.tabIndex; this.addEventListener('keydown', this._handleComponentKeyDown); } - disconnectedCallback(): void { + override disconnectedCallback(): void { super.disconnectedCallback(); this.removeEventListener('keydown', this._handleComponentKeyDown); } - render(): TemplateResult { + override render(): TemplateResult { return html`
`; From e3f648b173057c87d7799236c49001d5e328ab7b Mon Sep 17 00:00:00 2001 From: bendera Date: Fri, 4 Jul 2025 23:50:04 +0200 Subject: [PATCH 32/68] Fix select previous closed branch item --- dev/vscode-list/basic-example.html | 47 +++++++++++++++++++++++- src/vscode-list-item/vscode-list-item.ts | 10 +---- src/vscode-list/helpers.ts | 14 ++++--- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/dev/vscode-list/basic-example.html b/dev/vscode-list/basic-example.html index f378407f5..419099b50 100644 --- a/dev/vscode-list/basic-example.html +++ b/dev/vscode-list/basic-example.html @@ -110,7 +110,7 @@

Basic example

- + Basic example lorem + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 06bc2e079..550afe946 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -1,4 +1,4 @@ -import {PropertyValues, TemplateResult, html, nothing} from 'lit'; +import {TemplateResult, html, nothing} from 'lit'; import {consume} from '@lit/context'; import { customElement, @@ -10,11 +10,7 @@ import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; import {listContext, type ListContext} from '../vscode-list/list-context'; -import { - findAncestorOnSpecificLevel, - initPathTrackerProps, - selectItemAndAllVisibleDescendants, -} from '../vscode-list/helpers'; +import {initPathTrackerProps} from '../vscode-list/helpers'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -168,8 +164,6 @@ export class VscodeListItem extends VscElement { } } - console.log(i); - return i; } diff --git a/src/vscode-list/helpers.ts b/src/vscode-list/helpers.ts index 0bc164bbb..39b645cbb 100644 --- a/src/vscode-list/helpers.ts +++ b/src/vscode-list/helpers.ts @@ -11,7 +11,6 @@ export const initPathTrackerProps = ( parentElement: VscodeList | VscodeListItem, items: VscodeListItem[] ): void => { - console.log('initTrackerProps'); const numChildren = items.length; const parentElementLevel = isListRoot(parentElement) ? -1 @@ -24,15 +23,19 @@ export const initPathTrackerProps = ( parentElement.dataset.numChildren = numChildren.toString(); items.forEach((item, i) => { + const level = parentElementLevel + 1; + const index = i.toString(); + item.level = parentElementLevel + 1; item.dataset.level = (parentElementLevel + 1).toString(); item.dataset.index = i.toString(); item.dataset.last = i === numChildren - 1 ? 'true' : 'false'; + item.dataset.id = `${level}_${index}`; }); }; export const findLastChildItem = (item: VscodeListItem): VscodeListItem => { - const children = item.querySelectorAll('vscode-list-item'); + const children = item.querySelectorAll(':scope > vscode-list-item'); if (children.length < 1) { return item; @@ -120,7 +123,7 @@ export const findPrevItem = (item: VscodeListItem): VscodeListItem | null => { } const prevSibling = parentElement.querySelector( - `vscode-list-item[data-index="${index - 1}"]` + `:scope vscode-list-item[data-index="${index - 1}"]` ); if (!prevSibling) { @@ -130,7 +133,9 @@ export const findPrevItem = (item: VscodeListItem): VscodeListItem | null => { } if (prevSibling && prevSibling.branch && prevSibling.open) { - return findLastChildItem(prevSibling); + const lastChild = findLastChildItem(prevSibling); + + return lastChild; } return prevSibling; @@ -140,7 +145,6 @@ export const findAncestorOnSpecificLevel = ( item: VscodeListItem, level: number ): VscodeListItem | null => { - console.log(level); if ( !item.parentElement || item.parentElement.tagName.toUpperCase() !== 'VSCODE-LIST-ITEM' From d8bd0eca89018c6fbbbe63f5c2aae7bcc130e51d Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 5 Jul 2025 01:22:12 +0200 Subject: [PATCH 33/68] Focus item with keyboard --- .../vscode-list-item.styles.ts | 2 +- src/vscode-list-item/vscode-list-item.ts | 25 +++++++++---- src/vscode-list/list-context.ts | 1 - src/vscode-list/vscode-list.ts | 37 ++++++++----------- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 39da8dbb3..61176bff4 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -50,7 +50,7 @@ const styles: CSSResultGroup = [ outline: none; } - :host(:focus) .content { + :host(:focus) .content.active { outline-color: var(--vscode-list-focusAndSelectionOutline, var(--vscode-list-focusOutline)); outline-style: solid; outline-width: 1px; diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 550afe946..882630363 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -32,6 +32,16 @@ const arrowIcon = html` { - return; - }, hasBranchItem: false, rootElement: null, }; @@ -186,12 +190,12 @@ export class VscodeListItem extends VscElement { this._listContextState.itemListUpToDate = false; }; + // TODO: Will be replace with "activate" logic private _handleComponentFocus = () => { if ( this._listContextState.focusedItem && this._listContextState.focusedItem !== this ) { - this._listContextState.focusedItem.tabIndex = -1; this._listContextState.prevFocusedItem = this._listContextState.focusedItem; this._listContextState.focusedItem = null; @@ -244,9 +248,14 @@ export class VscodeListItem extends VscElement { indentation += ARROW_CONTAINER_WIDTH; } + const contentClasses = { + content: true, + active: this.active, + }; + return html`
diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 30c0e996d..7f9cda94a 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -11,7 +11,6 @@ export interface ListContext { itemListUpToDate: boolean; focusedItem: VscodeListItem | null; prevFocusedItem: VscodeListItem | null; - focusItem: (item: VscodeListItem) => void; /** If arrows are visible and `List` component has not any branch item, the * extra padding should be removed in the leaf elements before the content */ diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index e139144b7..5b3213428 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -60,7 +60,6 @@ export class VscodeList extends VscElement { prevFocusedItem: null, hasBranchItem: false, rootElement: this, - focusItem: this._focusItem, }; /** @@ -73,12 +72,10 @@ export class VscodeList extends VscElement { this._listContextState = {...this._listContextState, hasBranchItem}; } - private _originalTabIndex = 0; - @queryAssignedElements({selector: 'vscode-list-item'}) private _assignedListItems!: VscodeListItem[]; - private _focusPrevItem() { + private _activatePrevItem() { if (this._listContextState.focusedItem) { const item = findPrevItem(this._listContextState.focusedItem); @@ -87,12 +84,12 @@ export class VscodeList extends VscElement { // this._listContextState.focusedItem = item; // item.focused = true; // item.focus(); - this._focusItem(item); + this._activateItem(item); } } } - private _focusNextItem() { + private _activateNextItem() { if (this._listContextState.focusedItem) { const item = findNextItem(this._listContextState.focusedItem); @@ -101,20 +98,23 @@ export class VscodeList extends VscElement { // this._listContextState.focusedItem = item; // item.focused = true; // item.focus(); - this._focusItem(item); + this._activateItem(item); } } } - private _focusItem(item: VscodeListItem) { - const {focusedItem} = this._listContextState; - - if (focusedItem) { + private _activateItem(item: VscodeListItem) { + if (this._listContextState.focusedItem) { + this._listContextState.focusedItem.active = false; this._listContextState.focusedItem = null; } - item.focus(); + item.active = true; this._listContextState.focusedItem = item; + + this.updateComplete.then(() => { + this._listContextState.focusedItem?.focus(); + }); } private _handleComponentKeyDown = (ev: KeyboardEvent) => { @@ -128,14 +128,14 @@ export class VscodeList extends VscElement { if (key === 'ArrowDown' || key === 'ArrowUp') { if (this._listContextState.focusedItem) { if (key === 'ArrowDown') { - this._focusNextItem(); + this._activateNextItem(); } if (key === 'ArrowUp') { - this._focusPrevItem(); + this._activatePrevItem(); } } else { - this._focusItem(this._assignedListItems[0]); + this._activateItem(this._assignedListItems[0]); } } @@ -163,7 +163,7 @@ export class VscodeList extends VscElement { const firstChild = this.querySelector('vscode-list-item'); if (firstChild) { - firstChild.tabIndex = 0; + firstChild.active = true; } }; @@ -179,14 +179,9 @@ export class VscodeList extends VscElement { } } - setOriginalTabIndex() { - this.tabIndex = this._originalTabIndex; - } - override connectedCallback(): void { super.connectedCallback(); - this._originalTabIndex = this.tabIndex; this.addEventListener('keydown', this._handleComponentKeyDown); } From 65ec937ce70e870829d453c18223a5bd9baa245f Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 19:43:31 +0200 Subject: [PATCH 34/68] Set the default active element --- dev/vscode-list/basic-example.html | 2 +- src/vscode-list-item/vscode-list-item.ts | 43 +++++++++++++++++------- src/vscode-list/ListController.ts | 13 +++++++ src/vscode-list/list-context.ts | 5 +++ src/vscode-list/vscode-list.ts | 25 +++++++++++--- 5 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 src/vscode-list/ListController.ts diff --git a/dev/vscode-list/basic-example.html b/dev/vscode-list/basic-example.html index 419099b50..6d3bc8f2e 100644 --- a/dev/vscode-list/basic-example.html +++ b/dev/vscode-list/basic-example.html @@ -210,7 +210,7 @@

Basic example

lorem - + ('vscode-list'); + +export const listControllerContext = createContext( + Symbol('listControllerContext') +); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 5b3213428..6ae8ca957 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -8,8 +8,13 @@ import { import {VscElement} from '../includes/VscElement'; import type {VscodeListItem} from '../vscode-list-item'; import styles from './vscode-list.styles'; -import {listContext, type ListContext} from './list-context'; +import { + listContext, + listControllerContext, + type ListContext, +} from './list-context'; import {findNextItem, findPrevItem, initPathTrackerProps} from './helpers'; +import {ListController} from './ListController'; type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | ' '; @@ -62,6 +67,9 @@ export class VscodeList extends VscElement { rootElement: this, }; + @provide({context: listControllerContext}) + private _listController = new ListController(); + /** * @internal * Updates `hasBranchItem` property in the context state in order to removing @@ -160,11 +168,18 @@ export class VscodeList extends VscElement { this._listContextState.itemListUpToDate = false; initPathTrackerProps(this, this._assignedListItems); - const firstChild = this.querySelector('vscode-list-item'); + this.updateComplete.then(() => { + if (this._listController.activeItem === null) { + const firstChild = this.querySelector( + ':scope > vscode-list-item' + ); - if (firstChild) { - firstChild.active = true; - } + if (firstChild) { + firstChild.active = true; + this._listController.activeItem = firstChild; + } + } + }); }; protected override willUpdate(changedProperties: PropertyValues): void { From 6dc8f67e796cc72e8f883fa0511a1ec933aabd98 Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 20:04:24 +0200 Subject: [PATCH 35/68] Focus an item programmatically --- dev/vscode-list/basic-example.html | 21 ++++++++++++++++----- src/vscode-list-item/vscode-list-item.ts | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/dev/vscode-list/basic-example.html b/dev/vscode-list/basic-example.html index 6d3bc8f2e..acf6306a6 100644 --- a/dev/vscode-list/basic-example.html +++ b/dev/vscode-list/basic-example.html @@ -1,14 +1,14 @@ - - + + VSCode Elements + >
diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 2150b3236..926651595 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -239,7 +239,7 @@ export class VscodeListItem extends VscElement { } } - this._focusItem(this); + this.active = true; if (!isShiftDown) { this._listContextState.prevFocusedItem = this; From 92077caddeac1e35dc5c5779bb9ba03ca17d079e Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 21:01:56 +0200 Subject: [PATCH 36/68] Add config context --- src/vscode-list-item/vscode-list-item.ts | 8 +++++- src/vscode-list/list-context.ts | 10 +++++++ src/vscode-list/vscode-list.ts | 35 ++++++++++++++++++++---- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 926651595..4c9ddcccd 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -10,6 +10,8 @@ import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; import { + Config, + configContext, listContext, listControllerContext, type ListContext, @@ -95,6 +97,9 @@ export class VscodeListItem extends VscElement { @consume({context: listControllerContext}) private _listController!: ListController; + @consume({context: configContext, subscribe: true}) + private _configContext!: Config; + @queryAssignedElements({selector: 'vscode-list-item'}) private _initiallyAssignedListItems!: VscodeListItem[]; @@ -260,7 +265,8 @@ export class VscodeListItem extends VscElement { } override render(): TemplateResult { - const {arrows, indent, hasBranchItem} = this._listContextState; + const {arrows, indent} = this._configContext; + const {hasBranchItem} = this._listContextState; let indentation = BASE_INDENT + this.level * indent; if (!this.branch && arrows && hasBranchItem) { diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 838a136cb..ef66d9ebd 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -19,8 +19,18 @@ export interface ListContext { rootElement: VscodeList | null; } +export interface Config { + arrows: boolean; + indent: number; + indentGuides: boolean; +} + export const listContext = createContext('vscode-list'); +export const configContext = createContext( + Symbol('configContext') +); + export const listControllerContext = createContext( Symbol('listControllerContext') ); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 6ae8ca957..9fc3e7b13 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -9,6 +9,8 @@ import {VscElement} from '../includes/VscElement'; import type {VscodeListItem} from '../vscode-list-item'; import styles from './vscode-list.styles'; import { + Config, + configContext, listContext, listControllerContext, type ListContext, @@ -25,16 +27,22 @@ const listenedKeys: ListenedKey[] = [ 'Enter', 'Escape', ]; +const DEFAULT_ARROWS = false; +const DEFAULT_INDENT = 8; +const DEFAULT_INDENT_GUIDES = false; @customElement('vscode-list') export class VscodeList extends VscElement { static override styles = styles; @property({type: Boolean, reflect: true}) - arrows = false; + arrows = DEFAULT_ARROWS; @property({type: Number, reflect: true}) - indent = 8; + indent = DEFAULT_INDENT; + + @property({type: Boolean, attribute: 'indent-guides', reflect: true}) + indentGuides = DEFAULT_INDENT_GUIDES; @property({type: Boolean, reflect: true, attribute: 'multi-select'}) set multiSelect(val: boolean) { @@ -70,6 +78,13 @@ export class VscodeList extends VscElement { @provide({context: listControllerContext}) private _listController = new ListController(); + @provide({context: configContext}) + private _configContext: Config = { + arrows: DEFAULT_ARROWS, + indent: DEFAULT_INDENT, + indentGuides: DEFAULT_INDENT_GUIDES, + }; + /** * @internal * Updates `hasBranchItem` property in the context state in order to removing @@ -182,16 +197,24 @@ export class VscodeList extends VscElement { }); }; - protected override willUpdate(changedProperties: PropertyValues): void { - const {arrows, indent} = this; + private _updateConfigContext(changedProperties: PropertyValues) { + const {arrows, indent, indentGuides} = this; if (changedProperties.has('arrows')) { - this._listContextState = {...this._listContextState, arrows}; + this._configContext = {...this._configContext, arrows}; } if (changedProperties.has('indent')) { - this._listContextState = {...this._listContextState, indent}; + this._configContext = {...this._configContext, indent}; } + + if (changedProperties.has('indentGuides')) { + this._configContext = {...this._configContext, indentGuides}; + } + } + + protected override willUpdate(changedProperties: PropertyValues): void { + this._updateConfigContext(changedProperties); } override connectedCallback(): void { From e9225d899918a37b52a68ae3474314c41700173d Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 21:05:30 +0200 Subject: [PATCH 37/68] Remove unused property --- src/vscode-list/vscode-list.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 9fc3e7b13..1fed8bf56 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -58,9 +58,6 @@ export class VscodeList extends VscElement { @property({type: String, reflect: true}) override role = 'tree'; - @property({type: Number, reflect: true}) - numChildren = 0; - @provide({context: listContext}) private _listContextState: ListContext = { arrows: false, From 3b1f9a0ccf7afe0b6bad8bf49b8dde3e22bcb448 Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 23:30:39 +0200 Subject: [PATCH 38/68] Add path info to item --- src/vscode-list-item/vscode-list-item.ts | 9 +++++++++ src/vscode-list/helpers.ts | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 4c9ddcccd..a0741c449 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -61,6 +61,15 @@ export class VscodeListItem extends VscElement { } private _selected = false; + set path(newPath: number[]) { + this._path = newPath; + } + get path(): number[] { + return this._path; + } + + private _path: number[] = []; + protected override willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has('active')) { if (this.active) { diff --git a/src/vscode-list/helpers.ts b/src/vscode-list/helpers.ts index 39b645cbb..ec11fc42d 100644 --- a/src/vscode-list/helpers.ts +++ b/src/vscode-list/helpers.ts @@ -26,11 +26,18 @@ export const initPathTrackerProps = ( const level = parentElementLevel + 1; const index = i.toString(); + if ('path' in parentElement) { + item.path = [...parentElement.path, i]; + } else { + item.path = [i]; + } + item.level = parentElementLevel + 1; item.dataset.level = (parentElementLevel + 1).toString(); item.dataset.index = i.toString(); item.dataset.last = i === numChildren - 1 ? 'true' : 'false'; item.dataset.id = `${level}_${index}`; + item.dataset.path = item.path.join('.'); }); }; From 20d80244fd9bdd1bdf705c0a6933d6088a16798f Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 8 Jul 2025 23:59:44 +0200 Subject: [PATCH 39/68] Remove outdated function --- src/vscode-list-item/vscode-list-item.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index a0741c449..2ea85144f 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -115,17 +115,6 @@ export class VscodeListItem extends VscElement { @queryAssignedElements({selector: 'vscode-list-item', slot: 'children'}) private _childrenListItems!: VscodeListItem[]; - private _focusItem(item: VscodeListItem) { - const {focusedItem} = this._listContextState; - - if (focusedItem) { - this._listContextState.focusedItem = null; - } - - item.focus(); - this._listContextState.focusedItem = item; - } - private _selectItem(isCtrlDown: boolean) { const {selectedItems, multiSelect} = this._listContextState; From fad7f58fef9d6c58e545700703448a3bfd6e0401 Mon Sep 17 00:00:00 2001 From: bendera Date: Wed, 9 Jul 2025 22:27:13 +0200 Subject: [PATCH 40/68] Restructure --- src/vscode-list-item/vscode-list-item.ts | 80 +++++++++++++++--------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 2ea85144f..d5bc2baf9 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -39,6 +39,8 @@ const arrowIcon = html` Date: Thu, 10 Jul 2025 17:42:41 +0200 Subject: [PATCH 41/68] Simplify the active item focus logic --- src/vscode-list/vscode-list.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 1fed8bf56..1dc5d5cae 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -104,7 +104,7 @@ export class VscodeList extends VscElement { // this._listContextState.focusedItem = item; // item.focused = true; // item.focus(); - this._activateItem(item); + this._focusItem(item); } } } @@ -118,22 +118,16 @@ export class VscodeList extends VscElement { // this._listContextState.focusedItem = item; // item.focused = true; // item.focus(); - this._activateItem(item); + this._focusItem(item); } } } - private _activateItem(item: VscodeListItem) { - if (this._listContextState.focusedItem) { - this._listContextState.focusedItem.active = false; - this._listContextState.focusedItem = null; - } - + private _focusItem(item: VscodeListItem) { item.active = true; - this._listContextState.focusedItem = item; - this.updateComplete.then(() => { - this._listContextState.focusedItem?.focus(); + item.updateComplete.then(() => { + item.focus(); }); } @@ -155,7 +149,7 @@ export class VscodeList extends VscElement { this._activatePrevItem(); } } else { - this._activateItem(this._assignedListItems[0]); + this._focusItem(this._assignedListItems[0]); } } From b78ef028be38e3b59e8e3e13234c2838422bd318 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 17:51:29 +0200 Subject: [PATCH 42/68] Activate the first item by default --- src/vscode-list/vscode-list.test.ts | 22 ++++++++++++++++++++++ src/vscode-list/vscode-list.ts | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/vscode-list/vscode-list.test.ts diff --git a/src/vscode-list/vscode-list.test.ts b/src/vscode-list/vscode-list.test.ts new file mode 100644 index 000000000..0ecf872d7 --- /dev/null +++ b/src/vscode-list/vscode-list.test.ts @@ -0,0 +1,22 @@ +import {aTimeout, expect, fixture, html} from '@open-wc/testing'; +import {VscodeListItem} from '../main.js'; +import {VscodeList} from './index.js'; + +describe('vscode-list', () => { + it('is defined', () => { + const el = document.createElement('vscode-list'); + expect(el).to.instanceOf(VscodeList); + }); + + it('focuses first item by default', async () => { + const el = await fixture(html` + + Item 1 + Item 2 + + `); + const firstItem = el.querySelector('vscode-list-item')!; + + expect(firstItem.active).to.be.true; + }); +}); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 1dc5d5cae..c297bf445 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -182,7 +182,6 @@ export class VscodeList extends VscElement { if (firstChild) { firstChild.active = true; - this._listController.activeItem = firstChild; } } }); From c2adc7b473ffec7d4b3738c4ac21a3230595522e Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 18:03:33 +0200 Subject: [PATCH 43/68] Add dev pages --- dev/vscode-list/default-active-item.html | 239 ++++++++++++++++++ ...asic-example.html => set-active-item.html} | 0 2 files changed, 239 insertions(+) create mode 100644 dev/vscode-list/default-active-item.html rename dev/vscode-list/{basic-example.html => set-active-item.html} (100%) diff --git a/dev/vscode-list/default-active-item.html b/dev/vscode-list/default-active-item.html new file mode 100644 index 000000000..3c2839850 --- /dev/null +++ b/dev/vscode-list/default-active-item.html @@ -0,0 +1,239 @@ + + + + + + VSCode Elements + + + + + + + +

Basic example

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + + + +
+
+
+ + diff --git a/dev/vscode-list/basic-example.html b/dev/vscode-list/set-active-item.html similarity index 100% rename from dev/vscode-list/basic-example.html rename to dev/vscode-list/set-active-item.html From 9ef8068b4c513920171af91afb4a3e079d931d82 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 21:01:40 +0200 Subject: [PATCH 44/68] Finalize context logic --- src/vscode-list-item/vscode-list-item.ts | 25 +++++++----------- src/vscode-list/ListController.ts | 13 ---------- src/vscode-list/list-context.ts | 23 +++++++---------- src/vscode-list/vscode-list.ts | 32 +++++++++--------------- 4 files changed, 30 insertions(+), 63 deletions(-) delete mode 100644 src/vscode-list/ListController.ts diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index d5bc2baf9..dca85a909 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -10,14 +10,12 @@ import {VscElement} from '../includes/VscElement'; import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; import { - Config, + ConfigContext, configContext, listContext, - listControllerContext, type ListContext, } from '../vscode-list/list-context'; import {initPathTrackerProps} from '../vscode-list/helpers'; -import {ListController} from '../vscode-list/ListController'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -78,9 +76,6 @@ export class VscodeListItem extends VscElement { @consume({context: listContext, subscribe: true}) private _listContextState: ListContext = { - arrows: false, - indent: 8, - multiSelect: false, selectedItems: new Set(), allItems: null, itemListUpToDate: false, @@ -88,13 +83,11 @@ export class VscodeListItem extends VscElement { prevFocusedItem: null, hasBranchItem: false, rootElement: null, + activeItem: null, }; - @consume({context: listControllerContext}) - private _listController!: ListController; - @consume({context: configContext, subscribe: true}) - private _configContext!: Config; + private _configContext!: ConfigContext; @queryAssignedElements({selector: 'vscode-list-item'}) private _initiallyAssignedListItems!: VscodeListItem[]; @@ -122,15 +115,15 @@ export class VscodeListItem extends VscElement { protected override willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has('active')) { if (this.active) { - if (this._listController.activeItem) { - this._listController.activeItem.active = false; + if (this._listContextState.activeItem) { + this._listContextState.activeItem.active = false; } - this._listController.activeItem = this; + this._listContextState.activeItem = this; this.tabIndex = 0; } else { - if (this._listController.activeItem === this) { - this._listController.activeItem = null; + if (this._listContextState.activeItem === this) { + this._listContextState.activeItem = null; } this.tabIndex = -1; @@ -268,7 +261,7 @@ export class VscodeListItem extends VscElement { } else { this._selectItem(isCtrlDown); - if (this.branch && !(this._listContextState.multiSelect && isCtrlDown)) { + if (this.branch && !(this._configContext.multiSelect && isCtrlDown)) { this.open = !this.open; } } diff --git a/src/vscode-list/ListController.ts b/src/vscode-list/ListController.ts deleted file mode 100644 index e7f4f88ca..000000000 --- a/src/vscode-list/ListController.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {VscodeListItem} from '../vscode-list-item'; - -export class ListController { - private _activeItem: VscodeListItem | null = null; - - set activeItem(item: VscodeListItem | null) { - this._activeItem = item; - } - - get activeItem(): VscodeListItem | null { - return this._activeItem; - } -} diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index ef66d9ebd..ac14b6957 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -1,12 +1,8 @@ import {createContext} from '@lit/context'; import type {VscodeListItem} from '../vscode-list-item'; import type {VscodeList} from './vscode-list'; -import {ListController} from './ListController'; export interface ListContext { - indent: number; - arrows: boolean; - multiSelect: boolean; selectedItems: Set; allItems: NodeListOf | null; itemListUpToDate: boolean; @@ -17,20 +13,19 @@ export interface ListContext { */ hasBranchItem: boolean; rootElement: VscodeList | null; + activeItem: VscodeListItem | null; } -export interface Config { - arrows: boolean; - indent: number; - indentGuides: boolean; +export const listContext = createContext('vscode-list'); + +export interface ConfigContext { + readonly arrows: boolean; + readonly indent: number; + readonly indentGuides: boolean; + readonly multiSelect: boolean; } -export const listContext = createContext('vscode-list'); -export const configContext = createContext( +export const configContext = createContext( Symbol('configContext') ); - -export const listControllerContext = createContext( - Symbol('listControllerContext') -); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index c297bf445..b8904fde1 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -9,14 +9,12 @@ import {VscElement} from '../includes/VscElement'; import type {VscodeListItem} from '../vscode-list-item'; import styles from './vscode-list.styles'; import { - Config, + ConfigContext, configContext, listContext, - listControllerContext, type ListContext, } from './list-context'; import {findNextItem, findPrevItem, initPathTrackerProps} from './helpers'; -import {ListController} from './ListController'; type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | ' '; @@ -30,6 +28,7 @@ const listenedKeys: ListenedKey[] = [ const DEFAULT_ARROWS = false; const DEFAULT_INDENT = 8; const DEFAULT_INDENT_GUIDES = false; +const DEFAULT_MULTI_SELECT = false; @customElement('vscode-list') export class VscodeList extends VscElement { @@ -45,14 +44,7 @@ export class VscodeList extends VscElement { indentGuides = DEFAULT_INDENT_GUIDES; @property({type: Boolean, reflect: true, attribute: 'multi-select'}) - set multiSelect(val: boolean) { - this._multiSelect = val; - this._listContextState.multiSelect = val; - } - get multiSelect(): boolean { - return this._multiSelect; - } - private _multiSelect = false; + multiSelect = DEFAULT_MULTI_SELECT; /** @internal */ @property({type: String, reflect: true}) @@ -60,9 +52,7 @@ export class VscodeList extends VscElement { @provide({context: listContext}) private _listContextState: ListContext = { - arrows: false, - indent: 8, - multiSelect: false, + activeItem: null, selectedItems: new Set(), allItems: null, itemListUpToDate: false, @@ -72,14 +62,12 @@ export class VscodeList extends VscElement { rootElement: this, }; - @provide({context: listControllerContext}) - private _listController = new ListController(); - @provide({context: configContext}) - private _configContext: Config = { + private _configContext: ConfigContext = { arrows: DEFAULT_ARROWS, indent: DEFAULT_INDENT, indentGuides: DEFAULT_INDENT_GUIDES, + multiSelect: DEFAULT_MULTI_SELECT, }; /** @@ -175,7 +163,7 @@ export class VscodeList extends VscElement { initPathTrackerProps(this, this._assignedListItems); this.updateComplete.then(() => { - if (this._listController.activeItem === null) { + if (this._listContextState.activeItem === null) { const firstChild = this.querySelector( ':scope > vscode-list-item' ); @@ -188,7 +176,7 @@ export class VscodeList extends VscElement { }; private _updateConfigContext(changedProperties: PropertyValues) { - const {arrows, indent, indentGuides} = this; + const {arrows, indent, indentGuides, multiSelect} = this; if (changedProperties.has('arrows')) { this._configContext = {...this._configContext, arrows}; @@ -201,6 +189,10 @@ export class VscodeList extends VscElement { if (changedProperties.has('indentGuides')) { this._configContext = {...this._configContext, indentGuides}; } + + if (changedProperties.has('multiSelect')) { + this._configContext = {...this._configContext, multiSelect}; + } } protected override willUpdate(changedProperties: PropertyValues): void { From 7046fbf0bcd462a671a0310897ac552fb5bfa0ec Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 21:14:25 +0200 Subject: [PATCH 45/68] Fix multiSelect --- src/vscode-list-item/vscode-list-item.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index dca85a909..3832585bb 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -136,7 +136,8 @@ export class VscodeListItem extends VscElement { //#region private methods private _selectItem(isCtrlDown: boolean) { - const {selectedItems, multiSelect} = this._listContextState; + const {selectedItems} = this._listContextState; + const {multiSelect} = this._configContext; if (multiSelect && isCtrlDown) { if (this.selected) { From 4aa4b96bd49f23874d15a8755148a03809fe364f Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 22:44:50 +0200 Subject: [PATCH 46/68] Restructure the file --- src/vscode-list/vscode-list.ts | 96 +++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index b8904fde1..cfd81a66a 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -34,6 +34,8 @@ const DEFAULT_MULTI_SELECT = false; export class VscodeList extends VscElement { static override styles = styles; + //#region properties + @property({type: Boolean, reflect: true}) arrows = DEFAULT_ARROWS; @@ -50,6 +52,10 @@ export class VscodeList extends VscElement { @property({type: String, reflect: true}) override role = 'tree'; + //#endregion + + //#region private variables + @provide({context: listContext}) private _listContextState: ListContext = { activeItem: null, @@ -70,6 +76,31 @@ export class VscodeList extends VscElement { multiSelect: DEFAULT_MULTI_SELECT, }; + @queryAssignedElements({selector: 'vscode-list-item'}) + private _assignedListItems!: VscodeListItem[]; + + //#region lifecycle methods + + override connectedCallback(): void { + super.connectedCallback(); + + this.addEventListener('keydown', this._handleComponentKeyDown); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + + this.removeEventListener('keydown', this._handleComponentKeyDown); + } + + protected override willUpdate(changedProperties: PropertyValues): void { + this._updateConfigContext(changedProperties); + } + + //#endregion + + //#region public methods + /** * @internal * Updates `hasBranchItem` property in the context state in order to removing @@ -80,8 +111,29 @@ export class VscodeList extends VscElement { this._listContextState = {...this._listContextState, hasBranchItem}; } - @queryAssignedElements({selector: 'vscode-list-item'}) - private _assignedListItems!: VscodeListItem[]; + //#endregion + + //#region private methods + + private _updateConfigContext(changedProperties: PropertyValues) { + const {arrows, indent, indentGuides, multiSelect} = this; + + if (changedProperties.has('arrows')) { + this._configContext = {...this._configContext, arrows}; + } + + if (changedProperties.has('indent')) { + this._configContext = {...this._configContext, indent}; + } + + if (changedProperties.has('indentGuides')) { + this._configContext = {...this._configContext, indentGuides}; + } + + if (changedProperties.has('multiSelect')) { + this._configContext = {...this._configContext, multiSelect}; + } + } private _activatePrevItem() { if (this._listContextState.focusedItem) { @@ -119,6 +171,10 @@ export class VscodeList extends VscElement { }); } + //#endregion + + //#region event handlers + private _handleComponentKeyDown = (ev: KeyboardEvent) => { const key = ev.key as ListenedKey; @@ -175,41 +231,7 @@ export class VscodeList extends VscElement { }); }; - private _updateConfigContext(changedProperties: PropertyValues) { - const {arrows, indent, indentGuides, multiSelect} = this; - - if (changedProperties.has('arrows')) { - this._configContext = {...this._configContext, arrows}; - } - - if (changedProperties.has('indent')) { - this._configContext = {...this._configContext, indent}; - } - - if (changedProperties.has('indentGuides')) { - this._configContext = {...this._configContext, indentGuides}; - } - - if (changedProperties.has('multiSelect')) { - this._configContext = {...this._configContext, multiSelect}; - } - } - - protected override willUpdate(changedProperties: PropertyValues): void { - this._updateConfigContext(changedProperties); - } - - override connectedCallback(): void { - super.connectedCallback(); - - this.addEventListener('keydown', this._handleComponentKeyDown); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - - this.removeEventListener('keydown', this._handleComponentKeyDown); - } + //#endregion override render(): TemplateResult { return html`
From 369a29da34425c5b64821fc8494241e8ea92ec94 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 23:03:02 +0200 Subject: [PATCH 47/68] Fix range the selection with shift button --- src/vscode-list-item/vscode-list-item.ts | 8 ++++++-- src/vscode-list/list-context.ts | 1 + src/vscode-list/vscode-list.ts | 20 +++++++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 3832585bb..4eb1e8e1b 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -76,6 +76,7 @@ export class VscodeListItem extends VscElement { @consume({context: listContext, subscribe: true}) private _listContextState: ListContext = { + isShiftPressed: false, selectedItems: new Set(), allItems: null, itemListUpToDate: false, @@ -243,8 +244,11 @@ export class VscodeListItem extends VscElement { this._listContextState.focusedItem && this._listContextState.focusedItem !== this ) { - this._listContextState.prevFocusedItem = - this._listContextState.focusedItem; + if (!this._listContextState.isShiftPressed) { + this._listContextState.prevFocusedItem = + this._listContextState.focusedItem; + } + this._listContextState.focusedItem = null; } diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index ac14b6957..63abc1f40 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -3,6 +3,7 @@ import type {VscodeListItem} from '../vscode-list-item'; import type {VscodeList} from './vscode-list'; export interface ListContext { + isShiftPressed: boolean; selectedItems: Set; allItems: NodeListOf | null; itemListUpToDate: boolean; diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index cfd81a66a..6e972bc32 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -16,7 +16,7 @@ import { } from './list-context'; import {findNextItem, findPrevItem, initPathTrackerProps} from './helpers'; -type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | ' '; +type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | 'Shift' | ' '; const listenedKeys: ListenedKey[] = [ ' ', @@ -24,6 +24,7 @@ const listenedKeys: ListenedKey[] = [ 'ArrowUp', 'Enter', 'Escape', + 'Shift', ]; const DEFAULT_ARROWS = false; const DEFAULT_INDENT = 8; @@ -58,6 +59,7 @@ export class VscodeList extends VscElement { @provide({context: listContext}) private _listContextState: ListContext = { + isShiftPressed: false, activeItem: null, selectedItems: new Set(), allItems: null, @@ -81,6 +83,12 @@ export class VscodeList extends VscElement { //#region lifecycle methods + constructor() { + super(); + + this.addEventListener('keyup', this._handleComponentKeyUp); + } + override connectedCallback(): void { super.connectedCallback(); @@ -183,6 +191,10 @@ export class VscodeList extends VscElement { ev.preventDefault(); } + if (key === 'Shift') { + this._listContextState.isShiftPressed = true; + } + if (key === 'ArrowDown' || key === 'ArrowUp') { if (this._listContextState.focusedItem) { if (key === 'ArrowDown') { @@ -214,6 +226,12 @@ export class VscodeList extends VscElement { } }; + private _handleComponentKeyUp = (ev: KeyboardEvent) => { + if (ev.key === 'Shift') { + this._listContextState.isShiftPressed = false; + } + }; + private _handleSlotChange = () => { this._listContextState.itemListUpToDate = false; initPathTrackerProps(this, this._assignedListItems); From 86cc31076075d3cf6624a95228e038d95ab60479 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 23:10:51 +0200 Subject: [PATCH 48/68] Cleanup code --- src/vscode-list/vscode-list.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 6e972bc32..794512182 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -143,29 +143,21 @@ export class VscodeList extends VscElement { } } - private _activatePrevItem() { + private _focusPrevItem() { if (this._listContextState.focusedItem) { const item = findPrevItem(this._listContextState.focusedItem); if (item) { - // this._listContextState.focusedItem.focused = false; - // this._listContextState.focusedItem = item; - // item.focused = true; - // item.focus(); this._focusItem(item); } } } - private _activateNextItem() { + private _focusNextItem() { if (this._listContextState.focusedItem) { const item = findNextItem(this._listContextState.focusedItem); if (item) { - // this._listContextState.focusedItem.focused = false; - // this._listContextState.focusedItem = item; - // item.focused = true; - // item.focus(); this._focusItem(item); } } @@ -198,11 +190,11 @@ export class VscodeList extends VscElement { if (key === 'ArrowDown' || key === 'ArrowUp') { if (this._listContextState.focusedItem) { if (key === 'ArrowDown') { - this._activateNextItem(); + this._focusNextItem(); } if (key === 'ArrowUp') { - this._activatePrevItem(); + this._focusPrevItem(); } } else { this._focusItem(this._assignedListItems[0]); From cf0b6bda36c8525c4313b6c2f663a3d53240c956 Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 23:28:49 +0200 Subject: [PATCH 49/68] Select multiple items with keyboard --- src/vscode-list/vscode-list.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 794512182..4e5aaa168 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -149,6 +149,10 @@ export class VscodeList extends VscElement { if (item) { this._focusItem(item); + + if (this._listContextState.isShiftPressed) { + item.selected = !item.selected; + } } } } @@ -159,6 +163,10 @@ export class VscodeList extends VscElement { if (item) { this._focusItem(item); + + if (this._listContextState.isShiftPressed) { + item.selected = !item.selected; + } } } } From 8684890a6516da0a6046a1b39ba54903745b6f6a Mon Sep 17 00:00:00 2001 From: bendera Date: Thu, 10 Jul 2025 23:41:30 +0200 Subject: [PATCH 50/68] Handle single select --- dev/vscode-list/default-active-item.html | 2 +- dev/vscode-list/multi-select.html | 239 +++++++++++++++++++++++ dev/vscode-list/set-active-item.html | 2 +- src/vscode-list-item/vscode-list-item.ts | 3 +- src/vscode-list/vscode-list.ts | 20 +- 5 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 dev/vscode-list/multi-select.html diff --git a/dev/vscode-list/default-active-item.html b/dev/vscode-list/default-active-item.html index 3c2839850..830be84d8 100644 --- a/dev/vscode-list/default-active-item.html +++ b/dev/vscode-list/default-active-item.html @@ -29,7 +29,7 @@

Basic example

- + + + + + + VSCode Elements + + + + + + + +

Basic example

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + + + +
+
+
+ + diff --git a/dev/vscode-list/set-active-item.html b/dev/vscode-list/set-active-item.html index acf6306a6..6f9f2217e 100644 --- a/dev/vscode-list/set-active-item.html +++ b/dev/vscode-list/set-active-item.html @@ -29,7 +29,7 @@

Basic example

- + { if ( this._listContextState.focusedItem && @@ -261,7 +260,7 @@ export class VscodeListItem extends VscElement { const isCtrlDown = ev.ctrlKey; const isShiftDown = ev.shiftKey; - if (isShiftDown) { + if (isShiftDown && this._configContext.multiSelect) { this._selectRange(); } else { this._selectItem(isCtrlDown); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 4e5aaa168..ec751804a 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -143,6 +143,14 @@ export class VscodeList extends VscElement { } } + private _focusItem(item: VscodeListItem) { + item.active = true; + + item.updateComplete.then(() => { + item.focus(); + }); + } + private _focusPrevItem() { if (this._listContextState.focusedItem) { const item = findPrevItem(this._listContextState.focusedItem); @@ -150,7 +158,7 @@ export class VscodeList extends VscElement { if (item) { this._focusItem(item); - if (this._listContextState.isShiftPressed) { + if (this._listContextState.isShiftPressed && this.multiSelect) { item.selected = !item.selected; } } @@ -164,21 +172,13 @@ export class VscodeList extends VscElement { if (item) { this._focusItem(item); - if (this._listContextState.isShiftPressed) { + if (this._listContextState.isShiftPressed && this.multiSelect) { item.selected = !item.selected; } } } } - private _focusItem(item: VscodeListItem) { - item.active = true; - - item.updateComplete.then(() => { - item.focus(); - }); - } - //#endregion //#region event handlers From 22bedcdd6d8a767bcb4dd1ce8e2dc3863bc4e881 Mon Sep 17 00:00:00 2001 From: bendera Date: Fri, 11 Jul 2025 20:54:13 +0200 Subject: [PATCH 51/68] Add tests --- src/vscode-list-item/vscode-list-item.test.ts | 9 +++ src/vscode-list/vscode-list.test.ts | 61 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/vscode-list-item/vscode-list-item.test.ts diff --git a/src/vscode-list-item/vscode-list-item.test.ts b/src/vscode-list-item/vscode-list-item.test.ts new file mode 100644 index 000000000..599bfd311 --- /dev/null +++ b/src/vscode-list-item/vscode-list-item.test.ts @@ -0,0 +1,9 @@ +import {expect} from '@open-wc/testing'; +import {VscodeListItem} from './index.js'; + +describe('vscode-list', () => { + it('is defined', () => { + const el = document.createElement('vscode-list-item'); + expect(el).to.instanceOf(VscodeListItem); + }); +}); diff --git a/src/vscode-list/vscode-list.test.ts b/src/vscode-list/vscode-list.test.ts index 0ecf872d7..ed8d1653c 100644 --- a/src/vscode-list/vscode-list.test.ts +++ b/src/vscode-list/vscode-list.test.ts @@ -1,5 +1,7 @@ -import {aTimeout, expect, fixture, html} from '@open-wc/testing'; -import {VscodeListItem} from '../main.js'; +import {expect, fixture, html} from '@open-wc/testing'; +import {sendKeys} from '@web/test-runner-commands'; +import '../vscode-list-item/vscode-list-item.js'; +import {VscodeListItem} from '../vscode-list-item/vscode-list-item.js'; import {VscodeList} from './index.js'; describe('vscode-list', () => { @@ -15,8 +17,61 @@ describe('vscode-list', () => { Item 2 `); - const firstItem = el.querySelector('vscode-list-item')!; + const firstItem = + el.querySelectorAll('vscode-list-item')[0]!; + const secondItem = + el.querySelectorAll('vscode-list-item')[1]!; + expect(firstItem.tabIndex).to.eq(0); expect(firstItem.active).to.be.true; + expect(secondItem.tabIndex).to.eq(-1); + expect(secondItem.active).to.be.false; }); + + it('focuses active item on load', async () => { + const el = await fixture(html` + + Item 1 + Item 2 + + `); + const firstItem = + el.querySelectorAll('vscode-list-item')[0]!; + const secondItem = + el.querySelectorAll('vscode-list-item')[1]!; + + expect(firstItem.tabIndex).to.eq(-1); + expect(firstItem.active).to.be.false; + expect(secondItem.tabIndex).to.eq(0); + expect(secondItem.active).to.be.true; + }); + + it('focuses next item when arrow down key is pressed', async () => { + const el = await fixture(html` + + Item 1 + Item 2 + + `); + + el.querySelector('vscode-list-item')?.focus(); + await sendKeys({press: 'ArrowDown'}); + await el.updateComplete; + + const items = el.querySelectorAll('vscode-list-item')! + + expect(items[0].tabIndex).to.eq(-1); + expect(items[0].active).to.be.false; + expect(items[1].tabIndex).to.eq(0); + expect(items[1].active).to.be.true; + }); + + it('selects item with Enter key press'); + it('selects item with click on it'); + it('opens and selects branch item with Enter key press'); + it('opens and selects branch item with click on it'); + it('selecting multiple items upwards with the mouse and the Shift key'); + it('expands selection of multiple items upwards with the mouse and the Shift key'); + it('selecting multiple items downwards with the mouse and the Shift key'); + it('expands selection of multiple items downwards with the mouse and the Shift key'); }); From a3cb54e3e21089b3caf4def16d6b11c3a3395072 Mon Sep 17 00:00:00 2001 From: bendera Date: Fri, 11 Jul 2025 21:05:12 +0200 Subject: [PATCH 52/68] Add indent guides --- dev/vscode-list/guides-has-arrows.html | 239 ++++++++++++++++++ dev/vscode-list/guides-wo-arrows.html | 239 ++++++++++++++++++ .../vscode-list-item.styles.ts | 16 ++ src/vscode-list-item/vscode-list-item.ts | 24 +- 4 files changed, 512 insertions(+), 6 deletions(-) create mode 100644 dev/vscode-list/guides-has-arrows.html create mode 100644 dev/vscode-list/guides-wo-arrows.html diff --git a/dev/vscode-list/guides-has-arrows.html b/dev/vscode-list/guides-has-arrows.html new file mode 100644 index 000000000..b57044c48 --- /dev/null +++ b/dev/vscode-list/guides-has-arrows.html @@ -0,0 +1,239 @@ + + + + + + VSCode Elements + + + + + + + +

Basic example

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + + + +
+
+
+ + diff --git a/dev/vscode-list/guides-wo-arrows.html b/dev/vscode-list/guides-wo-arrows.html new file mode 100644 index 000000000..9c6a4cb21 --- /dev/null +++ b/dev/vscode-list/guides-wo-arrows.html @@ -0,0 +1,239 @@ + + + + + + VSCode Elements + + + + + + + +

Basic example

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + + + +
+
+
+ + diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index 61176bff4..b44337c66 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -86,6 +86,22 @@ const styles: CSSResultGroup = [ margin-right: 6px; } + .children { + position: relative; + } + + .children.guides:before { + background-color: var(--vscode-tree-inactiveIndentGuidesStroke); + content: ''; + display: block; + height: 100%; + left: var(--indentation-guide-left); + pointer-events: none; + position: absolute; + width: 1px; + z-index: 1; + } + .text-content { line-height: 22px; overflow: hidden; diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index e3b0cb036..bf039b3bb 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -5,10 +5,9 @@ import { property, queryAssignedElements, } from 'lit/decorators.js'; -import {styleMap} from 'lit/directives/style-map.js'; -import {VscElement} from '../includes/VscElement'; -import styles from './vscode-list-item.styles'; import {classMap} from 'lit/directives/class-map.js'; +import {VscElement} from '../includes/VscElement'; +import {stylePropertyMap} from '../includes/style-property-map'; import { ConfigContext, configContext, @@ -16,6 +15,7 @@ import { type ListContext, } from '../vscode-list/list-context'; import {initPathTrackerProps} from '../vscode-list/helpers'; +import styles from './vscode-list-item.styles'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -280,9 +280,11 @@ export class VscodeListItem extends VscElement { //#endregion override render(): TemplateResult { - const {arrows, indent} = this._configContext; + const {arrows, indent, indentGuides} = this._configContext; const {hasBranchItem} = this._listContextState; let indentation = BASE_INDENT + this.level * indent; + const guideOffset = arrows ? 13 : 3; + const indentGuideX = BASE_INDENT + this.level * indent + guideOffset; if (!this.branch && arrows && hasBranchItem) { indentation += ARROW_CONTAINER_WIDTH; @@ -293,11 +295,16 @@ export class VscodeListItem extends VscElement { active: this.active, }; + const childrenClasses = { + children: true, + 'guides': this.branch && indentGuides, + } + return html`
${this.branch && arrows ? html`
-
+
Date: Sat, 12 Jul 2025 00:35:41 +0200 Subject: [PATCH 53/68] Highlight the indent guide --- src/vscode-list-item/helpers.ts | 13 ++++ .../vscode-list-item.styles.ts | 4 ++ src/vscode-list-item/vscode-list-item.ts | 61 ++++++++++++++----- 3 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 src/vscode-list-item/helpers.ts diff --git a/src/vscode-list-item/helpers.ts b/src/vscode-list-item/helpers.ts new file mode 100644 index 000000000..6886cb96a --- /dev/null +++ b/src/vscode-list-item/helpers.ts @@ -0,0 +1,13 @@ +import {VscodeListItem} from './vscode-list-item'; + +export function getParentItem(childItem: VscodeListItem) { + if (!childItem.parentElement) { + return null; + } + + if (!(childItem.parentElement instanceof VscodeListItem)) { + return null; + } + + return childItem.parentElement; +} diff --git a/src/vscode-list-item/vscode-list-item.styles.ts b/src/vscode-list-item/vscode-list-item.styles.ts index b44337c66..c8427ed90 100644 --- a/src/vscode-list-item/vscode-list-item.styles.ts +++ b/src/vscode-list-item/vscode-list-item.styles.ts @@ -102,6 +102,10 @@ const styles: CSSResultGroup = [ z-index: 1; } + .children.guides.active-guides:before { + background-color: var(--vscode-tree-indentGuidesStroke); + } + .text-content { line-height: 22px; overflow: hidden; diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index bf039b3bb..63c001fec 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -16,6 +16,7 @@ import { } from '../vscode-list/list-context'; import {initPathTrackerProps} from '../vscode-list/helpers'; import styles from './vscode-list-item.styles'; +import {getParentItem} from './helpers'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -45,6 +46,12 @@ export class VscodeListItem extends VscElement { @property({type: Boolean, reflect: true}) branch = false; + @property({type: Boolean}) + hasActiveItem = false; + + @property({type: Boolean}) + hasSelectedItem = false; + @property({type: Boolean, reflect: true}) open = false; @@ -115,20 +122,7 @@ export class VscodeListItem extends VscElement { protected override willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has('active')) { - if (this.active) { - if (this._listContextState.activeItem) { - this._listContextState.activeItem.active = false; - } - - this._listContextState.activeItem = this; - this.tabIndex = 0; - } else { - if (this._listContextState.activeItem === this) { - this._listContextState.activeItem = null; - } - - this.tabIndex = -1; - } + this._toggleActiveState(); } } @@ -136,6 +130,40 @@ export class VscodeListItem extends VscElement { //#region private methods + private _setHasActiveItemFlagOnParent( + childItem: VscodeListItem, + value: boolean + ) { + const parent = getParentItem(childItem); + + if (parent) { + parent.hasActiveItem = value; + } + } + + private _toggleActiveState() { + if (this.active) { + if (this._listContextState.activeItem) { + this._listContextState.activeItem.active = false; + this._setHasActiveItemFlagOnParent( + this._listContextState.activeItem, + false + ); + } + + this._listContextState.activeItem = this; + this._setHasActiveItemFlagOnParent(this, true); + this.tabIndex = 0; + } else { + if (this._listContextState.activeItem === this) { + this._listContextState.activeItem = null; + this._setHasActiveItemFlagOnParent(this, false); + } + + this.tabIndex = -1; + } + } + private _selectItem(isCtrlDown: boolean) { const {selectedItems} = this._listContextState; const {multiSelect} = this._configContext; @@ -297,8 +325,9 @@ export class VscodeListItem extends VscElement { const childrenClasses = { children: true, - 'guides': this.branch && indentGuides, - } + guides: this.branch && indentGuides, + 'active-guides': this.hasActiveItem || this.hasSelectedItem, + }; return html`
Date: Sat, 12 Jul 2025 14:21:28 +0200 Subject: [PATCH 54/68] Add highlighted guides property --- dev/vscode-list/multi-select.html | 2 +- src/vscode-list-item/vscode-list-item.styles.ts | 2 +- src/vscode-list-item/vscode-list-item.ts | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dev/vscode-list/multi-select.html b/dev/vscode-list/multi-select.html index acf6306a6..37c75d754 100644 --- a/dev/vscode-list/multi-select.html +++ b/dev/vscode-list/multi-select.html @@ -29,7 +29,7 @@

Basic example

- + From 9ae02c3e534b99f68d320d312ce554755b4d1039 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 19:45:27 +0200 Subject: [PATCH 55/68] Highlight indent guide --- src/vscode-list-item/vscode-list-item.ts | 5 ++++ src/vscode-list/helpers.ts | 12 +++++++++ src/vscode-list/list-context.ts | 3 ++- src/vscode-list/vscode-list.ts | 34 +++++++++++++++++++++++- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index c3641d80c..304a4899a 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -95,6 +95,7 @@ export class VscodeListItem extends VscElement { hasBranchItem: false, rootElement: null, activeItem: null, + highlightedItems: [], }; @consume({context: configContext, subscribe: true}) @@ -306,6 +307,10 @@ export class VscodeListItem extends VscElement { if (!isShiftDown) { this._listContextState.prevFocusedItem = this; } + + this.updateComplete.then(() => { + this._listContextState.highlightGuides?.(); + }); }; //#endregion diff --git a/src/vscode-list/helpers.ts b/src/vscode-list/helpers.ts index ec11fc42d..ec821ed5e 100644 --- a/src/vscode-list/helpers.ts +++ b/src/vscode-list/helpers.ts @@ -190,3 +190,15 @@ export const selectItemAndAllVisibleDescendants = (item: VscodeListItem) => { }); } }; + +export function getParentItem(childItem: VscodeListItem) { + if (!childItem.parentElement) { + return null; + } + + if (!isListItem(childItem.parentElement)) { + return null; + } + + return childItem.parentElement; +} diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 63abc1f40..16ff68883 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -15,6 +15,8 @@ export interface ListContext { hasBranchItem: boolean; rootElement: VscodeList | null; activeItem: VscodeListItem | null; + highlightedItems: VscodeListItem[]; + highlightGuides?: () => void; } export const listContext = createContext('vscode-list'); @@ -26,7 +28,6 @@ export interface ConfigContext { readonly multiSelect: boolean; } - export const configContext = createContext( Symbol('configContext') ); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index ec751804a..0db18d06e 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -14,7 +14,12 @@ import { listContext, type ListContext, } from './list-context'; -import {findNextItem, findPrevItem, initPathTrackerProps} from './helpers'; +import { + findNextItem, + findPrevItem, + getParentItem, + initPathTrackerProps, +} from './helpers.js'; type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | 'Shift' | ' '; @@ -68,6 +73,10 @@ export class VscodeList extends VscElement { prevFocusedItem: null, hasBranchItem: false, rootElement: this, + highlightedItems: [], + highlightGuides: () => { + this._highlightGuides(); + }, }; @provide({context: configContext}) @@ -123,6 +132,27 @@ export class VscodeList extends VscElement { //#region private methods + private _highlightGuides() { + const {activeItem, highlightedItems} = this._listContextState; + + if (activeItem) { + highlightedItems.forEach((i) => (i.highlightedGuides = false)); + this._listContextState.highlightedItems = []; + + if (activeItem.branch && activeItem.open) { + activeItem.highlightedGuides = true; + this._listContextState.highlightedItems.push(activeItem); + } else { + const parent = getParentItem(activeItem); + + if (parent) { + parent.highlightedGuides = true; + this._listContextState.highlightedItems.push(parent); + } + } + } + } + private _updateConfigContext(changedProperties: PropertyValues) { const {arrows, indent, indentGuides, multiSelect} = this; @@ -246,6 +276,8 @@ export class VscodeList extends VscElement { firstChild.active = true; } } + + this._highlightGuides(); }); }; From c3488110672aefd071eb07a75096e1c4c4fc2ef4 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 20:28:04 +0200 Subject: [PATCH 56/68] Highlight guides --- src/vscode-list-item/vscode-list-item.ts | 1 + src/vscode-list/helpers.ts | 4 +++- src/vscode-list/vscode-list.ts | 24 +++++++++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 304a4899a..6df05b9cf 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -52,6 +52,7 @@ export class VscodeListItem extends VscElement { @property({type: Boolean}) hasSelectedItem = false; + /** @internal */ @property({type: Boolean}) highlightedGuides = false; diff --git a/src/vscode-list/helpers.ts b/src/vscode-list/helpers.ts index ec821ed5e..b39f4ced2 100644 --- a/src/vscode-list/helpers.ts +++ b/src/vscode-list/helpers.ts @@ -42,7 +42,9 @@ export const initPathTrackerProps = ( }; export const findLastChildItem = (item: VscodeListItem): VscodeListItem => { - const children = item.querySelectorAll(':scope > vscode-list-item'); + const children = item.querySelectorAll( + ':scope > vscode-list-item' + ); if (children.length < 1) { return item; diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 0db18d06e..0f0dc4844 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -133,10 +133,12 @@ export class VscodeList extends VscElement { //#region private methods private _highlightGuides() { - const {activeItem, highlightedItems} = this._listContextState; + const {activeItem, highlightedItems, selectedItems} = + this._listContextState; + + highlightedItems.forEach((i) => (i.highlightedGuides = false)); if (activeItem) { - highlightedItems.forEach((i) => (i.highlightedGuides = false)); this._listContextState.highlightedItems = []; if (activeItem.branch && activeItem.open) { @@ -145,12 +147,28 @@ export class VscodeList extends VscElement { } else { const parent = getParentItem(activeItem); - if (parent) { + if (parent && parent.branch) { parent.highlightedGuides = true; this._listContextState.highlightedItems.push(parent); } } } + + if (selectedItems) { + selectedItems.forEach((item) => { + if (item.branch && item.open) { + item.highlightedGuides = true; + this._listContextState.highlightedItems.push(item); + } else { + const parent = getParentItem(item); + + if (parent && parent.branch) { + parent.highlightedGuides = true; + this._listContextState.highlightedItems.push(item); + } + } + }); + } } private _updateConfigContext(changedProperties: PropertyValues) { From c34b2522bcb24d6606495a1727992d2217bbe3ee Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 21:25:45 +0200 Subject: [PATCH 57/68] Add expand/collapse all feature --- dev/vscode-list/expand-all-collapse-all.html | 249 +++++++++++++++++++ src/vscode-list/vscode-list.ts | 20 ++ 2 files changed, 269 insertions(+) create mode 100644 dev/vscode-list/expand-all-collapse-all.html diff --git a/dev/vscode-list/expand-all-collapse-all.html b/dev/vscode-list/expand-all-collapse-all.html new file mode 100644 index 000000000..97d5e619e --- /dev/null +++ b/dev/vscode-list/expand-all-collapse-all.html @@ -0,0 +1,249 @@ + + + + + + VSCode Elements + + + + + + + +

Expand/Collapse all

+
+ +

+ + +

+
+ + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + +
+
+
+ + diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 0f0dc4844..ebe1bbaf0 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -118,6 +118,26 @@ export class VscodeList extends VscElement { //#region public methods + expandAll() { + const children = this.querySelectorAll('vscode-list-item'); + + children.forEach((item) => { + if (item.branch) { + item.open = true; + } + }); + } + + collapseAll() { + const children = this.querySelectorAll('vscode-list-item'); + + children.forEach((item) => { + if (item.branch) { + item.open = false; + } + }); + } + /** * @internal * Updates `hasBranchItem` property in the context state in order to removing From bd3a0f7651fac700a4eb12f438528cd0ad12b2e1 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 21:42:32 +0200 Subject: [PATCH 58/68] A11Y aria-selected --- src/vscode-list-item/vscode-list-item.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 6df05b9cf..cb15c7c32 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -63,9 +63,10 @@ export class VscodeListItem extends VscElement { level = 0; @property({type: Boolean, reflect: true}) - set selected(val: boolean) { - this._selected = val; + set selected(selected: boolean) { + this._selected = selected; this._listContextState.selectedItems.add(this); + this.ariaSelected = selected ? "true" : "false"; } get selected(): boolean { return this._selected; From 88cde07a71f589bbeb0bd7ef6cc319950d12b8b8 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 22:07:54 +0200 Subject: [PATCH 59/68] Add aria-expanded and group role --- src/vscode-list-item/vscode-list-item.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index cb15c7c32..ff4aedfcf 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -130,12 +130,24 @@ export class VscodeListItem extends VscElement { if (changedProperties.has('active')) { this._toggleActiveState(); } + + if (changedProperties.has('open') || changedProperties.has('branch')) { + this._setAriaExpanded(); + } } //#endregion //#region private methods + private _setAriaExpanded() { + if (!this.branch) { + this.ariaExpanded = null; + } else { + this.ariaExpanded = this.open ? 'true' : 'false'; + } + } + private _setHasActiveItemFlagOnParent( childItem: VscodeListItem, value: boolean @@ -385,6 +397,7 @@ export class VscodeListItem extends VscElement { .style=${stylePropertyMap({ '--indentation-guide-left': `${indentGuideX}px`, })} + role="group" > Date: Sat, 12 Jul 2025 22:10:30 +0200 Subject: [PATCH 60/68] Add more aria attributes --- src/vscode-list-item/vscode-list-item.ts | 4 +++- src/vscode-list/vscode-list.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index ff4aedfcf..0ed754371 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -66,7 +66,7 @@ export class VscodeListItem extends VscElement { set selected(selected: boolean) { this._selected = selected; this._listContextState.selectedItems.add(this); - this.ariaSelected = selected ? "true" : "false"; + this.ariaSelected = selected ? 'true' : 'false'; } get selected(): boolean { return this._selected; @@ -116,6 +116,8 @@ export class VscodeListItem extends VscElement { override connectedCallback(): void { super.connectedCallback(); this._mainSlotChange(); + this.role = 'treeitem'; + this.ariaDisabled = 'false'; this.addEventListener('focus', this._handleComponentFocus); } diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index ebe1bbaf0..756543830 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -112,6 +112,10 @@ export class VscodeList extends VscElement { protected override willUpdate(changedProperties: PropertyValues): void { this._updateConfigContext(changedProperties); + + if (changedProperties.has('multiSelect')) { + this.ariaMultiSelectable = this.multiSelect ? 'true' : 'false'; + } } //#endregion From c983bb1fc567e5dc7b61059550679496c0fd290b Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 23:21:59 +0200 Subject: [PATCH 61/68] Handle arrow right press --- src/vscode-list/vscode-list.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 756543830..849d1f93f 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -21,7 +21,15 @@ import { initPathTrackerProps, } from './helpers.js'; -type ListenedKey = 'ArrowDown' | 'ArrowUp' | 'Enter' | 'Escape' | 'Shift' | ' '; +type ListenedKey = + | 'ArrowDown' + | 'ArrowUp' + | 'ArrowLeft' + | 'ArrowRight' + | 'Enter' + | 'Escape' + | 'Shift' + | ' '; const listenedKeys: ListenedKey[] = [ ' ', @@ -255,6 +263,22 @@ export class VscodeList extends VscElement { //#region event handlers + private _handleArrowRightPress() { + if (!this._listContextState.focusedItem) { + return; + } + + const {focusedItem} = this._listContextState; + + if (focusedItem.branch) { + if (focusedItem.open) { + this._focusNextItem(); + } else { + focusedItem.open = true; + } + } + } + private _handleComponentKeyDown = (ev: KeyboardEvent) => { const key = ev.key as ListenedKey; @@ -281,6 +305,10 @@ export class VscodeList extends VscElement { } } + if (key === 'ArrowRight') { + this._handleArrowRightPress(); + } + if (key === 'Enter' || key === ' ') { const {focusedItem} = this._listContextState; From 43748903eca5ffcde92100a51b9eb856939e3246 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 12 Jul 2025 23:55:54 +0200 Subject: [PATCH 62/68] Refactor --- src/vscode-list/vscode-list.ts | 89 ++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 849d1f93f..f2ca5ad9e 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -279,50 +279,75 @@ export class VscodeList extends VscElement { } } - private _handleComponentKeyDown = (ev: KeyboardEvent) => { - const key = ev.key as ListenedKey; + private _handleArrowLeftPress() { - if (listenedKeys.includes(key)) { - ev.stopPropagation(); - ev.preventDefault(); + } + + private _handleArrowDownPress() { + if (this._listContextState.focusedItem) { + this._focusNextItem(); + } else { + this._focusItem(this._assignedListItems[0]); } + } - if (key === 'Shift') { - this._listContextState.isShiftPressed = true; + private _handleArrowUpPress() { + if (this._listContextState.focusedItem) { + this._focusPrevItem(); + } else { + this._focusItem(this._assignedListItems[0]); } + } - if (key === 'ArrowDown' || key === 'ArrowUp') { - if (this._listContextState.focusedItem) { - if (key === 'ArrowDown') { - this._focusNextItem(); - } + private _handleEnterPress() { + const {focusedItem} = this._listContextState; - if (key === 'ArrowUp') { - this._focusPrevItem(); - } - } else { - this._focusItem(this._assignedListItems[0]); - } - } + if (focusedItem) { + this._listContextState.selectedItems.forEach( + (li) => (li.selected = false) + ); - if (key === 'ArrowRight') { - this._handleArrowRightPress(); + focusedItem.selected = true; + + if (focusedItem.branch) { + focusedItem.open = !focusedItem.open; + } } + } - if (key === 'Enter' || key === ' ') { - const {focusedItem} = this._listContextState; + private _handleShiftPress() { + this._listContextState.isShiftPressed = true; + } - if (focusedItem) { - this._listContextState.selectedItems.forEach( - (li) => (li.selected = false) - ); + private _handleComponentKeyDown = (ev: KeyboardEvent) => { + const key = ev.key as ListenedKey; - focusedItem.selected = true; + if (listenedKeys.includes(key)) { + ev.stopPropagation(); + ev.preventDefault(); + } - if (focusedItem.branch) { - focusedItem.open = !focusedItem.open; - } - } + switch (key) { + case ' ': + case 'Enter': + this._handleEnterPress(); + break; + case 'ArrowDown': + this._handleArrowDownPress(); + break; + case 'ArrowLeft': + this._handleArrowLeftPress(); + break; + case 'ArrowRight': + this._handleArrowRightPress(); + break ; + case 'ArrowUp': + this._handleArrowUpPress(); + break; + case 'Shift': + this._handleShiftPress(); + break; + default: } }; From 9ae9b8d3c0e904521e034d19954c04b1a4a25f16 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 00:05:02 +0200 Subject: [PATCH 63/68] Handle arrow left press --- src/vscode-list/vscode-list.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index f2ca5ad9e..a2d72cad2 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -279,8 +279,32 @@ export class VscodeList extends VscElement { } } - private _handleArrowLeftPress() { + private _handleArrowLeftPress(ev: KeyboardEvent) { + if (ev.ctrlKey) { + this.collapseAll(); + return; + } + if (!this._listContextState.focusedItem) { + return; + } + + const {focusedItem} = this._listContextState; + const parent = getParentItem(focusedItem); + + if (!focusedItem.branch) { + if (parent && parent.branch) { + this._focusItem(parent); + } + } else { + if (focusedItem.open) { + focusedItem.open = false; + } else { + if (parent && parent.branch) { + this._focusItem(parent); + } + } + } } private _handleArrowDownPress() { @@ -336,11 +360,11 @@ export class VscodeList extends VscElement { this._handleArrowDownPress(); break; case 'ArrowLeft': - this._handleArrowLeftPress(); + this._handleArrowLeftPress(ev); break; case 'ArrowRight': this._handleArrowRightPress(); - break ; + break; case 'ArrowUp': this._handleArrowUpPress(); break; From ce2d739b1f2f1c1c1c0d6e0a31e3567eec92ae69 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 00:52:16 +0200 Subject: [PATCH 64/68] Add expand mode --- dev/vscode-list/double-click-expand-mode.html | 235 ++++++++++++++++++ src/vscode-list-item/vscode-list-item.ts | 20 +- src/vscode-list/list-context.ts | 3 +- src/vscode-list/vscode-list.ts | 18 +- 4 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 dev/vscode-list/double-click-expand-mode.html diff --git a/dev/vscode-list/double-click-expand-mode.html b/dev/vscode-list/double-click-expand-mode.html new file mode 100644 index 000000000..116536863 --- /dev/null +++ b/dev/vscode-list/double-click-expand-mode.html @@ -0,0 +1,235 @@ + + + + + + VSCode Elements + + + + + + + +

Expand mode: doubleClick

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + +
+
+
+ + diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 0ed754371..b2f290e63 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -17,6 +17,7 @@ import { import {initPathTrackerProps} from '../vscode-list/helpers'; import styles from './vscode-list-item.styles'; import {getParentItem} from './helpers'; +import {EXPAND_MODE} from '../vscode-list/vscode-list'; const BASE_INDENT = 3; const ARROW_CONTAINER_WIDTH = 30; @@ -302,7 +303,7 @@ export class VscodeListItem extends VscElement { this._listContextState.focusedItem = this; }; - private _handleContentClick = (ev: MouseEvent) => { + private _handleContentClick(ev: MouseEvent) { ev.stopPropagation(); const isCtrlDown = ev.ctrlKey; @@ -313,8 +314,10 @@ export class VscodeListItem extends VscElement { } else { this._selectItem(isCtrlDown); - if (this.branch && !(this._configContext.multiSelect && isCtrlDown)) { - this.open = !this.open; + if (this._configContext.expandMode === EXPAND_MODE.SINGLE_CLICK) { + if (this.branch && !(this._configContext.multiSelect && isCtrlDown)) { + this.open = !this.open; + } } } @@ -327,7 +330,15 @@ export class VscodeListItem extends VscElement { this.updateComplete.then(() => { this._listContextState.highlightGuides?.(); }); - }; + } + + private _handleDoubleClick(ev: MouseEvent) { + if (this._configContext.expandMode === EXPAND_MODE.DOUBLE_CLICK) { + if (this.branch && !(this._configContext.multiSelect && ev.ctrlKey)) { + this.open = !this.open; + } + } + } //#endregion @@ -357,6 +368,7 @@ export class VscodeListItem extends VscElement {
${this.branch && arrows diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 16ff68883..8b4d37586 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -1,6 +1,6 @@ import {createContext} from '@lit/context'; import type {VscodeListItem} from '../vscode-list-item'; -import type {VscodeList} from './vscode-list'; +import type {ExpandMode, VscodeList} from './vscode-list'; export interface ListContext { isShiftPressed: boolean; @@ -23,6 +23,7 @@ export const listContext = createContext('vscode-list'); export interface ConfigContext { readonly arrows: boolean; + readonly expandMode: ExpandMode; readonly indent: number; readonly indentGuides: boolean; readonly multiSelect: boolean; diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index a2d72cad2..e2f490566 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -21,6 +21,13 @@ import { initPathTrackerProps, } from './helpers.js'; +export const EXPAND_MODE = { + SINGLE_CLICK: 'singleClick', + DOUBLE_CLICK: 'doubleClick', +} as const; + +export type ExpandMode = (typeof EXPAND_MODE)[keyof typeof EXPAND_MODE]; + type ListenedKey = | 'ArrowDown' | 'ArrowUp' @@ -43,6 +50,7 @@ const DEFAULT_ARROWS = false; const DEFAULT_INDENT = 8; const DEFAULT_INDENT_GUIDES = false; const DEFAULT_MULTI_SELECT = false; +const DEFAULT_EXPAND_MODE = EXPAND_MODE.SINGLE_CLICK; @customElement('vscode-list') export class VscodeList extends VscElement { @@ -53,6 +61,9 @@ export class VscodeList extends VscElement { @property({type: Boolean, reflect: true}) arrows = DEFAULT_ARROWS; + @property({type: String, attribute: 'expand-mode'}) + expandMode: ExpandMode = DEFAULT_EXPAND_MODE; + @property({type: Number, reflect: true}) indent = DEFAULT_INDENT; @@ -90,6 +101,7 @@ export class VscodeList extends VscElement { @provide({context: configContext}) private _configContext: ConfigContext = { arrows: DEFAULT_ARROWS, + expandMode: DEFAULT_EXPAND_MODE, indent: DEFAULT_INDENT, indentGuides: DEFAULT_INDENT_GUIDES, multiSelect: DEFAULT_MULTI_SELECT, @@ -204,12 +216,16 @@ export class VscodeList extends VscElement { } private _updateConfigContext(changedProperties: PropertyValues) { - const {arrows, indent, indentGuides, multiSelect} = this; + const {arrows, expandMode, indent, indentGuides, multiSelect} = this; if (changedProperties.has('arrows')) { this._configContext = {...this._configContext, arrows}; } + if (changedProperties.has('expandMode')) { + this._configContext = {...this._configContext, expandMode}; + } + if (changedProperties.has('indent')) { this._configContext = {...this._configContext, indent}; } From 0cc6e5ec1c6365759cc8b889b57d9a8694e4ea98 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 01:27:47 +0200 Subject: [PATCH 65/68] Fix missing listened keys --- src/vscode-list/vscode-list.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index e2f490566..38ced8efc 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -42,6 +42,8 @@ const listenedKeys: ListenedKey[] = [ ' ', 'ArrowDown', 'ArrowUp', + 'ArrowLeft', + 'ArrowRight', 'Enter', 'Escape', 'Shift', @@ -73,10 +75,6 @@ export class VscodeList extends VscElement { @property({type: Boolean, reflect: true, attribute: 'multi-select'}) multiSelect = DEFAULT_MULTI_SELECT; - /** @internal */ - @property({type: String, reflect: true}) - override role = 'tree'; - //#endregion //#region private variables @@ -116,18 +114,13 @@ export class VscodeList extends VscElement { super(); this.addEventListener('keyup', this._handleComponentKeyUp); + this.addEventListener('keydown', this._handleComponentKeyDown); } override connectedCallback(): void { super.connectedCallback(); - this.addEventListener('keydown', this._handleComponentKeyDown); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - - this.removeEventListener('keydown', this._handleComponentKeyDown); + this.role = 'tree'; } protected override willUpdate(changedProperties: PropertyValues): void { From a9b14e83b15e6201369ca4809080d83622dafb26 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 02:02:21 +0200 Subject: [PATCH 66/68] Dispatch select event --- dev/vscode-list/events.html | 237 +++++++++++++++++++++++ src/vscode-list-item/vscode-list-item.ts | 2 + src/vscode-list/list-context.ts | 1 + src/vscode-list/vscode-list.ts | 20 ++ 4 files changed, 260 insertions(+) create mode 100644 dev/vscode-list/events.html diff --git a/dev/vscode-list/events.html b/dev/vscode-list/events.html new file mode 100644 index 000000000..2f000fa69 --- /dev/null +++ b/dev/vscode-list/events.html @@ -0,0 +1,237 @@ + + + + + + VSCode Elements + + + + + + + +

Events

+
+ +
+ + + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + ipsum + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + + + dolor + + + + + lorem + + + + + + ipsum + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + lorem + + + + + + + + lorem + + + + + + + dolor + + + + +
+
+
+ + diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index b2f290e63..068e2ff73 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -311,8 +311,10 @@ export class VscodeListItem extends VscElement { if (isShiftDown && this._configContext.multiSelect) { this._selectRange(); + this._listContextState.emitSelectEvent?.(); } else { this._selectItem(isCtrlDown); + this._listContextState.emitSelectEvent?.(); if (this._configContext.expandMode === EXPAND_MODE.SINGLE_CLICK) { if (this.branch && !(this._configContext.multiSelect && isCtrlDown)) { diff --git a/src/vscode-list/list-context.ts b/src/vscode-list/list-context.ts index 8b4d37586..813cb7754 100644 --- a/src/vscode-list/list-context.ts +++ b/src/vscode-list/list-context.ts @@ -17,6 +17,7 @@ export interface ListContext { activeItem: VscodeListItem | null; highlightedItems: VscodeListItem[]; highlightGuides?: () => void; + emitSelectEvent?: () => void; } export const listContext = createContext('vscode-list'); diff --git a/src/vscode-list/vscode-list.ts b/src/vscode-list/vscode-list.ts index 38ced8efc..94473bebd 100644 --- a/src/vscode-list/vscode-list.ts +++ b/src/vscode-list/vscode-list.ts @@ -21,6 +21,8 @@ import { initPathTrackerProps, } from './helpers.js'; +export type VscListSelectEvent = CustomEvent<{selectedItems: VscodeListItem[]}>; + export const EXPAND_MODE = { SINGLE_CLICK: 'singleClick', DOUBLE_CLICK: 'doubleClick', @@ -94,6 +96,9 @@ export class VscodeList extends VscElement { highlightGuides: () => { this._highlightGuides(); }, + emitSelectEvent: () => { + this._emitSelectEvent(); + }, }; @provide({context: configContext}) @@ -169,6 +174,14 @@ export class VscodeList extends VscElement { //#region private methods + private _emitSelectEvent() { + const ev = new CustomEvent('vsc-list-select', { + detail: Array.from(this._listContextState.selectedItems), + }); + + this.dispatchEvent(ev); + } + private _highlightGuides() { const {activeItem, highlightedItems, selectedItems} = this._listContextState; @@ -249,6 +262,7 @@ export class VscodeList extends VscElement { if (this._listContextState.isShiftPressed && this.multiSelect) { item.selected = !item.selected; + this._emitSelectEvent(); } } } @@ -263,6 +277,7 @@ export class VscodeList extends VscElement { if (this._listContextState.isShiftPressed && this.multiSelect) { item.selected = !item.selected; + this._emitSelectEvent(); } } } @@ -341,6 +356,7 @@ export class VscodeList extends VscElement { ); focusedItem.selected = true; + this._emitSelectEvent(); if (focusedItem.branch) { focusedItem.open = !focusedItem.open; @@ -422,4 +438,8 @@ declare global { interface HTMLElementTagNameMap { 'vscode-list': VscodeList; } + + interface GlobalEventHandlersEventMap { + 'vsc-list-select': VscListSelectEvent; + } } From 28fc820a15fb7291f48aa919083750d271c95b0b Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 02:07:00 +0200 Subject: [PATCH 67/68] Refactor --- src/vscode-list-item/vscode-list-item.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vscode-list-item/vscode-list-item.ts b/src/vscode-list-item/vscode-list-item.ts index 068e2ff73..ca33c4006 100644 --- a/src/vscode-list-item/vscode-list-item.ts +++ b/src/vscode-list-item/vscode-list-item.ts @@ -114,19 +114,17 @@ export class VscodeListItem extends VscElement { //#region lifecycle methods + constructor() { + super(); + + this.addEventListener('focus', this._handleComponentFocus); + } + override connectedCallback(): void { super.connectedCallback(); this._mainSlotChange(); this.role = 'treeitem'; this.ariaDisabled = 'false'; - - this.addEventListener('focus', this._handleComponentFocus); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - - this.removeEventListener('focus', this._handleComponentFocus); } protected override willUpdate(changedProperties: PropertyValues): void { From a91708930e88784048f040ec73c8d4c145c6d107 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 13 Jul 2025 02:08:00 +0200 Subject: [PATCH 68/68] Prettier fix --- dev/vscode-list/multi-select.html | 8 +++++++- src/vscode-list-item/vscode-list-item.styles.ts | 5 ++++- src/vscode-list/vscode-list.test.ts | 10 +++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dev/vscode-list/multi-select.html b/dev/vscode-list/multi-select.html index 37c75d754..b0ecc1873 100644 --- a/dev/vscode-list/multi-select.html +++ b/dev/vscode-list/multi-select.html @@ -29,7 +29,13 @@

Basic example

- + { await sendKeys({press: 'ArrowDown'}); await el.updateComplete; - const items = el.querySelectorAll('vscode-list-item')! + const items = el.querySelectorAll('vscode-list-item')!; expect(items[0].tabIndex).to.eq(-1); expect(items[0].active).to.be.false; @@ -71,7 +71,11 @@ describe('vscode-list', () => { it('opens and selects branch item with Enter key press'); it('opens and selects branch item with click on it'); it('selecting multiple items upwards with the mouse and the Shift key'); - it('expands selection of multiple items upwards with the mouse and the Shift key'); + it( + 'expands selection of multiple items upwards with the mouse and the Shift key' + ); it('selecting multiple items downwards with the mouse and the Shift key'); - it('expands selection of multiple items downwards with the mouse and the Shift key'); + it( + 'expands selection of multiple items downwards with the mouse and the Shift key' + ); });