@fluid-topics/ft-select 1.3.63 → 1.4.1

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.
@@ -0,0 +1,22 @@
1
+ import { ElementDefinitionsMap, FtLitElement } from "@fluid-topics/ft-wc-utils";
2
+ import { FtSelectOptionProperties } from "./ft-select.properties";
3
+ import { PropertyValues } from "lit";
4
+ export declare class FtSelectOption extends FtLitElement implements FtSelectOptionProperties {
5
+ static elementDefinitions: ElementDefinitionsMap;
6
+ static shadowRootOptions: {
7
+ delegatesFocus: boolean;
8
+ mode: ShadowRootMode;
9
+ slotAssignment?: SlotAssignmentMode | undefined;
10
+ customElements?: CustomElementRegistry | undefined;
11
+ };
12
+ label: string;
13
+ value: any;
14
+ selected: boolean;
15
+ slotElement: HTMLSlotElement;
16
+ private isSlotEmpty;
17
+ static styles: import("lit").CSSResult[];
18
+ protected render(): import("lit-html").TemplateResult<1>;
19
+ private onSlotChange;
20
+ protected onClick(): void;
21
+ protected update(changedProperties: PropertyValues): void;
22
+ }
@@ -0,0 +1,74 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { FtLitElement, isSlotEmptyOrWithoutContent, } from "@fluid-topics/ft-wc-utils";
8
+ import { FtRipple } from "@fluid-topics/ft-ripple";
9
+ import { FtTypographyBody2 } from "@fluid-topics/ft-typography";
10
+ import { html, LitElement, } from "lit";
11
+ import { classMap } from "lit/directives/class-map.js";
12
+ import { property, query, } from "lit/decorators.js";
13
+ import { styles } from "./ft-select-option.styles";
14
+ class FtSelectOption extends FtLitElement {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.label = "";
18
+ this.value = null;
19
+ this.selected = false;
20
+ this.isSlotEmpty = true;
21
+ }
22
+ render() {
23
+ var _a, _b;
24
+ const classes = {
25
+ "ft-select--option": true,
26
+ "ft-select--option-selected": this.selected,
27
+ "ft-typography--body2": true,
28
+ };
29
+ return html `
30
+ <div class="${classMap(classes)}"
31
+ part="option option-${(_b = (_a = this.value) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(/[^\w-]/g, "")}"
32
+ tabindex="0"
33
+ aria-label="${this.label}"
34
+ data-value="${this.value}"
35
+ @click=${this.onClick}>
36
+ <ft-ripple ?primary=${this.selected} ?activated=${this.selected}></ft-ripple>
37
+ <slot @slotchange=${this.onSlotChange}>
38
+ </slot>
39
+ ${this.isSlotEmpty ? html `<span>${this.label}</span>` : html ``}
40
+ </div>
41
+ `;
42
+ }
43
+ onSlotChange() {
44
+ this.isSlotEmpty = isSlotEmptyOrWithoutContent(this.slotElement);
45
+ }
46
+ onClick() {
47
+ this.dispatchEvent(new Event("select-option", {
48
+ bubbles: true,
49
+ composed: true,
50
+ }));
51
+ }
52
+ update(changedProperties) {
53
+ super.update(changedProperties);
54
+ this.dispatchEvent(new CustomEvent("option-change", { detail: this, bubbles: true }));
55
+ }
56
+ }
57
+ FtSelectOption.elementDefinitions = {
58
+ "ft-ripple": FtRipple,
59
+ };
60
+ FtSelectOption.shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
61
+ FtSelectOption.styles = [styles, FtTypographyBody2];
62
+ __decorate([
63
+ property({ type: String })
64
+ ], FtSelectOption.prototype, "label", void 0);
65
+ __decorate([
66
+ property({ type: Object, converter: (value) => value })
67
+ ], FtSelectOption.prototype, "value", void 0);
68
+ __decorate([
69
+ property({ type: Boolean, reflect: true })
70
+ ], FtSelectOption.prototype, "selected", void 0);
71
+ __decorate([
72
+ query("slot")
73
+ ], FtSelectOption.prototype, "slotElement", void 0);
74
+ export { FtSelectOption };
@@ -0,0 +1,5 @@
1
+ export interface FtSelectOptionProperties {
2
+ label?: string;
3
+ selected?: boolean;
4
+ value: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,32 @@
1
+ import { FtRippleCssVariables } from "@fluid-topics/ft-ripple/build/ft-ripple.styles";
2
+ import { setVariable } from "@fluid-topics/ft-wc-utils";
3
+ import { FtSelectCssVariables } from "./ft-select.styles";
4
+ import { css } from "lit";
5
+ export const styles = css `
6
+ .ft-select--option {
7
+ ${setVariable(FtRippleCssVariables.opacityContentOnSurfaceHover, "0.08")};
8
+ ${setVariable(FtRippleCssVariables.opacityContentOnSurfacePressed, "0.04")};
9
+ }
10
+
11
+ .ft-select--option > *:not(ft-ripple) {
12
+ position: relative;
13
+ }
14
+
15
+ .ft-select--option {
16
+ position: relative;
17
+ padding: 4px ${FtSelectCssVariables.horizontalSpacing};
18
+ min-height: 32px;
19
+ display: flex;
20
+ align-items: center;
21
+ }
22
+
23
+ .ft-select--option:hover {
24
+ border-left: 2px ${FtSelectCssVariables.colorPrimary} solid;
25
+ }
26
+
27
+ .ft-select--option span {
28
+ text-overflow: ellipsis;
29
+ overflow: hidden;
30
+ }
31
+
32
+ `;
@@ -1,14 +1,8 @@
1
1
  import { PropertyValues } from "lit";
2
2
  import { ElementDefinitionsMap, FtLitElement } from "@fluid-topics/ft-wc-utils";
3
3
  import { FtSelectOptionProperties, FtSelectProperties } from "./ft-select.properties";
4
- export declare class FtSelectOption extends FtLitElement implements FtSelectOptionProperties {
5
- static elementDefinitions: ElementDefinitionsMap;
6
- label: string;
7
- value: any;
8
- selected: boolean;
9
- protected render(): import("lit-html").TemplateResult<1>;
10
- protected update(changedProperties: PropertyValues): void;
11
- }
4
+ import { FtSelectOption } from "./ft-select-option";
5
+ export { FtSelectOption };
12
6
  export declare class FtSelect extends FtLitElement implements FtSelectProperties {
13
7
  static elementDefinitions: ElementDefinitionsMap;
14
8
  static searchTimeoutMilliseconds: number;
@@ -29,11 +23,13 @@ export declare class FtSelect extends FtLitElement implements FtSelectProperties
29
23
  private container?;
30
24
  private optionsMenu?;
31
25
  private mainPanel;
32
- private firstOption?;
33
- private focusedOption?;
34
- private selectedOptionElement?;
35
- private lastOption?;
26
+ get firstOption(): FtSelectOption;
27
+ get optionsList(): FtSelectOption[];
28
+ get focusedOption(): FtSelectOption | undefined;
29
+ get selectedOptionElement(): FtSelectOption | undefined;
30
+ get lastOption(): FtSelectOption | undefined;
36
31
  private optionsSlot?;
32
+ private isSlotEmpty;
37
33
  protected render(): import("lit-html").TemplateResult<1>;
38
34
  private renderOption;
39
35
  protected willUpdate(props: PropertyValues): void;
@@ -45,8 +41,8 @@ export declare class FtSelect extends FtLitElement implements FtSelectProperties
45
41
  private onOptionsKeyDown;
46
42
  private isKeyAlphanumeric;
47
43
  private handleAlphanumericPress;
48
- private onOptionKeyDown;
49
44
  private getOptionId;
45
+ private onSelectOption;
50
46
  private selectOption;
51
47
  private hideOptions;
52
48
  connectedCallback(): void;
@@ -4,43 +4,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { html, nothing } from "lit";
8
- import { property, query, state } from "lit/decorators.js";
7
+ import { html, nothing, } from "lit";
8
+ import { property, query, state, } from "lit/decorators.js";
9
9
  import { classMap } from "lit/directives/class-map.js";
10
10
  import { repeat } from "lit/directives/repeat.js";
11
11
  import { when } from "lit/directives/when.js";
12
12
  import { ifDefined } from "lit/directives/if-defined.js";
13
- import { computeOffsetAutoPosition, deepEqual, FtLitElement } from "@fluid-topics/ft-wc-utils";
14
- import { FtTypography, FtTypographyBody2, FtTypographyCaption, FtTypographyVariants } from "@fluid-topics/ft-typography";
13
+ import { computeOffsetAutoPosition, deepEqual, FtLitElement, isSlotEmptyOrWithoutContent, last, } from "@fluid-topics/ft-wc-utils";
14
+ import { FtTypography, FtTypographyCaption, FtTypographyVariants, } from "@fluid-topics/ft-typography";
15
15
  import { FtInputLabel } from "@fluid-topics/ft-input-label";
16
16
  import { FtRipple } from "@fluid-topics/ft-ripple";
17
- import { FtIcon, FtIcons } from "@fluid-topics/ft-icon";
18
- import { FtSelectCssVariables, styles } from "./ft-select.styles";
19
- class FtSelectOption extends FtLitElement {
20
- constructor() {
21
- super(...arguments);
22
- this.label = "";
23
- this.value = null;
24
- this.selected = false;
25
- }
26
- render() {
27
- return html ``;
28
- }
29
- update(changedProperties) {
30
- super.update(changedProperties);
31
- this.dispatchEvent(new CustomEvent("option-change", { detail: this, bubbles: true }));
32
- }
33
- }
34
- FtSelectOption.elementDefinitions = {};
35
- __decorate([
36
- property({ type: String })
37
- ], FtSelectOption.prototype, "label", void 0);
38
- __decorate([
39
- property({ type: Object, converter: (value) => value })
40
- ], FtSelectOption.prototype, "value", void 0);
41
- __decorate([
42
- property({ type: Boolean, reflect: true })
43
- ], FtSelectOption.prototype, "selected", void 0);
17
+ import { FtIcon, FtIcons, } from "@fluid-topics/ft-icon";
18
+ import { FtSelectCssVariables, styles, } from "./ft-select.styles";
19
+ import { FtSelectOption } from "./ft-select-option";
44
20
  export { FtSelectOption };
45
21
  class FtSelect extends FtLitElement {
46
22
  constructor() {
@@ -57,8 +33,25 @@ class FtSelect extends FtLitElement {
57
33
  this.focusOptions = false;
58
34
  this.currentSearch = "";
59
35
  this.lastSearchInputDate = new Date("01/01/1970");
36
+ this.isSlotEmpty = true;
60
37
  this.hideOptions = (e) => this.optionsDisplayed = this.optionsDisplayed && e.composedPath().includes(this.container);
61
38
  }
39
+ get firstOption() {
40
+ return this.optionsList[0];
41
+ }
42
+ get optionsList() {
43
+ const slotOptions = [...this.querySelectorAll("ft-select-option")];
44
+ return slotOptions.length > 0 ? slotOptions : [...this.shadowRoot.querySelectorAll("ft-select-option")];
45
+ }
46
+ get focusedOption() {
47
+ return this.optionsList.find((el) => el.matches(":focus-within"));
48
+ }
49
+ get selectedOptionElement() {
50
+ return this.optionsList.find((el) => el.selected);
51
+ }
52
+ get lastOption() {
53
+ return last(this.optionsList);
54
+ }
62
55
  render() {
63
56
  var _a, _b, _c, _d, _e;
64
57
  const optionsDisplayed = this.hasOptionsMenuOpen;
@@ -101,16 +94,25 @@ class FtSelect extends FtLitElement {
101
94
  aria-activedescendant="${ifDefined(this.getOptionId(this.selectedOption))}"
102
95
  role="combobox">
103
96
  <ft-ripple ?disabled=${disabled} ?activated=${!this.outlined}></ft-ripple>
104
- <ft-typography variant="${FtTypographyVariants.body1}" class="ft-select--selected-option">
105
- ${(_e = (_d = this.selectedOption) === null || _d === void 0 ? void 0 : _d.label) !== null && _e !== void 0 ? _e : ""}
106
- </ft-typography>
97
+ <slot name="input-value">
98
+ <ft-typography variant="${FtTypographyVariants.body1}"
99
+ aria-hidden="${hasOptionSelected ? "false" : "true"}"
100
+ class="ft-select--selected-option">
101
+ ${(_e = (_d = this.selectedOption) === null || _d === void 0 ? void 0 : _d.label) !== null && _e !== void 0 ? _e : this.label}
102
+ </ft-typography>
103
+ </slot>
107
104
  <ft-icon aria-hidden="true" .value=${optionsDisplayed ? FtIcons.THIN_ARROW_UP : FtIcons.THIN_ARROW}></ft-icon>
108
105
  </div>
109
106
  <div class="ft-select--options"
110
107
  part="options"
111
108
  @keydown=${this.onOptionsKeyDown}
109
+ @select-option=${this.onSelectOption}
112
110
  role="listbox">
113
- ${repeat(this.options, (option) => option.value, (option, index) => this.renderOption(option, index))}
111
+ <slot @slotchange=${this.updateOptionsFromSlot}
112
+ @option-change=${this.updateOptionsFromSlot}
113
+ >
114
+ </slot>
115
+ ${when(this.isSlotEmpty, () => repeat(this.options, (option) => option.value, (option) => this.renderOption(option)))}
114
116
  </div>
115
117
  </div>
116
118
  <slot name="helper" part="helper">
@@ -119,31 +121,16 @@ class FtSelect extends FtLitElement {
119
121
  </ft-typography>` : nothing}
120
122
  </slot>
121
123
  </div>
122
- <slot @slotchange=${this.updateOptionsFromSlot}
123
- @option-change=${this.updateOptionsFromSlot}
124
- ></slot>
125
124
  `;
126
125
  }
127
- renderOption(option, index) {
128
- var _a, _b;
126
+ renderOption(option) {
129
127
  const selected = this.selectedOption === option;
130
- const classes = {
131
- "ft-select--option": true,
132
- "ft-select--option-selected": selected,
133
- "ft-typography--body2": true,
134
- };
135
128
  return html `
136
- <div class="${classMap(classes)}"
137
- part="option option-${(_b = (_a = option.value) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(/[^\w-]/g, "")}"
138
- tabindex="0"
139
- aria-label="${option.label}"
140
- data-value="${option.value}"
141
- id="${"option-" + index}"
142
- @keydown=${(e) => this.onOptionKeyDown(e, option)}
143
- @click=${() => this.selectOption(option)}>
144
- <ft-ripple ?primary=${selected} ?activated=${selected}></ft-ripple>
145
- <span>${option.label}</span>
146
- </div>
129
+ <ft-select-option
130
+ ?selected=${selected}
131
+ label="${option.label}"
132
+ .value=${option.value}
133
+ ></ft-select-option>
147
134
  `;
148
135
  }
149
136
  willUpdate(props) {
@@ -163,7 +150,7 @@ class FtSelect extends FtLitElement {
163
150
  this.focusOptions = false;
164
151
  }
165
152
  if (props.has("optionsDisplayed") && this.hasOptionsMenuOpen) {
166
- this.optionsMenu.style.width = this.mainPanel.getBoundingClientRect().width + "px";
153
+ this.optionsMenu.style.minWidth = this.mainPanel.getBoundingClientRect().width + "px";
167
154
  const fallbackPlacements = ["bottom", "top"];
168
155
  computeOffsetAutoPosition(this.mainPanel, this.optionsMenu, "bottom", fallbackPlacements, "fixed", FtSelectCssVariables.optionsHeight, 0)
169
156
  .then(({ x, y }) => {
@@ -181,6 +168,7 @@ class FtSelect extends FtLitElement {
181
168
  updateOptionsFromSlot(e) {
182
169
  var _a;
183
170
  e.stopPropagation();
171
+ this.isSlotEmpty = isSlotEmptyOrWithoutContent(this.optionsSlot);
184
172
  const optionsFromSlot = (_a = this.optionsSlot) === null || _a === void 0 ? void 0 : _a.assignedElements().map((n) => n);
185
173
  if (optionsFromSlot && optionsFromSlot.length > 0) {
186
174
  this.options = optionsFromSlot;
@@ -207,25 +195,34 @@ class FtSelect extends FtLitElement {
207
195
  }
208
196
  }
209
197
  onOptionsKeyDown(e) {
210
- var _a, _b, _c, _d, _e;
198
+ var _a, _b;
211
199
  let optionToFocus;
200
+ const indexOfFocusElement = this.focusedOption ? this.optionsList.indexOf(this.focusedOption) : 0;
212
201
  switch (e.key) {
213
- case "Escape":
202
+ case " ":
203
+ case "Enter":
204
+ e.preventDefault();
205
+ e.stopPropagation();
206
+ this.onSelectOption(e);
214
207
  this.optionsDisplayed = false;
215
208
  (_a = this.mainPanel) === null || _a === void 0 ? void 0 : _a.focus();
216
209
  break;
210
+ case "Escape":
211
+ this.optionsDisplayed = false;
212
+ (_b = this.mainPanel) === null || _b === void 0 ? void 0 : _b.focus();
213
+ break;
217
214
  case "Tab":
218
215
  this.optionsDisplayed = false;
219
216
  break;
220
217
  case "ArrowUp":
221
218
  e.preventDefault();
222
219
  e.stopPropagation();
223
- optionToFocus = (_c = (_b = this.focusedOption) === null || _b === void 0 ? void 0 : _b.previousElementSibling) !== null && _c !== void 0 ? _c : this.lastOption;
220
+ optionToFocus = this.optionsList[(indexOfFocusElement - 1 + this.optionsList.length) % this.optionsList.length];
224
221
  break;
225
222
  case "ArrowDown":
226
223
  e.preventDefault();
227
224
  e.stopPropagation();
228
- optionToFocus = (_e = (_d = this.focusedOption) === null || _d === void 0 ? void 0 : _d.nextElementSibling) !== null && _e !== void 0 ? _e : this.firstOption;
225
+ optionToFocus = this.optionsList[(indexOfFocusElement + 1) % this.optionsList.length];
229
226
  break;
230
227
  default:
231
228
  if (e.key.length != 1) {
@@ -245,7 +242,6 @@ class FtSelect extends FtLitElement {
245
242
  || (charCode > 96 && charCode < 123); // lower alpha (a-z)
246
243
  }
247
244
  handleAlphanumericPress(c) {
248
- var _a, _b;
249
245
  const currentDate = new Date();
250
246
  // Resetting current search if timeout elapsed
251
247
  if (currentDate.getTime() - this.lastSearchInputDate.getTime() > FtSelect.searchTimeoutMilliseconds) {
@@ -254,23 +250,8 @@ class FtSelect extends FtLitElement {
254
250
  this.currentSearch += c.toLowerCase();
255
251
  // Searching for option
256
252
  const foundOption = this.options.find((opt) => { var _a; return ((_a = opt.label) === null || _a === void 0 ? void 0 : _a.toLowerCase().substring(0, this.currentSearch.length)) === this.currentSearch; });
257
- if (foundOption === undefined) {
258
- this.lastSearchInputDate = currentDate;
259
- return undefined;
260
- }
261
- const optionElement = (_b = (_a = this.optionsMenu) === null || _a === void 0 ? void 0 : _a.querySelector(`[data-value="${foundOption.value}"]`)) !== null && _b !== void 0 ? _b : undefined;
262
253
  this.lastSearchInputDate = currentDate;
263
- return optionElement;
264
- }
265
- onOptionKeyDown(e, option) {
266
- var _a;
267
- if (e.key === "Enter" || e.key === " ") {
268
- e.preventDefault();
269
- e.stopPropagation();
270
- this.selectOption(option);
271
- this.optionsDisplayed = false;
272
- (_a = this.mainPanel) === null || _a === void 0 ? void 0 : _a.focus();
273
- }
254
+ return foundOption && this.optionsList.find((el) => el.value === foundOption.value);
274
255
  }
275
256
  getOptionId(selectedOption) {
276
257
  if (!selectedOption) {
@@ -282,9 +263,14 @@ class FtSelect extends FtLitElement {
282
263
  }
283
264
  return `option-${index}`;
284
265
  }
266
+ onSelectOption(e) {
267
+ e.stopPropagation();
268
+ const option = this.options.find((opt) => opt.value === e.target.value);
269
+ this.selectOption(option);
270
+ }
285
271
  selectOption(option) {
286
272
  var _a, _b;
287
- if (!deepEqual((_a = this.selectedOption) === null || _a === void 0 ? void 0 : _a.value, option.value)) {
273
+ if (!deepEqual((_a = this.selectedOption) === null || _a === void 0 ? void 0 : _a.value, option === null || option === void 0 ? void 0 : option.value)) {
288
274
  this.selectedOption = option;
289
275
  for (const otherOption of this.options) {
290
276
  otherOption.selected = otherOption === option;
@@ -307,10 +293,10 @@ FtSelect.elementDefinitions = {
307
293
  "ft-typography": FtTypography,
308
294
  "ft-ripple": FtRipple,
309
295
  "ft-icon": FtIcon,
296
+ "ft-select-option": FtSelectOption,
310
297
  };
311
298
  FtSelect.searchTimeoutMilliseconds = 2000;
312
299
  FtSelect.styles = [
313
- FtTypographyBody2,
314
300
  FtTypographyCaption,
315
301
  styles,
316
302
  ];
@@ -362,18 +348,6 @@ __decorate([
362
348
  __decorate([
363
349
  query(".ft-select--input-panel")
364
350
  ], FtSelect.prototype, "mainPanel", void 0);
365
- __decorate([
366
- query(".ft-select--option:first-child")
367
- ], FtSelect.prototype, "firstOption", void 0);
368
- __decorate([
369
- query(".ft-select--option:focus")
370
- ], FtSelect.prototype, "focusedOption", void 0);
371
- __decorate([
372
- query(".ft-select--option.ft-select--option-selected")
373
- ], FtSelect.prototype, "selectedOptionElement", void 0);
374
- __decorate([
375
- query(".ft-select--option:last-child")
376
- ], FtSelect.prototype, "lastOption", void 0);
377
351
  __decorate([
378
352
  query("slot:not([name])")
379
353
  ], FtSelect.prototype, "optionsSlot", void 0);