@lookwe/lit-mixins 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # Lit Mixins
2
+
3
+ This repository provides a collection of mixins for enhancing [LitElement](https://lit.dev/) components with advanced functionalities, including element internals, form association, focus delegation, popover control and more...
4
+
5
+ ## Installation
6
+
7
+ Install the mixins package using npm:
8
+
9
+ ```bash
10
+ npm install @lookwe/lit-mixins
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Available Mixins
16
+
17
+ ### 1. **`mixinElementInternals`**
18
+
19
+ Provides access to the `ElementInternals` API for a custom element, allowing you to manage form participation, accessibility roles, and more.
20
+
21
+ **Usage Example:**
22
+
23
+ ```ts
24
+ import { LitElement } from 'lit';
25
+
26
+ import { internals, mixinElementInternals } from '@lookwe/lit-mixins';
27
+
28
+ class MyElement extends mixinElementInternals(LitElement) {
29
+ constructor() {
30
+ super();
31
+ this[internals].role = 'button'; // Assign a role for accessibility
32
+ }
33
+ }
34
+ customElements.define('my-element', MyElement);
35
+ ```
36
+
37
+ **Key Features:**
38
+
39
+ - Provides a `readonly` property `[internals]` to access `ElementInternals`.
40
+
41
+ ---
42
+
43
+ ### 2. **`mixinFormAssociated`**
44
+
45
+ Enables your custom element to integrate with HTML forms, similar to native form controls.
46
+
47
+ **Usage Example:**
48
+
49
+ ```ts
50
+ import { LitElement } from 'lit';
51
+ import { property } from 'lit/decorator.js';
52
+
53
+ import { getFormValue, mixinElementInternals, mixinFormAssociated } from '@lookwe/lit-mixins';
54
+
55
+ class MyFormControl extends mixinFormAssociated(mixinElementInternals(LitElement)) {
56
+ @property() accessor value = '';
57
+
58
+ // ...
59
+
60
+ [getFormValue]() {
61
+ return this.value; // Return the form value
62
+ }
63
+
64
+ formResetCallback() {
65
+ this.value = this.getAttribute('value') || ''; // Reset value on form reset
66
+ }
67
+
68
+ formStateRestoreCallback(state) {
69
+ this.value = state || ''; // Restore form state
70
+ }
71
+ }
72
+ customElements.define('my-form-control', MyFormControl);
73
+ ```
74
+
75
+ **Key Features:**
76
+
77
+ - Provides properties like `form`, `labels`, and `name` for form integration.
78
+ - Supports callbacks like `formResetCallback` and `formStateRestoreCallback` for custom behaviors.
79
+
80
+ ---
81
+
82
+ ### 3. **`mixinDelegateFocus`**
83
+
84
+ Allows your component to delegate focus to a specific internal element, enhancing accessibility.
85
+
86
+ **Usage Example:**
87
+
88
+ ```ts
89
+ import { html, LitElement } from 'lit';
90
+
91
+ import { getFocusElement, mixinDelegateFocus } from '@lookwe/lit-mixins';
92
+
93
+ class MyFocusableElement extends mixinDelegateFocus(LitElement) {
94
+ render() {
95
+ return html` <div tabindex="0">Focusable Content</div> `;
96
+ }
97
+
98
+ [getFocusElement]() {
99
+ return this.shadowRoot.querySelector('[tabindex="0"]'); // Delegate focus to this element
100
+ }
101
+ }
102
+ customElements.define('my-focusable-element', MyFocusableElement);
103
+ ```
104
+
105
+ **Key Features:**
106
+
107
+ - Delegates focus to the first focusable child element.
108
+ - Supports overriding `focus()` and `blur()` methods.
109
+
110
+ ---
111
+
112
+ ### 4. **`mixinPopoverTarget`**
113
+
114
+ Enables your component to act as a controller for popovers, using the Popover API.
115
+
116
+ **Usage Example:**
117
+
118
+ ```ts
119
+ import { html, LitElement } from 'lit';
120
+
121
+ import { mixinPopoverTarget } from '@lookwe/lit-mixins';
122
+
123
+ class MyPopoverController extends mixinPopoverTarget(LitElement) {
124
+ render() {
125
+ return html`
126
+ <button
127
+ .popoverTargetElement="${this.popoverTargetElement}"
128
+ .popoverTargetAction="${this.popoverTargetAction}"
129
+ >
130
+ Toggle Popover
131
+ </button>
132
+ `;
133
+ }
134
+ }
135
+ customElements.define('my-popover-controller', MyPopoverController);
136
+ ```
137
+
138
+ **Key Features:**
139
+
140
+ - Provides properties like `popoverTarget`, `popoverTargetElement`, and `popoverTargetAction`.
141
+ - Supports actions such as `'hide'`, `'show'`, and `'toggle'`.
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './internal/delegate-focus';
2
+ export * from './internal/element-internals';
3
+ export * from './internal/form-associated';
4
+ export * from './internal/popover-target';
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { getFocusElement, mixinDelegatesFocus } from './internal/delegate-focus.js';
2
+ export { internals, mixinElementInternals } from './internal/element-internals.js';
3
+ export { getFormState, getFormValue, mixinFormAssociated } from './internal/form-associated.js';
4
+ export { mixinPopoverTarget } from './internal/popover-target.js';
@@ -0,0 +1,41 @@
1
+ import { ReactiveElement } from 'lit';
2
+ import { MixinBase, MixinReturn } from '@lookwe/utils';
3
+ /**
4
+ * A unique symbol to retrieve the focus element.
5
+ *
6
+ * @example
7
+ *
8
+ * class MyElement extends mixinDelegatesFocus(ReactiveElement) {
9
+ * overrride [getFocusElement](): HTMLElement | null {
10
+ * return this.renderRoot.querySelector('[tabindex="0"]');
11
+ * }
12
+ * }
13
+ *
14
+ */
15
+ export declare const getFocusElement: unique symbol;
16
+ /**
17
+ * An instance with an `getFocusElement` symbol property for the component's focus element.
18
+ */
19
+ export interface WithDelegatesFocus {
20
+ [getFocusElement]?(): HTMLElement | null;
21
+ }
22
+ /**
23
+ * The constructor of a `WithDelegatesFocus` element.
24
+ */
25
+ export interface WithDelegatesFocusConstructor {
26
+ /**
27
+ * Options used when calling attachShadow. The first focusable part is given focus.
28
+ */
29
+ readonly shadowRootOptions: ShadowRootInit & {
30
+ delegatesFocus: true;
31
+ };
32
+ }
33
+ /**
34
+ * The first focusable part is given focus, and the shadow host is given any available :focus styling.
35
+ *
36
+ * @param base The class to mix functionality into.
37
+ * @returns The provided class with `WithDelegatesFocus` mixed in.
38
+ */
39
+ export declare function mixinDelegatesFocus<T extends MixinBase<ReactiveElement> & {
40
+ shadowRootOptions: ShadowRootInit;
41
+ }>(base: T): MixinReturn<T & WithDelegatesFocusConstructor, WithDelegatesFocus>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A unique symbol to retrieve the focus element.
3
+ *
4
+ * @example
5
+ *
6
+ * class MyElement extends mixinDelegatesFocus(ReactiveElement) {
7
+ * overrride [getFocusElement](): HTMLElement | null {
8
+ * return this.renderRoot.querySelector('[tabindex="0"]');
9
+ * }
10
+ * }
11
+ *
12
+ */
13
+ const getFocusElement = Symbol('getFocusElement');
14
+ /**
15
+ * The first focusable part is given focus, and the shadow host is given any available :focus styling.
16
+ *
17
+ * @param base The class to mix functionality into.
18
+ * @returns The provided class with `WithDelegatesFocus` mixed in.
19
+ */
20
+ function mixinDelegatesFocus(base) {
21
+ var _a, _b;
22
+ class WithDelegatesFocusElement extends (_b = base) {
23
+ focus(options) {
24
+ const focusElement = this[getFocusElement]();
25
+ if (focusElement && focusElement !== this)
26
+ focusElement.focus(options);
27
+ else
28
+ super.focus(options);
29
+ }
30
+ blur() {
31
+ const focusElement = this[getFocusElement]();
32
+ if (focusElement && focusElement !== this)
33
+ focusElement.blur();
34
+ else
35
+ super.blur();
36
+ }
37
+ [getFocusElement]() {
38
+ return this;
39
+ }
40
+ }
41
+ _a = WithDelegatesFocusElement;
42
+ WithDelegatesFocusElement.shadowRootOptions = {
43
+ ...Reflect.get(_b, "shadowRootOptions", _a),
44
+ delegatesFocus: true,
45
+ };
46
+ return WithDelegatesFocusElement;
47
+ }
48
+
49
+ export { getFocusElement, mixinDelegatesFocus };
@@ -0,0 +1,38 @@
1
+ import { LitElement } from 'lit';
2
+ import { MixinBase, MixinReturn } from '@lookwe/utils';
3
+ /**
4
+ * A unique symbol used for protected access to an instance's `ElementInternals`.
5
+ *
6
+ * @example
7
+ *
8
+ * class MyElement extends mixinElementInternals(LitElement) {
9
+ * constructor() {
10
+ * super();
11
+ * this[internals].role = 'button';
12
+ * }
13
+ * }
14
+ *
15
+ */
16
+ export declare const internals: unique symbol;
17
+ /**
18
+ * An instance with an `internals` symbol property for the component's `ElementInternals`.
19
+ *
20
+ * Use this when protected access is needed for an instance's `ElementInternals`
21
+ * from other files. A unique symbol is used to access the internals.
22
+ */
23
+ export interface WithElementInternals {
24
+ /**
25
+ * An instance's `ElementInternals`.
26
+ */
27
+ readonly [internals]: ElementInternals;
28
+ }
29
+ /**
30
+ * Mixes in an attached `ElementInternals` instance.
31
+ *
32
+ * This mixin is only needed when other shared code needs access to a component's `ElementInternals`, such as
33
+ * form-associated mixins.
34
+ *
35
+ * @param base The class to mix functionality into.
36
+ * @returns The provided class with `WithElementInternals` mixed in.
37
+ */
38
+ export declare function mixinElementInternals<T extends MixinBase<LitElement>>(base: T): MixinReturn<T, WithElementInternals>;
@@ -0,0 +1,45 @@
1
+ import { __classPrivateFieldGet, __classPrivateFieldSet } from 'tslib';
2
+
3
+ /**
4
+ * A unique symbol used for protected access to an instance's `ElementInternals`.
5
+ *
6
+ * @example
7
+ *
8
+ * class MyElement extends mixinElementInternals(LitElement) {
9
+ * constructor() {
10
+ * super();
11
+ * this[internals].role = 'button';
12
+ * }
13
+ * }
14
+ *
15
+ */
16
+ const internals = Symbol('internals');
17
+ /**
18
+ * Mixes in an attached `ElementInternals` instance.
19
+ *
20
+ * This mixin is only needed when other shared code needs access to a component's `ElementInternals`, such as
21
+ * form-associated mixins.
22
+ *
23
+ * @param base The class to mix functionality into.
24
+ * @returns The provided class with `WithElementInternals` mixed in.
25
+ */
26
+ function mixinElementInternals(base) {
27
+ var _WithElementInternalsElement_internals;
28
+ class WithElementInternalsElement extends base {
29
+ constructor() {
30
+ super(...arguments);
31
+ _WithElementInternalsElement_internals.set(this, undefined);
32
+ }
33
+ get [(_WithElementInternalsElement_internals = new WeakMap(), internals)]() {
34
+ // Create internals in getter so that it can be used in methods called on
35
+ // construction in `ReactiveElement`, such as `requestUpdate()`.
36
+ if (!__classPrivateFieldGet(this, _WithElementInternalsElement_internals, "f")) {
37
+ __classPrivateFieldSet(this, _WithElementInternalsElement_internals, this.attachInternals(), "f");
38
+ }
39
+ return __classPrivateFieldGet(this, _WithElementInternalsElement_internals, "f");
40
+ }
41
+ }
42
+ return WithElementInternalsElement;
43
+ }
44
+
45
+ export { internals, mixinElementInternals };
@@ -0,0 +1,176 @@
1
+ import { LitElement } from 'lit';
2
+ import { MixinBase, MixinReturn } from '@lookwe/utils';
3
+ import { WithElementInternals } from './element-internals.js';
4
+ /**
5
+ * A form-associated element.
6
+ *
7
+ * IMPORTANT: Requires declares for lit-analyzer.
8
+ *
9
+ * @example
10
+ *
11
+ * const base = mixinFormAssociated(mixinElementInternals(LitElement));
12
+ * class MyControl extends base {
13
+ * // Writable mixin properties for lit-html binding, needed for lit-analyzer
14
+ * declare disabled: boolean;
15
+ * declare name: string;
16
+ * }
17
+ *
18
+ */
19
+ export interface FormAssociated {
20
+ /**
21
+ * The associated form element with which this element's value will submit.
22
+ */
23
+ readonly form: HTMLFormElement | null;
24
+ /**
25
+ * The labels this element is associated with.
26
+ */
27
+ readonly labels: NodeList;
28
+ /**
29
+ * The HTML name to use in form submission.
30
+ */
31
+ name: string;
32
+ /**
33
+ * Whether or not the element is disabled.
34
+ */
35
+ disabled: boolean;
36
+ /**
37
+ * Gets the current form value of a component.
38
+ *
39
+ * @returns The current form value.
40
+ */
41
+ [getFormValue](): FormValue | null;
42
+ /**
43
+ * Gets the current form state of a component. Defaults to the component's `[formValue]`.
44
+ *
45
+ * Use this when the state of an element is different from its value, such as checkboxes (internal boolean state and a
46
+ * user string value).
47
+ *
48
+ * @returns The current form state, defaults to the form value.
49
+ */
50
+ [getFormState](): FormValue | null;
51
+ /**
52
+ * A callback for when a form component should be disabled or enabled. This can be called in a variety of situations,
53
+ * such as disabled `<fieldset>`s.
54
+ *
55
+ * @param disabled Whether or not the form control should be disabled.
56
+ */
57
+ formDisabledCallback(disabled: boolean): void;
58
+ /**
59
+ * A callback for when the form requests to reset its value. Typically, the default value that is reset is represented
60
+ * in the attribute of an element.
61
+ *
62
+ * This means the attribute used for the value should not update as the value changes. For example,
63
+ * a checkbox should not change its default `checked`
64
+ * attribute when selected. Ensure form values do not reflect.
65
+ */
66
+ formResetCallback(): void;
67
+ /**
68
+ * A callback for when the form restores the state of a component. For example,
69
+ * when a page is reloaded or forms are autofilled.
70
+ *
71
+ * @param state The state to restore, or null to reset the form control's value.
72
+ * @param reason The reason state was restored, either `'restore'` or `'autocomplete'`.
73
+ */
74
+ formStateRestoreCallback(state: FormRestoreState | null, reason: FormRestoreReason): void;
75
+ /**
76
+ * An optional callback for when the associated form changes.
77
+ *
78
+ * @param form The new associated form, or `null` if there is none.
79
+ */
80
+ formAssociatedCallback?(form: HTMLFormElement | null): void;
81
+ }
82
+ /**
83
+ * The constructor of a `FormAssociated` element.
84
+ */
85
+ export interface FormAssociatedConstructor {
86
+ /**
87
+ * Indicates that an element is participating in form association.
88
+ */
89
+ readonly formAssociated: true;
90
+ }
91
+ /**
92
+ * A symbol property to retrieve the form value for an element.
93
+ */
94
+ export declare const getFormValue: unique symbol;
95
+ /**
96
+ * A symbol property to retrieve the form state for an element.
97
+ */
98
+ export declare const getFormState: unique symbol;
99
+ /**
100
+ * Mixes in form-associated behavior for a class. This allows an element to add values to `<form>`
101
+ * elements.
102
+ *
103
+ * Implementing classes should provide a `[formValue]` to return the current value of the element,
104
+ * as well as reset and restore callbacks.
105
+ *
106
+ * @param base The class to mix functionality into. The base class must use `mixinElementInternals()`.
107
+ * @returns The provided class with `FormAssociated` mixed in.
108
+ * @example
109
+ *
110
+ * const base = mixinFormAssociated(mixinElementInternals(LitElement));
111
+ *
112
+ * class MyControl extends base {
113
+ * \@property()
114
+ * accessor value = '';
115
+ *
116
+ * override [getFormValue]() {
117
+ * return this.value;
118
+ * }
119
+ *
120
+ * override formResetCallback() {
121
+ * const defaultValue = this.getAttribute('value');
122
+ * this.value = defaultValue;
123
+ * }
124
+ *
125
+ * override formStateRestoreCallback(state: string) {
126
+ * this.value = state;
127
+ * }
128
+ * }
129
+ *
130
+ * @example
131
+ *
132
+ * // Elements may optionally provide a `[formState]` if their values do not represent the state of the component.
133
+ *
134
+ * const BaseClass = mixinFormAssociated(mixinElementInternals(LitElement));
135
+ *
136
+ * class MyCheckbox extends BaseClass {
137
+ * \@property()
138
+ * accessor value = 'on';
139
+ *
140
+ * \@property({type: Boolean})
141
+ * accessor checked = false;
142
+ *
143
+ * override [getFormValue]() {
144
+ * return this.checked ? this.value : null;
145
+ * }
146
+ *
147
+ * override [getFormState]() {
148
+ * return String(this.checked);
149
+ * }
150
+ *
151
+ * override formResetCallback() {
152
+ * const defaultValue = this.hasAttribute('checked');
153
+ * this.checked = defaultValue;
154
+ * }
155
+ *
156
+ * override formStateRestoreCallback(state: string) {
157
+ * this.checked = Boolean(state);
158
+ * }
159
+ * }
160
+ *
161
+ */
162
+ export declare function mixinFormAssociated<T extends MixinBase<LitElement & WithElementInternals>>(base: T): MixinReturn<T & FormAssociatedConstructor, FormAssociated>;
163
+ /**
164
+ * A value that can be provided for form submission and state.
165
+ */
166
+ export type FormValue = File | string | FormData;
167
+ /**
168
+ * A value to be restored for a component's form value. If a component's form state is a `FormData`
169
+ * object, its entry list of name and values will be provided.
170
+ */
171
+ export type FormRestoreState = File | string | Array<[string, FormDataEntryValue]>;
172
+ /**
173
+ * The reason a form component is being restored for, either `'restore'` for browser restoration or `'autocomplete'` for
174
+ * restoring user values.
175
+ */
176
+ export type FormRestoreReason = 'restore' | 'autocomplete';
@@ -0,0 +1,143 @@
1
+ import { __classPrivateFieldGet, __classPrivateFieldSet, __runInitializers, __esDecorate } from 'tslib';
2
+ import { property } from 'lit/decorators.js';
3
+ import { stringConverter } from '@lookwe/lit-converters';
4
+ import { internals } from './element-internals.js';
5
+
6
+ /**
7
+ * A symbol property to retrieve the form value for an element.
8
+ */
9
+ const getFormValue = Symbol('getFormValue');
10
+ /**
11
+ * A symbol property to retrieve the form state for an element.
12
+ */
13
+ const getFormState = Symbol('getFormState');
14
+ /**
15
+ * Mixes in form-associated behavior for a class. This allows an element to add values to `<form>`
16
+ * elements.
17
+ *
18
+ * Implementing classes should provide a `[formValue]` to return the current value of the element,
19
+ * as well as reset and restore callbacks.
20
+ *
21
+ * @param base The class to mix functionality into. The base class must use `mixinElementInternals()`.
22
+ * @returns The provided class with `FormAssociated` mixed in.
23
+ * @example
24
+ *
25
+ * const base = mixinFormAssociated(mixinElementInternals(LitElement));
26
+ *
27
+ * class MyControl extends base {
28
+ * \@property()
29
+ * accessor value = '';
30
+ *
31
+ * override [getFormValue]() {
32
+ * return this.value;
33
+ * }
34
+ *
35
+ * override formResetCallback() {
36
+ * const defaultValue = this.getAttribute('value');
37
+ * this.value = defaultValue;
38
+ * }
39
+ *
40
+ * override formStateRestoreCallback(state: string) {
41
+ * this.value = state;
42
+ * }
43
+ * }
44
+ *
45
+ * @example
46
+ *
47
+ * // Elements may optionally provide a `[formState]` if their values do not represent the state of the component.
48
+ *
49
+ * const BaseClass = mixinFormAssociated(mixinElementInternals(LitElement));
50
+ *
51
+ * class MyCheckbox extends BaseClass {
52
+ * \@property()
53
+ * accessor value = 'on';
54
+ *
55
+ * \@property({type: Boolean})
56
+ * accessor checked = false;
57
+ *
58
+ * override [getFormValue]() {
59
+ * return this.checked ? this.value : null;
60
+ * }
61
+ *
62
+ * override [getFormState]() {
63
+ * return String(this.checked);
64
+ * }
65
+ *
66
+ * override formResetCallback() {
67
+ * const defaultValue = this.hasAttribute('checked');
68
+ * this.checked = defaultValue;
69
+ * }
70
+ *
71
+ * override formStateRestoreCallback(state: string) {
72
+ * this.checked = Boolean(state);
73
+ * }
74
+ * }
75
+ *
76
+ */
77
+ function mixinFormAssociated(base) {
78
+ let FormAssociatedElement = (() => {
79
+ var _FormAssociatedElement_instances, _a, _FormAssociatedElement_updateFormValue, _FormAssociatedElement_name_accessor_storage, _FormAssociatedElement_disabled_accessor_storage;
80
+ let _classSuper = base;
81
+ let _name_decorators;
82
+ let _name_initializers = [];
83
+ let _name_extraInitializers = [];
84
+ let _disabled_decorators;
85
+ let _disabled_initializers = [];
86
+ let _disabled_extraInitializers = [];
87
+ return _a = class FormAssociatedElement extends _classSuper {
88
+ get form() {
89
+ return this[internals].form;
90
+ }
91
+ get labels() {
92
+ return this[internals].labels;
93
+ }
94
+ get name() { return __classPrivateFieldGet(this, _FormAssociatedElement_name_accessor_storage, "f"); }
95
+ set name(value) { __classPrivateFieldSet(this, _FormAssociatedElement_name_accessor_storage, value, "f"); }
96
+ get disabled() { return __classPrivateFieldGet(this, _FormAssociatedElement_disabled_accessor_storage, "f"); }
97
+ set disabled(value) { __classPrivateFieldSet(this, _FormAssociatedElement_disabled_accessor_storage, value, "f"); }
98
+ requestUpdate(name, oldValue, options) {
99
+ super.requestUpdate(name, oldValue, options);
100
+ // If any properties change, update the form value, which may have changed
101
+ // as well.
102
+ // Update the form value synchronously in `requestUpdate()` rather than
103
+ // `update()` or `updated()`, which are async. This is necessary to ensure
104
+ // that form data is updated in time for synchronous event listeners.
105
+ if (!name)
106
+ return;
107
+ __classPrivateFieldGet(this, _FormAssociatedElement_instances, "m", _FormAssociatedElement_updateFormValue).call(this);
108
+ }
109
+ connectedCallback() {
110
+ super.connectedCallback();
111
+ __classPrivateFieldGet(this, _FormAssociatedElement_instances, "m", _FormAssociatedElement_updateFormValue).call(this);
112
+ }
113
+ [(_FormAssociatedElement_instances = new WeakSet(), _FormAssociatedElement_name_accessor_storage = new WeakMap(), _FormAssociatedElement_disabled_accessor_storage = new WeakMap(), _FormAssociatedElement_updateFormValue = function _FormAssociatedElement_updateFormValue() {
114
+ if (!this.isConnected)
115
+ return;
116
+ this[internals].setFormValue(this[getFormValue](), this[getFormState]());
117
+ }, _name_decorators = [property({ reflect: true, converter: stringConverter })], _disabled_decorators = [property({ type: Boolean })], getFormState)]() {
118
+ return this[getFormValue]();
119
+ }
120
+ formDisabledCallback(disabled) {
121
+ this.disabled = disabled;
122
+ }
123
+ constructor() {
124
+ super(...arguments);
125
+ _FormAssociatedElement_instances.add(this);
126
+ _FormAssociatedElement_name_accessor_storage.set(this, __runInitializers(this, _name_initializers, ''));
127
+ _FormAssociatedElement_disabled_accessor_storage.set(this, (__runInitializers(this, _name_extraInitializers), __runInitializers(this, _disabled_initializers, false)));
128
+ __runInitializers(this, _disabled_extraInitializers);
129
+ }
130
+ },
131
+ (() => {
132
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : undefined;
133
+ __esDecorate(_a, null, _name_decorators, { kind: "accessor", name: "name", static: false, private: false, access: { has: obj => "name" in obj, get: obj => obj.name, set: (obj, value) => { obj.name = value; } }, metadata: _metadata }, _name_initializers, _name_extraInitializers);
134
+ __esDecorate(_a, null, _disabled_decorators, { kind: "accessor", name: "disabled", static: false, private: false, access: { has: obj => "disabled" in obj, get: obj => obj.disabled, set: (obj, value) => { obj.disabled = value; } }, metadata: _metadata }, _disabled_initializers, _disabled_extraInitializers);
135
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
136
+ })(),
137
+ _a.formAssociated = true,
138
+ _a;
139
+ })();
140
+ return FormAssociatedElement;
141
+ }
142
+
143
+ export { getFormState, getFormValue, mixinFormAssociated };
@@ -0,0 +1,46 @@
1
+ import { ReactiveElement } from 'lit';
2
+ import { MixinBase, MixinReturn } from '@lookwe/utils';
3
+ /**
4
+ * An instance with all the useful properties to use the [Popover
5
+ * api](https://developer.mozilla.org/docs/Web/API/Popover_API).
6
+ */
7
+ export interface WithPopoverTarget {
8
+ /**
9
+ * Defines id to retrieve the controlled popover element.
10
+ *
11
+ * https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/popoverTargetElement
12
+ */
13
+ popoverTarget: string;
14
+ /**
15
+ * Defines the controlled popover element.
16
+ *
17
+ * https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/popoverTargetElement
18
+ */
19
+ popoverTargetElement: Element | null;
20
+ /**
21
+ * Defines the action to be performed ("hide", "show", or "toggle") on a controlled popover element.
22
+ *
23
+ * https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/popoverTargetAction
24
+ */
25
+ popoverTargetAction: 'hide' | 'show' | 'toggle';
26
+ }
27
+ /**
28
+ * Mixes in popovertarget behavior for a class. This allows an element to control an external popover element.
29
+ *
30
+ * @param base The class to mix functionality into.
31
+ * @returns The provided class with `WithPopoverTarget` mixed in.
32
+ * @example
33
+ *
34
+ * class MyButton extends mixinPopoverTarget(LitElement) {
35
+ * render() {
36
+ * return html`
37
+ * <button
38
+ * .popoverTargetElement=${this.popoverTargetElement}
39
+ * .popoverTargetAction=${this.popoverTargetAction}
40
+ * ></button>
41
+ * `;
42
+ * }
43
+ * }
44
+ *
45
+ */
46
+ export declare function mixinPopoverTarget<T extends MixinBase<ReactiveElement>>(base: T): MixinReturn<T, WithPopoverTarget>;
@@ -0,0 +1,83 @@
1
+ import { __esDecorate, __classPrivateFieldGet, __classPrivateFieldSet, __runInitializers } from 'tslib';
2
+ import { property } from 'lit/decorators.js';
3
+ import { stringConverter } from '@lookwe/lit-converters';
4
+
5
+ /**
6
+ * Mixes in popovertarget behavior for a class. This allows an element to control an external popover element.
7
+ *
8
+ * @param base The class to mix functionality into.
9
+ * @returns The provided class with `WithPopoverTarget` mixed in.
10
+ * @example
11
+ *
12
+ * class MyButton extends mixinPopoverTarget(LitElement) {
13
+ * render() {
14
+ * return html`
15
+ * <button
16
+ * .popoverTargetElement=${this.popoverTargetElement}
17
+ * .popoverTargetAction=${this.popoverTargetAction}
18
+ * ></button>
19
+ * `;
20
+ * }
21
+ * }
22
+ *
23
+ */
24
+ function mixinPopoverTarget(base) {
25
+ let WithPopoverTargetElement = (() => {
26
+ var _WithPopoverTargetElement_instances, _a, _WithPopoverTargetElement_popoverTargetElement, _WithPopoverTargetElement_getPopoverTargetElement, _WithPopoverTargetElement_popoverTarget_accessor_storage, _WithPopoverTargetElement_popoverTargetAction_accessor_storage;
27
+ let _classSuper = base;
28
+ let _instanceExtraInitializers = [];
29
+ let _popoverTarget_decorators;
30
+ let _popoverTarget_initializers = [];
31
+ let _popoverTarget_extraInitializers = [];
32
+ let _set_popoverTargetElement_decorators;
33
+ let _popoverTargetAction_decorators;
34
+ let _popoverTargetAction_initializers = [];
35
+ let _popoverTargetAction_extraInitializers = [];
36
+ return _a = class WithPopoverTargetElement extends _classSuper {
37
+ get popoverTarget() { return __classPrivateFieldGet(this, _WithPopoverTargetElement_popoverTarget_accessor_storage, "f"); }
38
+ set popoverTarget(value) { __classPrivateFieldSet(this, _WithPopoverTargetElement_popoverTarget_accessor_storage, value, "f"); }
39
+ set popoverTargetElement(popoverTargetElement) {
40
+ __classPrivateFieldSet(this, _WithPopoverTargetElement_popoverTargetElement, popoverTargetElement, "f");
41
+ }
42
+ get popoverTargetElement() {
43
+ if (__classPrivateFieldGet(this, _WithPopoverTargetElement_popoverTargetElement, "f") && __classPrivateFieldGet(this, _WithPopoverTargetElement_popoverTargetElement, "f").isConnected)
44
+ return __classPrivateFieldGet(this, _WithPopoverTargetElement_popoverTargetElement, "f");
45
+ return (__classPrivateFieldSet(this, _WithPopoverTargetElement_popoverTargetElement, __classPrivateFieldGet(this, _WithPopoverTargetElement_instances, "m", _WithPopoverTargetElement_getPopoverTargetElement).call(this), "f"));
46
+ }
47
+ get popoverTargetAction() { return __classPrivateFieldGet(this, _WithPopoverTargetElement_popoverTargetAction_accessor_storage, "f"); }
48
+ set popoverTargetAction(value) { __classPrivateFieldSet(this, _WithPopoverTargetElement_popoverTargetAction_accessor_storage, value, "f"); }
49
+ constructor() {
50
+ super(...arguments);
51
+ _WithPopoverTargetElement_instances.add(this);
52
+ _WithPopoverTargetElement_popoverTarget_accessor_storage.set(this, (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _popoverTarget_initializers, '')));
53
+ _WithPopoverTargetElement_popoverTargetElement.set(this, (__runInitializers(this, _popoverTarget_extraInitializers), null));
54
+ _WithPopoverTargetElement_popoverTargetAction_accessor_storage.set(this, __runInitializers(this, _popoverTargetAction_initializers, 'toggle'));
55
+ __runInitializers(this, _popoverTargetAction_extraInitializers);
56
+ }
57
+ },
58
+ _WithPopoverTargetElement_popoverTargetElement = new WeakMap(),
59
+ _WithPopoverTargetElement_instances = new WeakSet(),
60
+ _WithPopoverTargetElement_popoverTarget_accessor_storage = new WeakMap(),
61
+ _WithPopoverTargetElement_popoverTargetAction_accessor_storage = new WeakMap(),
62
+ _WithPopoverTargetElement_getPopoverTargetElement = function _WithPopoverTargetElement_getPopoverTargetElement() {
63
+ if (!this.popoverTarget)
64
+ return null;
65
+ const root = this.getRootNode();
66
+ return root.querySelector(`#${CSS.escape(this.popoverTarget)}`);
67
+ },
68
+ (() => {
69
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : undefined;
70
+ _popoverTarget_decorators = [property({ reflect: true, attribute: 'popovertarget', converter: stringConverter })];
71
+ _set_popoverTargetElement_decorators = [property({ attribute: false })];
72
+ _popoverTargetAction_decorators = [property({ attribute: 'popovertargetaction' })];
73
+ __esDecorate(_a, null, _popoverTarget_decorators, { kind: "accessor", name: "popoverTarget", static: false, private: false, access: { has: obj => "popoverTarget" in obj, get: obj => obj.popoverTarget, set: (obj, value) => { obj.popoverTarget = value; } }, metadata: _metadata }, _popoverTarget_initializers, _popoverTarget_extraInitializers);
74
+ __esDecorate(_a, null, _set_popoverTargetElement_decorators, { kind: "setter", name: "popoverTargetElement", static: false, private: false, access: { has: obj => "popoverTargetElement" in obj, set: (obj, value) => { obj.popoverTargetElement = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
75
+ __esDecorate(_a, null, _popoverTargetAction_decorators, { kind: "accessor", name: "popoverTargetAction", static: false, private: false, access: { has: obj => "popoverTargetAction" in obj, get: obj => obj.popoverTargetAction, set: (obj, value) => { obj.popoverTargetAction = value; } }, metadata: _metadata }, _popoverTargetAction_initializers, _popoverTargetAction_extraInitializers);
76
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
77
+ })(),
78
+ _a;
79
+ })();
80
+ return WithPopoverTargetElement;
81
+ }
82
+
83
+ export { mixinPopoverTarget };
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@lookwe/lit-mixins",
3
+ "description": "Some lit mixins",
4
+ "homepage": "https://github.com/Lookwe69/lit-mixins",
5
+ "bugs": {
6
+ "url": "https://github.com/Lookwe69/lit-mixins/issues"
7
+ },
8
+ "keywords": [
9
+ "lui mixin",
10
+ "web-components mixin",
11
+ "typescript",
12
+ "lit mixin",
13
+ "lit element internals",
14
+ "lit popover target"
15
+ ],
16
+ "author": "Lookwe",
17
+ "version": "1.0.0",
18
+ "type": "module",
19
+ "scripts": {
20
+ "build": "rollup --config rollup-dist.config.js",
21
+ "build:watch": "npm run build -- --watch",
22
+ "build:prepack": "rollup --config rollup-prepack.config.js",
23
+ "clean": "rm -rf dist && rm -rf coverage && rm -rf internal",
24
+ "lint": "npm run lint:eslint && npm run lint:ts",
25
+ "lint:eslint": "eslint 'src/**/*.ts'",
26
+ "lint:ts": "tsc --noEmit",
27
+ "format": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore .",
28
+ "format:fix": "npm run format -- --write && npm run format -- --write --plugin=@homer0/prettier-plugin-jsdoc",
29
+ "test": "npm run build && wtr && MODE=prod wtr",
30
+ "test:dev": "npm run build && wtr",
31
+ "test:dev:watch": "npm run build && wtr --watch",
32
+ "test:prod": "MODE=prod wtr",
33
+ "test:prod:watch": "npm run build && MODE=prod wtr --watch",
34
+ "test:coverage": "npm run test -- --coverage",
35
+ "prepare-pack": "npm run clean && npm run build:prepack",
36
+ "prepack": "npm run prepare-pack",
37
+ "semantic-release": "semantic-release"
38
+ },
39
+ "files": [
40
+ "index.js",
41
+ "index.d.ts",
42
+ "internal"
43
+ ],
44
+ "main": "index.js",
45
+ "module": "index.js",
46
+ "types": "index.d.ts",
47
+ "exports": {
48
+ ".": "./index.js",
49
+ "./delegate-focus": "./internal/delegate-focus.js",
50
+ "./element-internals": "./internal/element-internals.js",
51
+ "./form-associated": "./internal/form-associated.js",
52
+ "./popover-target": "./internal/popover-target.js"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "dependencies": {
58
+ "@lookwe/lit-converters": "^1.0.1",
59
+ "@lookwe/utils": "^1.1.0",
60
+ "lit": "^3.2.1",
61
+ "tslib": "^2.8.0"
62
+ },
63
+ "devDependencies": {
64
+ "@homer0/prettier-plugin-jsdoc": "^9.1.0",
65
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
66
+ "@open-wc/testing": "^4.0.0",
67
+ "@rollup/plugin-node-resolve": "^16.0.0",
68
+ "@rollup/plugin-replace": "^6.0.2",
69
+ "@rollup/plugin-typescript": "^12.1.2",
70
+ "@types/mocha": "^10.0.10",
71
+ "@web/dev-server-rollup": "^0.6.4",
72
+ "@web/test-runner": "^0.17.3",
73
+ "@web/test-runner-commands": "^0.9.0",
74
+ "@web/test-runner-playwright": "^0.11.0",
75
+ "eslint": "^9.18.0",
76
+ "globals": "^15.14.0",
77
+ "prettier": "^3.4.2",
78
+ "rollup": "^4.31.0",
79
+ "rollup-plugin-multi-input": "^1.5.0",
80
+ "semantic-release": "^24.2.1",
81
+ "sinon": "^19.0.2",
82
+ "typescript": "^5.7.3",
83
+ "typescript-eslint": "^8.21.0"
84
+ }
85
+ }