Compare commits
	
		
			29 Commits
		
	
	
		
			eap-but-ac
			...
			web/legibi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1bc8fa0a9d | |||
| 3ec0d30965 | |||
| 50d2f69332 | |||
| 7d972ec711 | |||
| 854427e463 | |||
| be349e2e14 | |||
| bd0e81b8ad | |||
| f6afb59515 | |||
| dddde09be5 | |||
| 6d7fc94698 | |||
| 1dcf9108ad | |||
| 7bb6a3dfe6 | |||
| 9cc440eee1 | |||
| fe9e4526ac | |||
| 20b66f850c | |||
| 67b327414b | |||
| 5b8d86b5a9 | |||
| 67aed3e318 | |||
| 9809b94030 | |||
| e7527c551b | |||
| 36b10b434a | |||
| 831797b871 | |||
| 5cc2c0f45f | |||
| 32442766f4 | |||
| 75790909a8 | |||
| e0d5df89ca | |||
| f25a9c624e | |||
| 914993a788 | |||
| 89dad07a66 | 
| @ -1,6 +1,5 @@ | |||||||
| import { AkControlElement } from "@goauthentik/elements/AkControlElement.js"; | import { AkControlElement } from "@goauthentik/elements/AkControlElement.js"; | ||||||
| import { debounce } from "@goauthentik/elements/utils/debounce"; | import { debounce } from "@goauthentik/elements/utils/debounce"; | ||||||
| import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; | import { msg } from "@lit/localize"; | ||||||
| import { PropertyValues, html } from "lit"; | import { PropertyValues, html } from "lit"; | ||||||
| @ -12,6 +11,11 @@ import type { Pagination } from "@goauthentik/api"; | |||||||
|  |  | ||||||
| import "./ak-dual-select"; | import "./ak-dual-select"; | ||||||
| import { AkDualSelect } from "./ak-dual-select"; | import { AkDualSelect } from "./ak-dual-select"; | ||||||
|  | import { | ||||||
|  |     DualSelectChangeEvent, | ||||||
|  |     DualSelectPaginatorNavEvent, | ||||||
|  |     DualSelectSearchEvent, | ||||||
|  | } from "./events"; | ||||||
| import type { DataProvider, DualSelectPair } from "./types"; | import type { DataProvider, DualSelectPair } from "./types"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -26,7 +30,7 @@ import type { DataProvider, DualSelectPair } from "./types"; | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| @customElement("ak-dual-select-provider") | @customElement("ak-dual-select-provider") | ||||||
| export class AkDualSelectProvider extends CustomListenerElement(AkControlElement) { | export class AkDualSelectProvider extends AkControlElement { | ||||||
|     /** A function that takes a page and returns the DualSelectPair[] collection with which to update |     /** A function that takes a page and returns the DualSelectPair[] collection with which to update | ||||||
|      * the "Available" pane. |      * the "Available" pane. | ||||||
|      * |      * | ||||||
| @ -86,9 +90,9 @@ export class AkDualSelectProvider extends CustomListenerElement(AkControlElement | |||||||
|         this.onNav = this.onNav.bind(this); |         this.onNav = this.onNav.bind(this); | ||||||
|         this.onChange = this.onChange.bind(this); |         this.onChange = this.onChange.bind(this); | ||||||
|         this.onSearch = this.onSearch.bind(this); |         this.onSearch = this.onSearch.bind(this); | ||||||
|         this.addCustomListener("ak-pagination-nav-to", this.onNav); |         this.addEventListener(DualSelectPaginatorNavEvent.eventName, this.onNav); | ||||||
|         this.addCustomListener("ak-dual-select-change", this.onChange); |         this.addEventListener(DualSelectSearchEvent.eventName, this.onSearch); | ||||||
|         this.addCustomListener("ak-dual-select-search", this.onSearch); |         this.addEventListener(DualSelectChangeEvent.eventName, this.onChange); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     willUpdate(changedProperties: PropertyValues<this>) { |     willUpdate(changedProperties: PropertyValues<this>) { | ||||||
| @ -122,26 +126,16 @@ export class AkDualSelectProvider extends CustomListenerElement(AkControlElement | |||||||
|         this.isLoading = false; |         this.isLoading = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     onNav(event: Event) { |     onNav(event: DualSelectPaginatorNavEvent) { | ||||||
|         if (!(event instanceof CustomEvent)) { |         this.fetch(event.page); | ||||||
|             throw new Error(`Expecting a CustomEvent for navigation, received ${event} instead`); |  | ||||||
|         } |  | ||||||
|         this.fetch(event.detail); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     onChange(event: Event) { |     onChange(event: DualSelectChangeEvent) { | ||||||
|         if (!(event instanceof CustomEvent)) { |         this.selected = this.internalSelected = event.selected; | ||||||
|             throw new Error(`Expecting a CustomEvent for change, received ${event} instead`); |  | ||||||
|         } |  | ||||||
|         this.internalSelected = event.detail.value; |  | ||||||
|         this.selected = this.internalSelected; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     onSearch(event: Event) { |     onSearch(event: DualSelectSearchEvent) { | ||||||
|         if (!(event instanceof CustomEvent)) { |         this.doSearch(event.search); | ||||||
|             throw new Error(`Expecting a CustomEvent for change, received ${event} instead`); |  | ||||||
|         } |  | ||||||
|         this.doSearch(event.detail); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     doSearch(search: string) { |     doSearch(search: string) { | ||||||
|  | |||||||
| @ -1,8 +1,5 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import { | import { match } from "ts-pattern"; | ||||||
|     CustomEmitterElement, |  | ||||||
|     CustomListenerElement, |  | ||||||
| } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { msg, str } from "@lit/localize"; | import { msg, str } from "@lit/localize"; | ||||||
| import { PropertyValues, html, nothing } from "lit"; | import { PropertyValues, html, nothing } from "lit"; | ||||||
| @ -23,15 +20,13 @@ import { AkDualSelectSelectedPane } from "./components/ak-dual-select-selected-p | |||||||
| import "./components/ak-pagination"; | import "./components/ak-pagination"; | ||||||
| import "./components/ak-search-bar"; | import "./components/ak-search-bar"; | ||||||
| import { | import { | ||||||
|     EVENT_ADD_ALL, |     DualSelectChangeEvent, | ||||||
|     EVENT_ADD_ONE, |     DualSelectMoveRequestEvent, | ||||||
|     EVENT_ADD_SELECTED, |     DualSelectPanelSearchEvent, | ||||||
|     EVENT_DELETE_ALL, |     DualSelectSearchEvent, | ||||||
|     EVENT_REMOVE_ALL, |     DualSelectUpdateEvent, | ||||||
|     EVENT_REMOVE_ONE, | } from "./events"; | ||||||
|     EVENT_REMOVE_SELECTED, | import type { BasePagination, DualSelectPair } from "./types"; | ||||||
| } from "./constants"; |  | ||||||
| import type { BasePagination, DualSelectPair, SearchbarEvent } from "./types"; |  | ||||||
|  |  | ||||||
| function alphaSort([_k1, v1, s1]: DualSelectPair, [_k2, v2, s2]: DualSelectPair) { | function alphaSort([_k1, v1, s1]: DualSelectPair, [_k2, v2, s2]: DualSelectPair) { | ||||||
|     const [l, r] = [s1 !== undefined ? s1 : v1, s2 !== undefined ? s2 : v2]; |     const [l, r] = [s1 !== undefined ? s1 : v1, s2 !== undefined ? s2 : v2]; | ||||||
| @ -60,7 +55,7 @@ const keyfinder = | |||||||
|         k === key; |         k === key; | ||||||
|  |  | ||||||
| @customElement("ak-dual-select") | @customElement("ak-dual-select") | ||||||
| export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKElement)) { | export class AkDualSelect extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return styles; | ||||||
|     } |     } | ||||||
| @ -96,21 +91,9 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE | |||||||
|         super(); |         super(); | ||||||
|         this.handleMove = this.handleMove.bind(this); |         this.handleMove = this.handleMove.bind(this); | ||||||
|         this.handleSearch = this.handleSearch.bind(this); |         this.handleSearch = this.handleSearch.bind(this); | ||||||
|         [ |         this.addEventListener(DualSelectMoveRequestEvent.eventName, this.handleMove); | ||||||
|             EVENT_ADD_ALL, |         this.addEventListener(DualSelectUpdateEvent.eventName, () => this.requestUpdate()); | ||||||
|             EVENT_ADD_SELECTED, |         this.addEventListener(DualSelectPanelSearchEvent.eventName, this.handleSearch); | ||||||
|             EVENT_DELETE_ALL, |  | ||||||
|             EVENT_REMOVE_ALL, |  | ||||||
|             EVENT_REMOVE_SELECTED, |  | ||||||
|             EVENT_ADD_ONE, |  | ||||||
|             EVENT_REMOVE_ONE, |  | ||||||
|         ].forEach((eventName: string) => { |  | ||||||
|             this.addCustomListener(eventName, (event: Event) => this.handleMove(eventName, event)); |  | ||||||
|         }); |  | ||||||
|         this.addCustomListener("ak-dual-select-move", () => { |  | ||||||
|             this.requestUpdate(); |  | ||||||
|         }); |  | ||||||
|         this.addCustomListener("ak-search", this.handleSearch); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     willUpdate(changedProperties: PropertyValues<this>) { |     willUpdate(changedProperties: PropertyValues<this>) { | ||||||
| @ -123,47 +106,17 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     handleMove(eventName: string, event: Event) { |     handleMove(event: DualSelectMoveRequestEvent) { | ||||||
|         if (!(event instanceof CustomEvent)) { |         match(event.move) | ||||||
|             throw new Error(`Expected move event here, got ${eventName}`); |             .with("add-all", () => this.addAllVisible()) | ||||||
|         } |             .with("add-one", () => this.addOne(event.key)) | ||||||
|  |             .with("add-selected", () => this.addSelected()) | ||||||
|         switch (eventName) { |             .with("delete-all", () => this.removeAll()) | ||||||
|             case EVENT_ADD_SELECTED: { |             .with("remove-all", () => this.removeAllVisible()) | ||||||
|                 this.addSelected(); |             .with("remove-one", () => this.removeOne(event.key)) | ||||||
|                 break; |             .with("remove-selected", () => this.removeSelected()) | ||||||
|             } |             .exhaustive(); | ||||||
|             case EVENT_REMOVE_SELECTED: { |         this.dispatchEvent(new DualSelectChangeEvent(this.value)); | ||||||
|                 this.removeSelected(); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case EVENT_ADD_ALL: { |  | ||||||
|                 this.addAllVisible(); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case EVENT_REMOVE_ALL: { |  | ||||||
|                 this.removeAllVisible(); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case EVENT_DELETE_ALL: { |  | ||||||
|                 this.removeAll(); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case EVENT_ADD_ONE: { |  | ||||||
|                 this.addOne(event.detail); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case EVENT_REMOVE_ONE: { |  | ||||||
|                 this.removeOne(event.detail); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             default: |  | ||||||
|                 throw new Error( |  | ||||||
|                     `AkDualSelect.handleMove received unknown event type: ${eventName}`, |  | ||||||
|                 ); |  | ||||||
|         } |  | ||||||
|         this.dispatchCustomEvent("ak-dual-select-change", { value: this.value }); |  | ||||||
|         event.stopPropagation(); |         event.stopPropagation(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -182,7 +135,10 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE | |||||||
|         this.availablePane.value!.clearMove(); |         this.availablePane.value!.clearMove(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     addOne(key: string) { |     addOne(key?: string) { | ||||||
|  |         if (!key) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         const requested = this.options.find(keyfinder(key)); |         const requested = this.options.find(keyfinder(key)); | ||||||
|         if (requested && !this.selected.find(keyfinder(requested[0]))) { |         if (requested && !this.selected.find(keyfinder(requested[0]))) { | ||||||
|             this.selected = [...this.selected, requested]; |             this.selected = [...this.selected, requested]; | ||||||
| @ -207,7 +163,10 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE | |||||||
|         this.selectedPane.value!.clearMove(); |         this.selectedPane.value!.clearMove(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     removeOne(key: string) { |     removeOne(key?: string) { | ||||||
|  |         if (!key) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         this.selected = this.selected.filter(([k]) => k !== key); |         this.selected = this.selected.filter(([k]) => k !== key); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -223,18 +182,18 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE | |||||||
|         this.selectedPane.value!.clearMove(); |         this.selectedPane.value!.clearMove(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     handleSearch(event: SearchbarEvent) { |     handleSearch(event: DualSelectPanelSearchEvent) { | ||||||
|         switch (event.detail.source) { |         switch (event.source) { | ||||||
|             case "ak-dual-list-available-search": |             case "ak-dual-list-available-search": | ||||||
|                 return this.handleAvailableSearch(event.detail.value); |                 return this.handleAvailableSearch(event.filterOn); | ||||||
|             case "ak-dual-list-selected-search": |             case "ak-dual-list-selected-search": | ||||||
|                 return this.handleSelectedSearch(event.detail.value); |                 return this.handleSelectedSearch(event.filterOn); | ||||||
|         } |         } | ||||||
|         event.stopPropagation(); |         event.stopPropagation(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     handleAvailableSearch(value: string) { |     handleAvailableSearch(value: string) { | ||||||
|         this.dispatchCustomEvent("ak-dual-select-search", value); |         this.dispatchEvent(new DualSelectSearchEvent(value)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     handleSelectedSearch(value: string) { |     handleSelectedSearch(value: string) { | ||||||
|  | |||||||
| @ -1,26 +1,19 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { bound } from "@goauthentik/elements/decorators/bound"; | ||||||
| import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { html, nothing } from "lit"; | import { html, nothing } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| import { classMap } from "lit/directives/class-map.js"; | import { classMap } from "lit/directives/class-map.js"; | ||||||
| import { map } from "lit/directives/map.js"; | import { map } from "lit/directives/map.js"; | ||||||
|  |  | ||||||
| import { availablePaneStyles, listStyles } from "./styles.css"; | import { availablePaneStyles } from "./styles.css"; | ||||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; |  | ||||||
| import PFDualListSelector from "@patternfly/patternfly/components/DualListSelector/dual-list-selector.css"; |  | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; |  | ||||||
|  |  | ||||||
| import { EVENT_ADD_ONE } from "../constants"; | import { | ||||||
|  |     DualSelectMoveAvailableEvent, | ||||||
|  |     DualSelectMoveRequestEvent, | ||||||
|  |     DualSelectUpdateEvent, | ||||||
|  | } from "../events"; | ||||||
| import type { DualSelectPair } from "../types"; | import type { DualSelectPair } from "../types"; | ||||||
|  | import { AkDualSelectAbstractPane } from "./ak-dual-select-pane"; | ||||||
| const styles = [PFBase, PFButton, PFDualListSelector, listStyles, availablePaneStyles]; |  | ||||||
|  |  | ||||||
| const hostAttributes = [ |  | ||||||
|     ["aria-labelledby", "dual-list-selector-available-pane-status"], |  | ||||||
|     ["aria-multiselectable", "true"], |  | ||||||
|     ["role", "listbox"], |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @element ak-dual-select-available-panel |  * @element ak-dual-select-available-panel | ||||||
| @ -40,9 +33,9 @@ const hostAttributes = [ | |||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| @customElement("ak-dual-select-available-pane") | @customElement("ak-dual-select-available-pane") | ||||||
| export class AkDualSelectAvailablePane extends CustomEmitterElement(AKElement) { | export class AkDualSelectAvailablePane extends AkDualSelectAbstractPane { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return [...AkDualSelectAbstractPane.styles, availablePaneStyles]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* The array of key/value pairs this pane is currently showing */ |     /* The array of key/value pairs this pane is currently showing */ | ||||||
| @ -56,68 +49,31 @@ export class AkDualSelectAvailablePane extends CustomEmitterElement(AKElement) { | |||||||
|     @property({ type: Object }) |     @property({ type: Object }) | ||||||
|     readonly selected: Set<string> = new Set(); |     readonly selected: Set<string> = new Set(); | ||||||
|  |  | ||||||
|     /* This is the only mutator for this object. It collects the list of objects the user has |     @bound | ||||||
|      * clicked on *in this pane*. It is explicitly marked as "public" to emphasize that the parent |  | ||||||
|      * orchestrator for the dual-select widget can and will access it to get the list of keys to be |  | ||||||
|      * moved (removed) if the user so requests. |  | ||||||
|      * |  | ||||||
|      */ |  | ||||||
|     @state() |  | ||||||
|     public toMove: Set<string> = new Set(); |  | ||||||
|  |  | ||||||
|     constructor() { |  | ||||||
|         super(); |  | ||||||
|         this.onClick = this.onClick.bind(this); |  | ||||||
|         this.onMove = this.onMove.bind(this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     connectedCallback() { |  | ||||||
|         super.connectedCallback(); |  | ||||||
|         hostAttributes.forEach(([attr, value]) => { |  | ||||||
|             if (!this.hasAttribute(attr)) { |  | ||||||
|                 this.setAttribute(attr, value); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     clearMove() { |  | ||||||
|         this.toMove = new Set(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     onClick(key: string) { |     onClick(key: string) { | ||||||
|         if (this.selected.has(key)) { |         if (this.selected.has(key)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (this.toMove.has(key)) { |         this.move(key); | ||||||
|             this.toMove.delete(key); |         this.dispatchEvent(new DualSelectMoveAvailableEvent(this.moveable.sort())); | ||||||
|         } else { |         this.dispatchEvent(new DualSelectUpdateEvent()); | ||||||
|             this.toMove.add(key); |  | ||||||
|         } |  | ||||||
|         this.dispatchCustomEvent( |  | ||||||
|             "ak-dual-select-available-move-changed", |  | ||||||
|             Array.from(this.toMove.values()).sort(), |  | ||||||
|         ); |  | ||||||
|         this.dispatchCustomEvent("ak-dual-select-move"); |  | ||||||
|         // Necessary because updating a map won't trigger a state change |         // Necessary because updating a map won't trigger a state change | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @bound | ||||||
|     onMove(key: string) { |     onMove(key: string) { | ||||||
|         this.toMove.delete(key); |         this.toMove.delete(key); | ||||||
|         this.dispatchCustomEvent(EVENT_ADD_ONE, key); |         this.dispatchEvent(new DualSelectMoveRequestEvent("add-one", key)); | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get moveable() { |  | ||||||
|         return Array.from(this.toMove.values()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // DO NOT use `Array.map()` instead of Lit's `map()` function. Lit's `map()` is object-aware and |     // DO NOT use `Array.map()` instead of Lit's `map()` function. Lit's `map()` is object-aware and | ||||||
|     // will not re-arrange or reconstruct the list automatically if the actual sources do not |     // will not re-arrange or reconstruct the list automatically if the actual sources do not | ||||||
|     // change; this allows the available pane to illustrate selected items with the checkmark |     // change; this allows the available pane to illustrate selected items with the checkmark | ||||||
|     // without causing the list to scroll back up to the top. |     // without causing the list to scroll back up to the top. | ||||||
|  |  | ||||||
|     render() { |     override render() { | ||||||
|         return html` |         return html` | ||||||
|             <div class="pf-c-dual-list-selector__menu"> |             <div class="pf-c-dual-list-selector__menu"> | ||||||
|                 <ul class="pf-c-dual-list-selector__list"> |                 <ul class="pf-c-dual-list-selector__list"> | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; | import { msg } from "@lit/localize"; | ||||||
| import { css, html, nothing } from "lit"; | import { css, html, nothing } from "lit"; | ||||||
| @ -8,13 +7,7 @@ import { customElement, property } from "lit/decorators.js"; | |||||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
| import { | import { DualSelectMoveRequestEvent, type MoveEventType } from "../events"; | ||||||
|     EVENT_ADD_ALL, |  | ||||||
|     EVENT_ADD_SELECTED, |  | ||||||
|     EVENT_DELETE_ALL, |  | ||||||
|     EVENT_REMOVE_ALL, |  | ||||||
|     EVENT_REMOVE_SELECTED, |  | ||||||
| } from "../constants"; |  | ||||||
|  |  | ||||||
| const styles = [ | const styles = [ | ||||||
|     PFBase, |     PFBase, | ||||||
| @ -47,7 +40,7 @@ const styles = [ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| @customElement("ak-dual-select-controls") | @customElement("ak-dual-select-controls") | ||||||
| export class AkDualSelectControls extends CustomEmitterElement(AKElement) { | export class AkDualSelectControls extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return styles; | ||||||
|     } |     } | ||||||
| @ -96,11 +89,11 @@ export class AkDualSelectControls extends CustomEmitterElement(AKElement) { | |||||||
|         this.onClick = this.onClick.bind(this); |         this.onClick = this.onClick.bind(this); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     onClick(eventName: string) { |     onClick(eventName: MoveEventType) { | ||||||
|         this.dispatchCustomEvent(eventName); |         this.dispatchEvent(new DualSelectMoveRequestEvent(eventName)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderButton(label: string, event: string, active: boolean, direction: string) { |     renderButton(label: string, event: MoveEventType, active: boolean, direction: string) { | ||||||
|         return html` |         return html` | ||||||
|             <div class="pf-c-dual-list-selector__controls-item"> |             <div class="pf-c-dual-list-selector__controls-item"> | ||||||
|                 <button |                 <button | ||||||
| @ -121,23 +114,18 @@ export class AkDualSelectControls extends CustomEmitterElement(AKElement) { | |||||||
|     render() { |     render() { | ||||||
|         return html` |         return html` | ||||||
|             <div class="ak-dual-list-selector__controls"> |             <div class="ak-dual-list-selector__controls"> | ||||||
|                 ${this.renderButton( |                 ${this.renderButton(msg("Add"), "add-selected", this.addActive, "fa-angle-right")} | ||||||
|                     msg("Add"), |  | ||||||
|                     EVENT_ADD_SELECTED, |  | ||||||
|                     this.addActive, |  | ||||||
|                     "fa-angle-right", |  | ||||||
|                 )} |  | ||||||
|                 ${this.selectAll |                 ${this.selectAll | ||||||
|                     ? html` |                     ? html` | ||||||
|                           ${this.renderButton( |                           ${this.renderButton( | ||||||
|                               msg("Add All Available"), |                               msg("Add All Available"), | ||||||
|                               EVENT_ADD_ALL, |                               "add-all", | ||||||
|                               this.addAllActive, |                               this.addAllActive, | ||||||
|                               "fa-angle-double-right", |                               "fa-angle-double-right", | ||||||
|                           )} |                           )} | ||||||
|                           ${this.renderButton( |                           ${this.renderButton( | ||||||
|                               msg("Remove All Available"), |                               msg("Remove All Available"), | ||||||
|                               EVENT_REMOVE_ALL, |                               "remove-all", | ||||||
|                               this.removeAllActive, |                               this.removeAllActive, | ||||||
|                               "fa-angle-double-left", |                               "fa-angle-double-left", | ||||||
|                           )} |                           )} | ||||||
| @ -145,14 +133,14 @@ export class AkDualSelectControls extends CustomEmitterElement(AKElement) { | |||||||
|                     : nothing} |                     : nothing} | ||||||
|                 ${this.renderButton( |                 ${this.renderButton( | ||||||
|                     msg("Remove"), |                     msg("Remove"), | ||||||
|                     EVENT_REMOVE_SELECTED, |                     "remove-selected", | ||||||
|                     this.removeActive, |                     this.removeActive, | ||||||
|                     "fa-angle-left", |                     "fa-angle-left", | ||||||
|                 )} |                 )} | ||||||
|                 ${this.deleteAll |                 ${this.deleteAll | ||||||
|                     ? html`${this.renderButton( |                     ? html`${this.renderButton( | ||||||
|                           msg("Remove All"), |                           msg("Remove All"), | ||||||
|                           EVENT_DELETE_ALL, |                           "delete-all", | ||||||
|                           this.enableDeleteAll, |                           this.enableDeleteAll, | ||||||
|                           "fa-times", |                           "fa-times", | ||||||
|                       )}` |                       )}` | ||||||
|  | |||||||
| @ -0,0 +1,75 @@ | |||||||
|  | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
|  |  | ||||||
|  | import { TemplateResult } from "lit"; | ||||||
|  | import { state } from "lit/decorators.js"; | ||||||
|  |  | ||||||
|  | import { listStyles } from "./styles.css"; | ||||||
|  | import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||||
|  | import PFDualListSelector from "@patternfly/patternfly/components/DualListSelector/dual-list-selector.css"; | ||||||
|  | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
|  | const styles = [PFBase, PFButton, PFDualListSelector, listStyles]; | ||||||
|  |  | ||||||
|  | const hostAttributes = [ | ||||||
|  |     ["aria-labelledby", "dual-list-selector-selected-pane-status"], | ||||||
|  |     ["aria-multiselectable", "true"], | ||||||
|  |     ["role", "listbox"], | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @element ak-dual-select-panel | ||||||
|  |  * | ||||||
|  |  * The "selected options" or "right" pane in a dual-list multi-select.  It receives from its parent | ||||||
|  |  * a list of the selected options, and maintains an internal list of objects selected to move. | ||||||
|  |  * | ||||||
|  |  * @fires ak-dual-select-selected-move-changed - When the list of "to move" entries changed. | ||||||
|  |  * Includes the current `toMove` content. | ||||||
|  |  * | ||||||
|  |  * @fires ak-dual-select-remove-one - Double-click with the element clicked on. | ||||||
|  |  * | ||||||
|  |  * It is not expected that the `ak-dual-select-selected-move-changed` will be used; instead, the | ||||||
|  |  * attribute will be read by the parent when a control is clicked. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | export abstract class AkDualSelectAbstractPane extends AKElement { | ||||||
|  |     static get styles() { | ||||||
|  |         return styles; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |      * This is the only mutator for this object. It collects the list of objects the user has | ||||||
|  |      * clicked on *in this pane*. It is explicitly marked as "public" to emphasize that the parent | ||||||
|  |      * orchestrator for the dual-select widget can and will access it to get the list of keys to be | ||||||
|  |      * moved (removed) if the user so requests. | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     @state() | ||||||
|  |     public toMove: Set<string> = new Set(); | ||||||
|  |  | ||||||
|  |     connectedCallback() { | ||||||
|  |         super.connectedCallback(); | ||||||
|  |         hostAttributes.forEach(([attr, value]) => { | ||||||
|  |             if (!this.hasAttribute(attr)) { | ||||||
|  |                 this.setAttribute(attr, value); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     clearMove() { | ||||||
|  |         this.toMove = new Set(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     move(key: string) { | ||||||
|  |         if (this.toMove.has(key)) { | ||||||
|  |             this.toMove.delete(key); | ||||||
|  |         } else { | ||||||
|  |             this.toMove.add(key); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get moveable() { | ||||||
|  |         return Array.from(this.toMove.values()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     abstract render(): TemplateResult; | ||||||
|  | } | ||||||
| @ -1,26 +1,19 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { bound } from "@goauthentik/elements/decorators/bound"; | ||||||
| import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { html } from "lit"; | import { html } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| import { classMap } from "lit/directives/class-map.js"; | import { classMap } from "lit/directives/class-map.js"; | ||||||
| import { map } from "lit/directives/map.js"; | import { map } from "lit/directives/map.js"; | ||||||
|  |  | ||||||
| import { listStyles, selectedPaneStyles } from "./styles.css"; | import { selectedPaneStyles } from "./styles.css"; | ||||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; |  | ||||||
| import PFDualListSelector from "@patternfly/patternfly/components/DualListSelector/dual-list-selector.css"; |  | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; |  | ||||||
|  |  | ||||||
| import { EVENT_REMOVE_ONE } from "../constants"; | import { | ||||||
|  |     DualSelectMoveRequestEvent, | ||||||
|  |     DualSelectMoveSelectedEvent, | ||||||
|  |     DualSelectUpdateEvent, | ||||||
|  | } from "../events"; | ||||||
| import type { DualSelectPair } from "../types"; | import type { DualSelectPair } from "../types"; | ||||||
|  | import { AkDualSelectAbstractPane } from "./ak-dual-select-pane"; | ||||||
| const styles = [PFBase, PFButton, PFDualListSelector, listStyles, selectedPaneStyles]; |  | ||||||
|  |  | ||||||
| const hostAttributes = [ |  | ||||||
|     ["aria-labelledby", "dual-list-selector-selected-pane-status"], |  | ||||||
|     ["aria-multiselectable", "true"], |  | ||||||
|     ["role", "listbox"], |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @element ak-dual-select-available-panel |  * @element ak-dual-select-available-panel | ||||||
| @ -38,70 +31,32 @@ const hostAttributes = [ | |||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| @customElement("ak-dual-select-selected-pane") | @customElement("ak-dual-select-selected-pane") | ||||||
| export class AkDualSelectSelectedPane extends CustomEmitterElement(AKElement) { | export class AkDualSelectSelectedPane extends AkDualSelectAbstractPane { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return [...AkDualSelectAbstractPane.styles, selectedPaneStyles]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* The array of key/value pairs that are in the selected list.  ALL of them. */ |     /* The array of key/value pairs that are in the selected list.  ALL of them. */ | ||||||
|     @property({ type: Array }) |     @property({ type: Array }) | ||||||
|     readonly selected: DualSelectPair[] = []; |     readonly selected: DualSelectPair[] = []; | ||||||
|  |  | ||||||
|     /* |     @bound | ||||||
|      * This is the only mutator for this object. It collects the list of objects the user has |  | ||||||
|      * clicked on *in this pane*. It is explicitly marked as "public" to emphasize that the parent |  | ||||||
|      * orchestrator for the dual-select widget can and will access it to get the list of keys to be |  | ||||||
|      * moved (removed) if the user so requests. |  | ||||||
|      * |  | ||||||
|      */ |  | ||||||
|     @state() |  | ||||||
|     public toMove: Set<string> = new Set(); |  | ||||||
|  |  | ||||||
|     constructor() { |  | ||||||
|         super(); |  | ||||||
|         this.onClick = this.onClick.bind(this); |  | ||||||
|         this.onMove = this.onMove.bind(this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     connectedCallback() { |  | ||||||
|         super.connectedCallback(); |  | ||||||
|         hostAttributes.forEach(([attr, value]) => { |  | ||||||
|             if (!this.hasAttribute(attr)) { |  | ||||||
|                 this.setAttribute(attr, value); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     clearMove() { |  | ||||||
|         this.toMove = new Set(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     onClick(key: string) { |     onClick(key: string) { | ||||||
|         if (this.toMove.has(key)) { |         this.move(key); | ||||||
|             this.toMove.delete(key); |         this.dispatchEvent(new DualSelectMoveSelectedEvent(this.moveable.sort())); | ||||||
|         } else { |         this.dispatchEvent(new DualSelectUpdateEvent()); | ||||||
|             this.toMove.add(key); |  | ||||||
|         } |  | ||||||
|         this.dispatchCustomEvent( |  | ||||||
|             "ak-dual-select-selected-move-changed", |  | ||||||
|             Array.from(this.toMove.values()).sort(), |  | ||||||
|         ); |  | ||||||
|         this.dispatchCustomEvent("ak-dual-select-move"); |  | ||||||
|         // Necessary because updating a map won't trigger a state change |         // Necessary because updating a map won't trigger a state change | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @bound | ||||||
|     onMove(key: string) { |     onMove(key: string) { | ||||||
|         this.toMove.delete(key); |         this.toMove.delete(key); | ||||||
|         this.dispatchCustomEvent(EVENT_REMOVE_ONE, key); |         this.dispatchEvent(new DualSelectMoveRequestEvent("remove-one", key)); | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get moveable() { |     override render() { | ||||||
|         return Array.from(this.toMove.values()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return html` |         return html` | ||||||
|             <div class="pf-c-dual-list-selector__menu"> |             <div class="pf-c-dual-list-selector__menu"> | ||||||
|                 <ul class="pf-c-dual-list-selector__list"> |                 <ul class="pf-c-dual-list-selector__list"> | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { msg, str } from "@lit/localize"; | import { msg, str } from "@lit/localize"; | ||||||
| import { css, html, nothing } from "lit"; | import { css, html, nothing } from "lit"; | ||||||
| @ -9,6 +8,7 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; | |||||||
| import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css"; | import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css"; | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
|  | import { DualSelectPaginatorNavEvent } from "../events"; | ||||||
| import type { BasePagination } from "../types"; | import type { BasePagination } from "../types"; | ||||||
|  |  | ||||||
| const styles = [ | const styles = [ | ||||||
| @ -27,7 +27,7 @@ const styles = [ | |||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @customElement("ak-pagination") | @customElement("ak-pagination") | ||||||
| export class AkPagination extends CustomEmitterElement(AKElement) { | export class AkPagination extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return styles; | ||||||
|     } |     } | ||||||
| @ -41,7 +41,7 @@ export class AkPagination extends CustomEmitterElement(AKElement) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     onClick(nav: number | undefined) { |     onClick(nav: number | undefined) { | ||||||
|         this.dispatchCustomEvent("ak-pagination-nav-to", nav ?? 0); |         this.dispatchEvent(new DualSelectPaginatorNavEvent(nav ?? 0)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; |  | ||||||
|  |  | ||||||
| import { html } from "lit"; | import { html } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| @ -9,12 +8,12 @@ import type { Ref } from "lit/directives/ref.js"; | |||||||
| import { globalVariables, searchStyles } from "./search.css"; | import { globalVariables, searchStyles } from "./search.css"; | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
| import type { SearchbarEvent } from "../types"; | import { DualSelectPanelSearchEvent } from "../events"; | ||||||
|  |  | ||||||
| const styles = [PFBase, globalVariables, searchStyles]; | const styles = [PFBase, globalVariables, searchStyles]; | ||||||
|  |  | ||||||
| @customElement("ak-search-bar") | @customElement("ak-search-bar") | ||||||
| export class AkSearchbar extends CustomEmitterElement(AKElement) { | export class AkSearchbar extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return styles; |         return styles; | ||||||
|     } |     } | ||||||
| @ -40,10 +39,7 @@ export class AkSearchbar extends CustomEmitterElement(AKElement) { | |||||||
|         if (this.input.value) { |         if (this.input.value) { | ||||||
|             this.value = this.input.value.value; |             this.value = this.input.value.value; | ||||||
|         } |         } | ||||||
|         this.dispatchCustomEvent<SearchbarEvent>("ak-search", { |         this.dispatchEvent(new DualSelectPanelSearchEvent(this.name, this.value)); | ||||||
|             source: this.name, |  | ||||||
|             value: this.value, |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|  | |||||||
							
								
								
									
										112
									
								
								web/src/elements/ak-dual-select/events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								web/src/elements/ak-dual-select/events.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | import { DualSelectPair } from "./types"; | ||||||
|  |  | ||||||
|  | // Handled by the Server layer provider | ||||||
|  |  | ||||||
|  | // Request to provide a different page of the paginated results in the "available" panel. | ||||||
|  | export class DualSelectPaginatorNavEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-paginator-nav"; | ||||||
|  |     constructor(public page: number = 0) { | ||||||
|  |         super(DualSelectPaginatorNavEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Request to provide a filtered collection for the "available" panel via a search string | ||||||
|  | export class DualSelectSearchEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-search"; | ||||||
|  |     constructor(public search: string) { | ||||||
|  |         super(DualSelectSearchEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Request to update the "selected" list in the provider | ||||||
|  | export class DualSelectChangeEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-change"; | ||||||
|  |     constructor(public selected: DualSelectPair[]) { | ||||||
|  |         super(DualSelectChangeEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Paginator and specific item events | ||||||
|  |  | ||||||
|  | export const moveEvents = [ | ||||||
|  |     "add-all", | ||||||
|  |     "add-one", | ||||||
|  |     "add-selected", | ||||||
|  |     "delete-all", | ||||||
|  |     "remove-all", | ||||||
|  |     "remove-one", | ||||||
|  |     "remove-selected", | ||||||
|  | ] as const; | ||||||
|  |  | ||||||
|  | export type MoveEventType = (typeof moveEvents)[number]; | ||||||
|  |  | ||||||
|  | // Request to add or remove all, some, or just one item from the "selected" panel | ||||||
|  | export class DualSelectMoveRequestEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-request-move"; | ||||||
|  |     constructor( | ||||||
|  |         public move: MoveEventType, | ||||||
|  |         public key?: string, | ||||||
|  |     ) { | ||||||
|  |         super(DualSelectMoveRequestEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Update events | ||||||
|  |  | ||||||
|  | // Request to update the viewset | ||||||
|  | export class DualSelectUpdateEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-update"; | ||||||
|  |     constructor() { | ||||||
|  |         super(DualSelectUpdateEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface DualSelectMoveChangedEvent { | ||||||
|  |     keys: string[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Request to update the list of "marked for move" items in the "available" panel | ||||||
|  | export class DualSelectMoveAvailableEvent extends Event implements DualSelectMoveChangedEvent { | ||||||
|  |     static readonly eventName = "ak-dual-select-move-available"; | ||||||
|  |     constructor(public keys: string[]) { | ||||||
|  |         super(DualSelectMoveAvailableEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Request to update the list of "marked for move" items in the "selected" panel | ||||||
|  | export class DualSelectMoveSelectedEvent extends Event implements DualSelectMoveChangedEvent { | ||||||
|  |     static readonly eventName = "ak-dual-select-move-selected"; | ||||||
|  |     constructor(public keys: string[]) { | ||||||
|  |         super(DualSelectMoveSelectedEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Request to update either panel with a Filter | ||||||
|  | export class DualSelectPanelSearchEvent extends Event { | ||||||
|  |     static readonly eventName = "ak-dual-select-panel-search"; | ||||||
|  |     constructor( | ||||||
|  |         public source: string, | ||||||
|  |         public filterOn: string, | ||||||
|  |     ) { | ||||||
|  |         super(DualSelectPanelSearchEvent.eventName, { bubbles: true, composed: true }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | declare global { | ||||||
|  |     interface HTMLElementEventMap { | ||||||
|  |         [DualSelectUpdateEvent.eventName]: DualSelectUpdateEvent; | ||||||
|  |         [DualSelectMoveAvailableEvent.eventName]: DualSelectMoveAvailableEvent; | ||||||
|  |         [DualSelectMoveSelectedEvent.eventName]: DualSelectMoveSelectedEvent; | ||||||
|  |         [DualSelectMoveRequestEvent.eventName]: DualSelectMoveRequestEvent; | ||||||
|  |         [DualSelectPaginatorNavEvent.eventName]: DualSelectPaginatorNavEvent; | ||||||
|  |         [DualSelectSearchEvent.eventName]: DualSelectSearchEvent; | ||||||
|  |         [DualSelectChangeEvent.eventName]: DualSelectChangeEvent; | ||||||
|  |         [DualSelectPanelSearchEvent.eventName]: DualSelectPanelSearchEvent; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface WindowEventMap { | ||||||
|  |         [DualSelectMoveRequestEvent.eventName]: DualSelectMoveRequestEvent; | ||||||
|  |         [DualSelectPaginatorNavEvent.eventName]: DualSelectPaginatorNavEvent; | ||||||
|  |         [DualSelectMoveSelectedEvent.eventName]: DualSelectMoveSelectedEvent; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -6,6 +6,7 @@ import { TemplateResult, html } from "lit"; | |||||||
|  |  | ||||||
| import "../components/ak-dual-select-available-pane"; | import "../components/ak-dual-select-available-pane"; | ||||||
| import { AkDualSelectAvailablePane } from "../components/ak-dual-select-available-pane"; | import { AkDualSelectAvailablePane } from "../components/ak-dual-select-available-pane"; | ||||||
|  | import { DualSelectMoveSelectedEvent } from "../events"; | ||||||
| import "./sb-host-provider"; | import "./sb-host-provider"; | ||||||
|  |  | ||||||
| const metadata: Meta<AkDualSelectAvailablePane> = { | const metadata: Meta<AkDualSelectAvailablePane> = { | ||||||
| @ -53,15 +54,15 @@ const container = (testItem: TemplateResult) => | |||||||
|     </div>`; |     </div>`; | ||||||
|  |  | ||||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
| const handleMoveChanged = (result: any) => { | const handleMoveChanged = (result: DualSelectMoveSelectedEvent) => { | ||||||
|     const target = document.querySelector("#action-button-message-pad"); |     const target = document.querySelector("#action-button-message-pad"); | ||||||
|     target!.innerHTML = ""; |     target!.innerHTML = ""; | ||||||
|     result.detail.forEach((key: string) => { |     result.keys.forEach((key: string) => { | ||||||
|         target!.append(new DOMParser().parseFromString(`<li>${key}</li>`, "text/xml").firstChild!); |         target!.append(new DOMParser().parseFromString(`<li>${key}</li>`, "text/xml").firstChild!); | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| window.addEventListener("ak-dual-select-available-move-changed", handleMoveChanged); | window.addEventListener(DualSelectMoveSelectedEvent.eventName, handleMoveChanged); | ||||||
|  |  | ||||||
| type Story = StoryObj; | type Story = StoryObj; | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import { TemplateResult, html } from "lit"; | |||||||
|  |  | ||||||
| import "../components/ak-dual-select-controls"; | import "../components/ak-dual-select-controls"; | ||||||
| import { AkDualSelectControls } from "../components/ak-dual-select-controls"; | import { AkDualSelectControls } from "../components/ak-dual-select-controls"; | ||||||
|  | import { DualSelectMoveRequestEvent } from "../events"; | ||||||
|  |  | ||||||
| const metadata: Meta<AkDualSelectControls> = { | const metadata: Meta<AkDualSelectControls> = { | ||||||
|     title: "Elements / Dual Select / Control Panel", |     title: "Elements / Dual Select / Control Panel", | ||||||
| @ -59,10 +60,9 @@ const displayMessage = (result: any) => { | |||||||
|     target!.appendChild(doc.firstChild!); |     target!.appendChild(doc.firstChild!); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| window.addEventListener("ak-dual-select-add", () => displayMessage("add")); | window.addEventListener(DualSelectMoveRequestEvent.eventName, (ev: DualSelectMoveRequestEvent) => | ||||||
| window.addEventListener("ak-dual-select-remove", () => displayMessage("remove")); |     displayMessage(ev.move.toString()), | ||||||
| window.addEventListener("ak-dual-select-add-all", () => displayMessage("add all")); | ); | ||||||
| window.addEventListener("ak-dual-select-remove-all", () => displayMessage("remove all")); |  | ||||||
|  |  | ||||||
| type Story = StoryObj; | type Story = StoryObj; | ||||||
|  |  | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import { Pagination } from "@goauthentik/api"; | |||||||
|  |  | ||||||
| import "../ak-dual-select"; | import "../ak-dual-select"; | ||||||
| import { AkDualSelect } from "../ak-dual-select"; | import { AkDualSelect } from "../ak-dual-select"; | ||||||
|  | import { DualSelectPaginatorNavEvent } from "../events"; | ||||||
| import type { DualSelectPair } from "../types"; | import type { DualSelectPair } from "../types"; | ||||||
|  |  | ||||||
| const goodForYouRaw = ` | const goodForYouRaw = ` | ||||||
| @ -83,11 +84,11 @@ export class AkSbFruity extends LitElement { | |||||||
|             totalPages: Math.ceil(this.options.length / this.pageLength), |             totalPages: Math.ceil(this.options.length / this.pageLength), | ||||||
|         }; |         }; | ||||||
|         this.onNavigation = this.onNavigation.bind(this); |         this.onNavigation = this.onNavigation.bind(this); | ||||||
|         this.addEventListener("ak-pagination-nav-to", this.onNavigation); |         this.addEventListener(DualSelectPaginatorNavEvent.eventName, this.onNavigation); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     onNavigation(evt: Event) { |     onNavigation(evt: DualSelectPaginatorNavEvent) { | ||||||
|         const current: number = (evt as CustomEvent).detail; |         const current = evt.page; | ||||||
|         const index = current - 1; |         const index = current - 1; | ||||||
|         if (index * this.pageLength > this.options.length) { |         if (index * this.pageLength > this.options.length) { | ||||||
|             console.warn( |             console.warn( | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ import { TemplateResult, html } from "lit"; | |||||||
|  |  | ||||||
| import "../components/ak-dual-select-selected-pane"; | import "../components/ak-dual-select-selected-pane"; | ||||||
| import { AkDualSelectSelectedPane } from "../components/ak-dual-select-selected-pane"; | import { AkDualSelectSelectedPane } from "../components/ak-dual-select-selected-pane"; | ||||||
|  | import { DualSelectMoveSelectedEvent } from "../events"; | ||||||
| import "./sb-host-provider"; | import "./sb-host-provider"; | ||||||
|  |  | ||||||
| const metadata: Meta<AkDualSelectSelectedPane> = { | const metadata: Meta<AkDualSelectSelectedPane> = { | ||||||
| @ -50,15 +51,15 @@ const container = (testItem: TemplateResult) => | |||||||
|     </div>`; |     </div>`; | ||||||
|  |  | ||||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
| const handleMoveChanged = (result: any) => { | const handleMoveChanged = (result: DualSelectMoveSelectedEvent) => { | ||||||
|     const target = document.querySelector("#action-button-message-pad"); |     const target = document.querySelector("#action-button-message-pad"); | ||||||
|     target!.innerHTML = ""; |     target!.innerHTML = ""; | ||||||
|     result.detail.forEach((key: string) => { |     result.keys.forEach((key: string) => { | ||||||
|         target!.append(new DOMParser().parseFromString(`<li>${key}</li>`, "text/xml").firstChild!); |         target!.append(new DOMParser().parseFromString(`<li>${key}</li>`, "text/xml").firstChild!); | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| window.addEventListener("ak-dual-select-selected-move-changed", handleMoveChanged); | window.addEventListener(DualSelectMoveSelectedEvent.eventName, handleMoveChanged); | ||||||
|  |  | ||||||
| type Story = StoryObj; | type Story = StoryObj; | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import { TemplateResult, html } from "lit"; | |||||||
|  |  | ||||||
| import "../components/ak-pagination"; | import "../components/ak-pagination"; | ||||||
| import { AkPagination } from "../components/ak-pagination"; | import { AkPagination } from "../components/ak-pagination"; | ||||||
|  | import { DualSelectPaginatorNavEvent } from "../events"; | ||||||
|  |  | ||||||
| const metadata: Meta<AkPagination> = { | const metadata: Meta<AkPagination> = { | ||||||
|     title: "Elements / Dual Select / Pagination Control", |     title: "Elements / Dual Select / Pagination Control", | ||||||
| @ -43,18 +44,18 @@ const container = (testItem: TemplateResult) => | |||||||
|     </div>`; |     </div>`; | ||||||
|  |  | ||||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
| const handleMoveChanged = (result: any) => { | const handleMoveChanged = (result: DualSelectPaginatorNavEvent) => { | ||||||
|     console.debug(result); |     console.debug(result); | ||||||
|     const target = document.querySelector("#action-button-message-pad"); |     const target = document.querySelector("#action-button-message-pad"); | ||||||
|     target!.append( |     target!.append( | ||||||
|         new DOMParser().parseFromString( |         new DOMParser().parseFromString( | ||||||
|             `<li>Request to move to page ${result.detail}</li>`, |             `<li>Request to move to page ${result.page}</li>`, | ||||||
|             "text/xml", |             "text/xml", | ||||||
|         ).firstChild!, |         ).firstChild!, | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| window.addEventListener("ak-pagination-nav-to", handleMoveChanged); | window.addEventListener(DualSelectPaginatorNavEvent.eventName, handleMoveChanged); | ||||||
|  |  | ||||||
| type Story = StoryObj; | type Story = StoryObj; | ||||||
|  |  | ||||||
|  | |||||||
| @ -29,10 +29,3 @@ export type DataProvision = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export type DataProvider = (page: number, search?: string) => Promise<DataProvision>; | export type DataProvider = (page: number, search?: string) => Promise<DataProvision>; | ||||||
|  |  | ||||||
| export interface SearchbarEvent extends CustomEvent { |  | ||||||
|     detail: { |  | ||||||
|         source: string; |  | ||||||
|         value: string; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	