
# What - Replace all `CustomEvent<T>` handlers with a child class Event. - Use the standard `dispatchEvent()` and `addEventListener()` clauses. - Move common methods of the two panes into an abstract parent class. # Why CustomEvent is a bit of a side-show in JavaScript; it's perfectly acceptable to inherit from Event, and there's much better support for type management while using it, plus deconstructing the received event object is cleaner with child Events than unpacking the `details` object. This codebase is now more open to change, and is closer to our still-evolving style. Doing it this way mean that, once you have an event defined, you don't have to remember if you're sending a custom event or a normal event, and you don't need all that infrastructure for making your Lit objects sensitive to custom event handling. There's enough mixin clutter already. And just like I hate clutter, I hate duplication. The two panes had a lot in common with ARIA handling and storing, clearing, and assigning selected items to the "pending move" table. Moving all that into a parent class exposed the differences: one is a source and the other a sink; one reflects changes made, the other possible changes to be made. # Testing The Storybook tests have all been updated to match the codebase, and there are standalone tests for the various components (pagination, pane control, search) that can be exercised before deployment.
160 lines
5.3 KiB
TypeScript
160 lines
5.3 KiB
TypeScript
import { AKElement } from "@goauthentik/elements/Base";
|
|
|
|
import { msg } from "@lit/localize";
|
|
import { css, html, nothing } from "lit";
|
|
import { customElement, property } from "lit/decorators.js";
|
|
|
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|
|
|
import { DualSelectMoveRequestEvent, type MoveEventType } from "../events";
|
|
|
|
const styles = [
|
|
PFBase,
|
|
PFButton,
|
|
css`
|
|
:host {
|
|
align-self: center;
|
|
padding-right: var(--pf-c-dual-list-selector__controls--PaddingRight);
|
|
padding-left: var(--pf-c-dual-list-selector__controls--PaddingLeft);
|
|
}
|
|
.pf-c-dual-list-selector {
|
|
max-width: 4rem;
|
|
}
|
|
.ak-dual-list-selector__controls {
|
|
display: grid;
|
|
justify-content: center;
|
|
align-content: center;
|
|
height: 100%;
|
|
}
|
|
`,
|
|
];
|
|
|
|
/**
|
|
* @element ak-dual-select-controls
|
|
*
|
|
* The "control box" for a dual-list multi-select. It's controlled by the parent orchestrator as to
|
|
* whether or not any of its controls are enabled. It sends a variety of messages to the parent
|
|
* orchestrator which will then reconcile the "available" and "selected" panes at need.
|
|
*
|
|
*/
|
|
|
|
@customElement("ak-dual-select-controls")
|
|
export class AkDualSelectControls extends AKElement {
|
|
static get styles() {
|
|
return styles;
|
|
}
|
|
|
|
/* Set to true if any *visible* elements can be added to the selected list
|
|
*/
|
|
@property({ attribute: "add-active", type: Boolean })
|
|
addActive = false;
|
|
|
|
/* Set to true if any elements can be removed from the selected list (essentially,
|
|
* if the selected list is not empty)
|
|
*/
|
|
@property({ attribute: "remove-active", type: Boolean })
|
|
removeActive = false;
|
|
|
|
/* Set to true if *all* the currently visible elements can be moved
|
|
* into the selected list (essentially, if any visible elements are
|
|
* not currently selected)
|
|
*/
|
|
@property({ attribute: "add-all-active", type: Boolean })
|
|
addAllActive = false;
|
|
|
|
/* Set to true if *any* of the elements currently visible in the available
|
|
* pane are available to be moved to the selected list, enabling that
|
|
* all of those specific elements be moved out of the selected list
|
|
*/
|
|
@property({ attribute: "remove-all-active", type: Boolean })
|
|
removeAllActive = false;
|
|
|
|
/* if deleteAll is enabled, set to true to show that there are elements in the
|
|
* selected list that can be deleted.
|
|
*/
|
|
@property({ attribute: "delete-all-active", type: Boolean })
|
|
enableDeleteAll = false;
|
|
|
|
/* Set to true if you want the `...AllActive` buttons made available. */
|
|
@property({ attribute: "enable-select-all", type: Boolean })
|
|
selectAll = false;
|
|
|
|
/* Set to true if you want the `ClearAllSelected` button made available */
|
|
@property({ attribute: "enable-delete-all", type: Boolean })
|
|
deleteAll = false;
|
|
|
|
constructor() {
|
|
super();
|
|
this.onClick = this.onClick.bind(this);
|
|
}
|
|
|
|
onClick(eventName: MoveEventType) {
|
|
this.dispatchEvent(new DualSelectMoveRequestEvent(eventName));
|
|
}
|
|
|
|
renderButton(label: string, event: MoveEventType, active: boolean, direction: string) {
|
|
return html`
|
|
<div class="pf-c-dual-list-selector__controls-item">
|
|
<button
|
|
?aria-disabled=${!active}
|
|
?disabled=${!active}
|
|
aria-label=${label}
|
|
class="pf-c-button pf-m-plain"
|
|
type="button"
|
|
@click=${() => this.onClick(event)}
|
|
data-ouia-component-type="AK/Button"
|
|
>
|
|
<i class="fa ${direction}"></i>
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<div class="ak-dual-list-selector__controls">
|
|
${this.renderButton(msg("Add"), "add-selected", this.addActive, "fa-angle-right")}
|
|
${this.selectAll
|
|
? html`
|
|
${this.renderButton(
|
|
msg("Add All Available"),
|
|
"add-all",
|
|
this.addAllActive,
|
|
"fa-angle-double-right",
|
|
)}
|
|
${this.renderButton(
|
|
msg("Remove All Available"),
|
|
"remove-all",
|
|
this.removeAllActive,
|
|
"fa-angle-double-left",
|
|
)}
|
|
`
|
|
: nothing}
|
|
${this.renderButton(
|
|
msg("Remove"),
|
|
"remove-selected",
|
|
this.removeActive,
|
|
"fa-angle-left",
|
|
)}
|
|
${this.deleteAll
|
|
? html`${this.renderButton(
|
|
msg("Remove All"),
|
|
"delete-all",
|
|
this.enableDeleteAll,
|
|
"fa-times",
|
|
)}`
|
|
: nothing}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
export default AkDualSelectControls;
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ak-dual-select-controls": AkDualSelectControls;
|
|
}
|
|
}
|