@digital-realty/ix-select 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/{src/IxSelect.d.ts → IxSelect.d.ts} +1 -0
  2. package/dist/{src/IxSelect.js → IxSelect.js} +8 -1
  3. package/dist/IxSelect.js.map +1 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/ix-select.js.map +1 -0
  6. package/dist/react/IxSelect.js.map +1 -0
  7. package/dist/react/IxSelectOption.js.map +1 -0
  8. package/dist/selectoption/IxSelectOption.js.map +1 -0
  9. package/dist/selectoption/ix-select-option.js.map +1 -0
  10. package/dist/{src/selectoption → selectoption}/selectOptionController.d.ts +1 -1
  11. package/dist/selectoption/selectOptionController.js.map +1 -0
  12. package/package.json +16 -9
  13. package/demo/index.html +0 -58
  14. package/dist/src/IxSelect.js.map +0 -1
  15. package/dist/src/index.js.map +0 -1
  16. package/dist/src/ix-select.js.map +0 -1
  17. package/dist/src/react/IxSelect.js.map +0 -1
  18. package/dist/src/react/IxSelectOption.js.map +0 -1
  19. package/dist/src/selectoption/IxSelectOption.js.map +0 -1
  20. package/dist/src/selectoption/ix-select-option.js.map +0 -1
  21. package/dist/src/selectoption/selectOptionController.js.map +0 -1
  22. package/dist/test/ix-select.test.d.ts +0 -1
  23. package/dist/test/ix-select.test.js +0 -58
  24. package/dist/test/ix-select.test.js.map +0 -1
  25. package/dist/tsconfig.tsbuildinfo +0 -1
  26. package/src/IxSelect.ts +0 -218
  27. package/src/index.ts +0 -2
  28. package/src/ix-select.ts +0 -3
  29. package/src/react/IxSelect.ts +0 -14
  30. package/src/react/IxSelectOption.ts +0 -14
  31. package/src/selectoption/IxSelectOption.ts +0 -192
  32. package/src/selectoption/ix-select-option.ts +0 -3
  33. package/src/selectoption/selectOptionController.ts +0 -179
  34. package/test/ix-select.test.ts +0 -79
  35. package/tsconfig.json +0 -21
  36. package/web-dev-server.config.mjs +0 -27
  37. package/web-test-runner.config.mjs +0 -43
  38. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  39. /package/dist/{src/index.js → index.js} +0 -0
  40. /package/dist/{src/ix-select.d.ts → ix-select.d.ts} +0 -0
  41. /package/dist/{src/ix-select.js → ix-select.js} +0 -0
  42. /package/dist/{src/react → react}/IxSelect.d.ts +0 -0
  43. /package/dist/{src/react → react}/IxSelect.js +0 -0
  44. /package/dist/{src/react → react}/IxSelectOption.d.ts +0 -0
  45. /package/dist/{src/react → react}/IxSelectOption.js +0 -0
  46. /package/dist/{src/selectoption → selectoption}/IxSelectOption.d.ts +0 -0
  47. /package/dist/{src/selectoption → selectoption}/IxSelectOption.js +0 -0
  48. /package/dist/{src/selectoption → selectoption}/ix-select-option.d.ts +0 -0
  49. /package/dist/{src/selectoption → selectoption}/ix-select-option.js +0 -0
  50. /package/dist/{src/selectoption → selectoption}/selectOptionController.js +0 -0
package/src/IxSelect.ts DELETED
@@ -1,218 +0,0 @@
1
- import { html, LitElement, nothing, PropertyValues } from 'lit';
2
- import { property, query, state } from 'lit/decorators.js';
3
- import { Select } from '@material/web/select/internal/select.js';
4
- import { Field } from '@material/web/field/internal/field';
5
- import { ARIAMixinStrict } from '@material/web/internal/aria/aria.js';
6
- import '@material/web/select/filled-select.js';
7
- import '@material/web/select/outlined-select.js';
8
- import './selectoption/ix-select-option.js';
9
-
10
- export const DEFAULT_TYPEAHEAD_BUFFER_TIME = 200;
11
-
12
- export class IxSelect extends LitElement {
13
- /** @nocollapse */
14
- static readonly formAssociated = true;
15
-
16
- @query('.field') private readonly field!: Field | null;
17
-
18
- @query('slot') slotEl!: HTMLSlotElement;
19
-
20
- @query('md-outlined-select') default!: Select;
21
-
22
- @property() appearance: 'filled' | 'outlined' = 'outlined';
23
-
24
- @property() label: String = '';
25
-
26
- @property({ type: Boolean }) quick = false;
27
-
28
- @property({ type: Boolean }) required = false;
29
-
30
- @property({ type: Boolean, reflect: true }) disabled = false;
31
-
32
- @property({ type: String, attribute: 'error-text' }) errorText = '';
33
-
34
- @property({ type: String, attribute: 'supporting-text' }) supportingText = '';
35
-
36
- @property({ type: Boolean, reflect: true }) error = false;
37
-
38
- @property({ type: String, attribute: 'display-text' }) displayText = '';
39
-
40
- /**
41
- * Whether or not the underlying md-menu should be position: fixed to display
42
- * in a top-level manner, or position: absolute.
43
- *
44
- * position:fixed is useful for cases where select is inside of another
45
- * element with stacking context and hidden overflows such as `md-dialog`.
46
- */
47
- @property({ attribute: 'menu-positioning' })
48
- menuPositioning: 'absolute' | 'fixed' = 'absolute';
49
-
50
- /**
51
- * The max time between the keystrokes of the typeahead select / menu behavior
52
- * before it clears the typeahead buffer.
53
- */
54
- @property({ type: Number, attribute: 'typeahead-delay' })
55
- typeaheadDelay = DEFAULT_TYPEAHEAD_BUFFER_TIME;
56
-
57
- @state() private nativeError = false;
58
-
59
- @state() private nativeErrorText = '';
60
-
61
- private get hasError() {
62
- return this.error || this.nativeError;
63
- }
64
-
65
- private readonly internals = (this as HTMLElement) /* needed for closure */
66
- .attachInternals();
67
-
68
- private customValidationMessage = '';
69
-
70
- get form() {
71
- return this.internals.form;
72
- }
73
-
74
- get labels() {
75
- return this.internals.labels;
76
- }
77
-
78
- get name() {
79
- return this.getAttribute('name') ?? '';
80
- }
81
-
82
- set name(name: string) {
83
- this.setAttribute('name', name);
84
- }
85
-
86
- get validity() {
87
- this.syncValidity();
88
- return this.internals.validity;
89
- }
90
-
91
- get value() {
92
- return this.materialRoot.value;
93
- }
94
-
95
- get validationMessage() {
96
- this.syncValidity();
97
- return this.internals.validationMessage;
98
- }
99
-
100
- get materialRoot(): Select {
101
- return this.shadowRoot?.querySelector('md-filled-select') ?? this.default;
102
- }
103
-
104
- firstUpdated() {
105
- this.addEventListener('request-selection', () => {
106
- this.internals.setFormValue(this.materialRoot.value);
107
- });
108
- }
109
-
110
- protected override updated(changed: PropertyValues<Select>) {
111
- if (changed.has('required')) {
112
- this.syncValidity();
113
- }
114
- }
115
-
116
- // eslint-disable-next-line consistent-return
117
- render() {
118
- if (this.appearance === 'outlined') {
119
- return html` <md-outlined-select
120
- .aria-label=${(this as ARIAMixinStrict).ariaLabel || nothing}
121
- .tabindex=${this.disabled ? '-1' : '0'}
122
- .label=${this.label}
123
- .populated=${!!this.displayText}
124
- .disabled=${this.disabled}
125
- .quick=${this.quick}
126
- .menu-positioning=${this.menuPositioning}
127
- .typeaheadDelay=${this.typeaheadDelay}
128
- .error=${this.hasError}
129
- .name=${this.name}
130
- .required=${this.required}
131
- supporting-text=${this.supportingText}
132
- error-text=${this.getErrorText()}
133
- >
134
- <slot></slot>
135
- </md-outlined-select>`;
136
- }
137
- if (this.appearance === 'filled') {
138
- return html`
139
- <md-filled-select
140
- .aria-label=${(this as ARIAMixinStrict).ariaLabel || nothing}
141
- .tabindex=${this.disabled ? '-1' : '0'}
142
- .label=${this.label}
143
- .populated=${!!this.displayText}
144
- .disabled=${this.disabled}
145
- .quick=${this.quick}
146
- .positioning=${this.menuPositioning}
147
- .typeaheadDelay=${this.typeaheadDelay}
148
- .required=${this.required}
149
- .error=${this.hasError}
150
- supporting-text=${this.supportingText}
151
- error-text=${this.getErrorText()}
152
- >
153
- <slot></slot>
154
- </md-outlined-select>`;
155
- }
156
- }
157
-
158
- private getErrorText() {
159
- return this.error ? this.errorText : this.nativeErrorText;
160
- }
161
-
162
- checkValidity() {
163
- this.syncValidity();
164
- return this.internals.checkValidity();
165
- }
166
-
167
- reportValidity() {
168
- let invalidEvent: Event | undefined;
169
- this.addEventListener(
170
- 'invalid',
171
- event => {
172
- invalidEvent = event;
173
- },
174
- { once: true }
175
- );
176
-
177
- const valid = this.checkValidity();
178
- if (invalidEvent?.defaultPrevented) {
179
- return valid;
180
- }
181
-
182
- const prevMessage = this.getErrorText();
183
- this.nativeError = !valid;
184
- this.nativeErrorText = this.validationMessage;
185
-
186
- if (prevMessage === this.getErrorText()) {
187
- this.field?.reannounceError();
188
- }
189
-
190
- return valid;
191
- }
192
-
193
- setCustomValidity(error: string) {
194
- this.customValidationMessage = error;
195
- this.syncValidity();
196
- }
197
-
198
- private syncValidity() {
199
- const valueMissing = this.required && !this.materialRoot.value;
200
- const customError = !!this.customValidationMessage;
201
- const validationMessage =
202
- this.customValidationMessage ||
203
- (valueMissing && this.getRequiredValidationMessage()) ||
204
- '';
205
-
206
- this.internals.setValidity(
207
- { valueMissing, customError },
208
- validationMessage
209
- );
210
- }
211
-
212
- // eslint-disable-next-line class-methods-use-this
213
- private getRequiredValidationMessage() {
214
- const select = document.createElement('select');
215
- select.required = true;
216
- return select.validationMessage;
217
- }
218
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { IxSelect } from './IxSelect.js';
2
- export { IxSelectOption } from './selectoption/IxSelectOption.js';
package/src/ix-select.ts DELETED
@@ -1,3 +0,0 @@
1
- import { IxSelect } from './IxSelect.js';
2
-
3
- window.customElements.define('ix-select', IxSelect);
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import { createComponent } from '@lit-labs/react';
3
- import { IxSelect as IxSelectLit } from '../IxSelect.js';
4
-
5
- window.customElements.define('ix-select', IxSelectLit);
6
-
7
- export const IxSelect = createComponent({
8
- tagName: 'ix-select',
9
- elementClass: IxSelectLit,
10
- react: React,
11
- events: {
12
- onclick: 'onClick',
13
- },
14
- });
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import { createComponent } from '@lit-labs/react';
3
- import { IxSelectOption as IxSelectOptionLit } from '../selectoption/IxSelectOption.js';
4
-
5
- window.customElements.define('ix-select', IxSelectOptionLit);
6
-
7
- export const IxSelectOption = createComponent({
8
- tagName: 'ix-select',
9
- elementClass: IxSelectOptionLit,
10
- react: React,
11
- events: {
12
- onclick: 'onClick',
13
- },
14
- });
@@ -1,192 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2023 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import '@material/web/ripple/ripple.js';
8
- import '@material/web/focus/md-focus-ring.js';
9
- import '@material/web/labs/item/item.js';
10
-
11
- import { html, LitElement, nothing } from 'lit';
12
- import { property, query, queryAssignedElements } from 'lit/decorators.js';
13
- import { ClassInfo, classMap } from 'lit/directives/class-map.js';
14
-
15
- import { ARIAMixinStrict } from '@material/web/internal/aria/aria.js';
16
- import { requestUpdateOnAriaChange } from '@material/web/internal/aria/delegate.js';
17
-
18
- import {
19
- SelectOption,
20
- SelectOptionController,
21
- } from './selectOptionController.js';
22
-
23
- /**
24
- * @fires close-menu Closes the encapsulating menu on
25
- * @fires request-selection Requests the parent md-select to select this element
26
- * (and deselect others if single-selection) when `selected` changed to `true`.
27
- * @fires request-deselection Requests the parent md-select to deselect this
28
- * element when `selected` changed to `false`.
29
- */
30
- export class IxSelectOption extends LitElement implements SelectOption {
31
- static {
32
- requestUpdateOnAriaChange(IxSelectOption);
33
- }
34
-
35
- /** @nocollapse */
36
- static override shadowRootOptions = {
37
- ...LitElement.shadowRootOptions,
38
- delegatesFocus: true,
39
- };
40
-
41
- /**
42
- * Disables the item and makes it non-selectable and non-interactive.
43
- */
44
- @property({ type: Boolean, reflect: true }) disabled = false;
45
-
46
- /**
47
- * READONLY: self-identifies as a menu item and sets its identifying attribute
48
- */
49
- @property({ type: Boolean, attribute: 'md-menu-item', reflect: true })
50
- isMenuItem = true;
51
-
52
- /**
53
- * Sets the item in the selected visual state when a submenu is opened.
54
- */
55
- @property({ type: Boolean }) selected = false;
56
-
57
- /**
58
- * Form value of the option.
59
- */
60
- @property() value = '';
61
-
62
- @query('.list-item') protected readonly listItemRoot!: HTMLElement | null;
63
-
64
- @queryAssignedElements({ slot: 'headline' })
65
- protected readonly headlineElements!: HTMLElement[];
66
-
67
- type = 'option' as const;
68
-
69
- /**
70
- * The text that is selectable via typeahead. If not set, defaults to the
71
- * innerText of the item slotted into the `"headline"` slot.
72
- */
73
- get typeaheadText() {
74
- return this.selectOptionController.typeaheadText;
75
- }
76
-
77
- @property({ attribute: 'typeahead-text' })
78
- set typeaheadText(text: string) {
79
- this.selectOptionController.setTypeaheadText(text);
80
- }
81
-
82
- /**
83
- * The text that is displayed in the select field when selected. If not set,
84
- * defaults to the textContent of the item slotted into the `"headline"` slot.
85
- */
86
- get displayText() {
87
- return this.selectOptionController.displayText;
88
- }
89
-
90
- @property({ attribute: 'display-text' })
91
- set displayText(text: string) {
92
- this.selectOptionController.setDisplayText(text);
93
- }
94
-
95
- private readonly selectOptionController = new SelectOptionController(this, {
96
- getHeadlineElements: () => this.headlineElements,
97
- });
98
-
99
- protected override render() {
100
- return this.renderListItem(html`
101
- <md-item>
102
- <div slot="container">
103
- ${this.renderRipple()} ${this.renderFocusRing()}
104
- </div>
105
- <slot name="start" slot="start"></slot>
106
- <slot name="end" slot="end"></slot>
107
- ${this.renderBody()}
108
- </md-item>
109
- `);
110
- }
111
-
112
- /**
113
- * Renders the root list item.
114
- *
115
- * @param content the child content of the list item.
116
- */
117
- protected renderListItem(content: unknown) {
118
- return html`
119
- <li
120
- id="item"
121
- tabindex=${this.disabled ? -1 : 0}
122
- role=${this.selectOptionController.role}
123
- aria-label=${(this as ARIAMixinStrict).ariaLabel || nothing}
124
- aria-selected=${(this as ARIAMixinStrict).ariaSelected || nothing}
125
- aria-checked=${(this as ARIAMixinStrict).ariaChecked || nothing}
126
- aria-expanded=${(this as ARIAMixinStrict).ariaExpanded || nothing}
127
- aria-haspopup=${(this as ARIAMixinStrict).ariaHasPopup || nothing}
128
- class="list-item ${classMap(this.getRenderClasses())}"
129
- @click=${this.selectOptionController.onClick}
130
- @keydown=${this.selectOptionController.onKeydown}
131
- >
132
- ${content}
133
- </li>
134
- `;
135
- }
136
-
137
- /**
138
- * Handles rendering of the ripple element.
139
- */
140
- protected renderRipple() {
141
- return html` <md-ripple
142
- part="ripple"
143
- for="item"
144
- ?disabled=${this.disabled}
145
- ></md-ripple>`;
146
- }
147
-
148
- /**
149
- * Handles rendering of the focus ring.
150
- */
151
- // eslint-disable-next-line class-methods-use-this
152
- protected renderFocusRing() {
153
- return html` <md-focus-ring
154
- part="focus-ring"
155
- for="item"
156
- inward
157
- ></md-focus-ring>`;
158
- }
159
-
160
- /**
161
- * Classes applied to the list item root.
162
- */
163
- protected getRenderClasses(): ClassInfo {
164
- return {
165
- disabled: this.disabled,
166
- selected: this.selected,
167
- };
168
- }
169
-
170
- /**
171
- * Handles rendering the headline and supporting text.
172
- */
173
- // eslint-disable-next-line class-methods-use-this
174
- protected renderBody() {
175
- return html`
176
- <slot></slot>
177
- <slot name="overline" slot="overline"></slot>
178
- <slot name="headline" slot="headline"></slot>
179
- <slot name="supporting-text" slot="supporting-text"></slot>
180
- <slot
181
- name="trailing-supporting-text"
182
- slot="trailing-supporting-text"
183
- ></slot>
184
- `;
185
- }
186
-
187
- override focus() {
188
- // TODO(b/300334509): needed for some cases where delegatesFocus doesn't
189
- // work programmatically like in FF and select-option
190
- this.listItemRoot?.focus();
191
- }
192
- }
@@ -1,3 +0,0 @@
1
- import { IxSelectOption } from './IxSelectOption.js';
2
-
3
- window.customElements.define('ix-select-option', IxSelectOption);
@@ -1,179 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2023 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import { ReactiveController, ReactiveControllerHost } from 'lit';
8
-
9
- import {
10
- MenuItem,
11
- MenuItemController,
12
- MenuItemControllerConfig,
13
- } from '@material/web/menu/internal/controllers/menuItemController.js';
14
-
15
- /**
16
- * The interface specific to a Select Option
17
- */
18
- interface SelectOptionSelf {
19
- /**
20
- * The form value associated with the Select Option. (Note: the visual portion
21
- * of the SelectOption is the headline defined in ListItem)
22
- */
23
- value: string;
24
- /**
25
- * Whether or not the SelectOption is selected.
26
- */
27
- selected: boolean;
28
- /**
29
- * The text to display in the select when selected. Defaults to the
30
- * textContent of the Element slotted into the headline.
31
- */
32
- displayText: string;
33
- }
34
-
35
- /**
36
- * The interface to implement for a select option. Additionally, the element
37
- * must have `md-list-item` and `md-menu-item` attributes on the host.
38
- */
39
- export type SelectOption = SelectOptionSelf & MenuItem;
40
-
41
- /**
42
- * Creates an event fired by a SelectOption to request selection from md-select.
43
- * Typically fired after `selected` changes from `false` to `true`.
44
- */
45
- export function createRequestSelectionEvent() {
46
- return new Event('request-selection', {
47
- bubbles: true,
48
- composed: true,
49
- });
50
- }
51
-
52
- /**
53
- * Creates an event fired by a SelectOption to request deselection from
54
- * md-select. Typically fired after `selected` changes from `true` to `false`.
55
- */
56
- export function createRequestDeselectionEvent() {
57
- return new Event('request-deselection', {
58
- bubbles: true,
59
- composed: true,
60
- });
61
- }
62
-
63
- /**
64
- * The options used to inialize SelectOptionController.
65
- */
66
- export type SelectOptionConfig = MenuItemControllerConfig;
67
-
68
- /**
69
- * A controller that provides most functionality and md-select compatibility for
70
- * an element that implements the SelectOption interface.
71
- */
72
- export class SelectOptionController implements ReactiveController {
73
- private readonly menuItemController: MenuItemController;
74
-
75
- private readonly getHeadlineElements: SelectOptionConfig['getHeadlineElements'];
76
-
77
- private internalDisplayText: string | null = null;
78
-
79
- private lastSelected = this.host.selected;
80
-
81
- private firstUpdate = true;
82
-
83
- /**
84
- * The recommended role of the select option.
85
- */
86
- get role() {
87
- return this.menuItemController.role;
88
- }
89
-
90
- /**
91
- * The text that is selectable via typeahead. If not set, defaults to the
92
- * innerText of the item slotted into the `"headline"` slot.
93
- */
94
- get typeaheadText() {
95
- return this.menuItemController.typeaheadText;
96
- }
97
-
98
- setTypeaheadText(text: string) {
99
- this.menuItemController.setTypeaheadText(text);
100
- }
101
-
102
- /**
103
- * The text that is displayed in the select field when selected. If not set,
104
- * defaults to the textContent of the item slotted into the `"headline"` slot.
105
- */
106
- get displayText() {
107
- if (this.internalDisplayText !== null) {
108
- return this.internalDisplayText;
109
- }
110
-
111
- const headlineElements = this.getHeadlineElements();
112
-
113
- const textParts: string[] = [];
114
- headlineElements.forEach(headlineElement => {
115
- if (headlineElement.textContent && headlineElement.textContent.trim()) {
116
- textParts.push(headlineElement.textContent.trim());
117
- }
118
- });
119
-
120
- return textParts.join(' ');
121
- }
122
-
123
- setDisplayText(text: string) {
124
- this.internalDisplayText = text;
125
- }
126
-
127
- /**
128
- * @param host The SelectOption in which to attach this controller to.
129
- * @param config The object that configures this controller's behavior.
130
- */
131
- constructor(
132
- private readonly host: ReactiveControllerHost & SelectOption,
133
- config: SelectOptionConfig
134
- ) {
135
- this.menuItemController = new MenuItemController(host, config);
136
- this.getHeadlineElements = config.getHeadlineElements;
137
- host.addController(this);
138
- }
139
-
140
- hostUpdate() {
141
- if (this.lastSelected !== this.host.selected) {
142
- this.host.ariaSelected = this.host.selected ? 'true' : 'false';
143
- }
144
- }
145
-
146
- hostUpdated() {
147
- // Do not dispatch event on first update / boot-up.
148
- if (this.lastSelected !== this.host.selected && !this.firstUpdate) {
149
- // This section is really useful for when the user sets selected on the
150
- // option programmatically. Most other cases (click and keyboard) are
151
- // handled by md-select because it needs to coordinate the
152
- // single-selection behavior.
153
- if (this.host.selected) {
154
- this.host.dispatchEvent(createRequestSelectionEvent());
155
- } else {
156
- this.host.dispatchEvent(createRequestDeselectionEvent());
157
- }
158
- }
159
-
160
- this.lastSelected = this.host.selected;
161
- this.firstUpdate = false;
162
- }
163
-
164
- /**
165
- * Bind this click listener to the interactive element. Handles closing the
166
- * menu.
167
- */
168
- onClick = () => {
169
- this.menuItemController.onClick();
170
- };
171
-
172
- /**
173
- * Bind this click listener to the interactive element. Handles closing the
174
- * menu.
175
- */
176
- onKeydown = (e: KeyboardEvent) => {
177
- this.menuItemController.onKeydown(e);
178
- };
179
- }