@dile/ui 3.18.5 → 3.19.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.
@@ -65,6 +65,8 @@ export class DileSelectSearch extends DileSelectSearchableBase {
65
65
  value: option.value,
66
66
  text: option.textContent.trim(),
67
67
  icon: option.dataset.icon || select.dataset.icon,
68
+ // The native <option>, so an optionRenderer can read its dataset.
69
+ element: option,
68
70
  }));
69
71
  }
70
72
 
@@ -91,7 +93,7 @@ export class DileSelectSearch extends DileSelectSearchableBase {
91
93
  * Template method: decide whether an option matches the keyword.
92
94
  * By default it searches the keyword anywhere in the option text, ignoring case and accents.
93
95
  * Override it in a subclass to customize the search (e.g. also match option.value).
94
- * @param {{value: string, text: string, icon: string}} option
96
+ * @param {{value: string, text: string, icon: string, element: HTMLOptionElement}} option
95
97
  * @param {string} keyword
96
98
  * @returns {boolean}
97
99
  */
@@ -11,6 +11,8 @@ import { labelStyles } from '../../input/src/label-styles.js';
11
11
  import { messageStyles } from '../../input/src/message-styles.js';
12
12
 
13
13
  const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter', 'Escape', 'Tab'];
14
+ // Parts of the nested dile-select-overlay re-exported to this component's host.
15
+ const OPTION_PARTS = ['option', 'option-highlighted', 'option-selected', 'option-content'];
14
16
 
15
17
  /**
16
18
  * Base class for the searchable members of the select family (dile-select-ajax-overlay,
@@ -158,6 +160,13 @@ export class DileSelectSearchableBase extends DileEmmitChange(LitElement) {
158
160
  /** dile-iconlib "family.name" icon string applied to every option that has no icon of its
159
161
  * own via iconProperty. */
160
162
  icon: { type: String },
163
+ /** Optional custom renderer for the content of each result: `(item, index) => TemplateResult | string`,
164
+ * receiving the original data object. The displayProperty text is still used for the accessible
165
+ * name, the keyboard announcements and the selected text. */
166
+ optionRenderer: { attribute: false },
167
+ /** Space or comma separated extra part names used inside optionRenderer templates, re-exported
168
+ * from the nested dile-select-overlay so they can be styled with ::part() from outside. */
169
+ optionParts: { type: String },
161
170
  delay: { type: Number },
162
171
  keyword: {
163
172
  type: String,
@@ -382,6 +391,8 @@ export class DileSelectSearchableBase extends DileEmmitChange(LitElement) {
382
391
  name="generated-select-field"
383
392
  placeholder="${this.placeholder}"
384
393
  ?disabled=${this.disabled}
394
+ exportparts="${this.exportedOptionParts}"
395
+ .optionRenderer=${this.nestedOptionRenderer}
385
396
  @element-changed=${this.doSelected}
386
397
  >
387
398
  <select slot="select" data-icon="${ifDefined(this.icon)}">
@@ -393,6 +404,39 @@ export class DileSelectSearchableBase extends DileEmmitChange(LitElement) {
393
404
  `;
394
405
  }
395
406
 
407
+ /** exportparts value for the nested dile-select-overlay: built-in option parts plus optionParts. */
408
+ get exportedOptionParts() {
409
+ const extra = (this.optionParts || '').split(/[\s,]+/).filter(Boolean);
410
+ return [...new Set([...OPTION_PARTS, ...extra])].join(', ');
411
+ }
412
+
413
+ /** Adapts optionRenderer (which receives data items) to the nested overlay's (option, index) signature.
414
+ * Cached per optionRenderer so the nested component doesn't see a new function on every render. */
415
+ get nestedOptionRenderer() {
416
+ if (typeof this.optionRenderer !== 'function') {
417
+ return undefined;
418
+ }
419
+ if (this._nestedRendererFor !== this.optionRenderer) {
420
+ const renderer = this.optionRenderer;
421
+ this._nestedRendererFor = renderer;
422
+ this._nestedRenderer = (option, index) => {
423
+ const item = this.findItemForOption(option, index);
424
+ return item === undefined ? option.text : renderer(item, index);
425
+ };
426
+ }
427
+ return this._nestedRenderer;
428
+ }
429
+
430
+ /** Data item behind an option of the nested overlay: by index (options are generated from
431
+ * `data` in order), checked against idProperty, falling back to a search by idProperty. */
432
+ findItemForOption(option, index) {
433
+ const byIndex = this.data[index];
434
+ if (byIndex && String(byIndex[this.idProperty]) === option.value) {
435
+ return byIndex;
436
+ }
437
+ return this.data.find(item => String(item[this.idProperty]) === option.value);
438
+ }
439
+
396
440
  get messageTemplate() {
397
441
  return html`
398
442
  ${this.message
@@ -28,7 +28,7 @@ export const DileCloseDocumentClick = (SuperClass) =>
28
28
  DileCloseDocumentClick.closeDocumentHandler = () => {
29
29
  if(DileCloseDocumentClick.elements) {
30
30
  for (let ele of DileCloseDocumentClick.elements) {
31
- ele.close();
31
+ ele.closeFromDocumentClick();
32
32
  }
33
33
  }
34
34
  };
@@ -70,6 +70,15 @@ export const DileCloseDocumentClick = (SuperClass) =>
70
70
  });
71
71
  }
72
72
 
73
+ /**
74
+ * Close requested by a click in the document. Calls close() by default.
75
+ * Override it to ignore these closes in some states, as DileOverlay does
76
+ * while the overlay is opening.
77
+ */
78
+ closeFromDocumentClick() {
79
+ this.close();
80
+ }
81
+
73
82
  /**
74
83
  * This method only alerts the developer to create the close() method in his component
75
84
  */
@@ -80,35 +80,63 @@ export const DileOverlay = function(superClass) {
80
80
  }
81
81
  /**
82
82
  * closes the overlay
83
+ *
84
+ * An explicit close() while the overlay is still opening (the 50ms delay of open())
85
+ * cancels the opening, so the overlay ends up closed.
83
86
  */
84
87
  close() {
85
- if(!this._opening && this._overlayClass=='opened') {
88
+ if(this._opening) {
89
+ this.cancelDelay();
90
+ this._opening = false;
91
+ this._overlayClass = '';
92
+ this.overlay.style.display = 'none';
93
+ this._dispatchClosed();
94
+ } else if(this._overlayClass=='opened') {
86
95
  this._overlayClass = '';
87
96
  this._removePositionListeners();
88
97
  this.cancelDelay();
89
98
  this.delayId = setTimeout(() => {
90
99
  this.overlay.style.display = 'none'
91
100
  }, 500);
92
- this.dispatchEvent(new CustomEvent('overlay-closed', {
101
+ this._dispatchClosed();
102
+ }
103
+ }
104
+ /**
105
+ * Close requested by a document click (DileCloseDocumentClick). It is ignored while
106
+ * the overlay is opening, because the same click that opens the overlay bubbles up
107
+ * to the document.
108
+ */
109
+ closeFromDocumentClick() {
110
+ if(!this._opening) {
111
+ this.close();
112
+ }
113
+ }
114
+ _dispatchClosed() {
115
+ this.dispatchEvent(new CustomEvent('overlay-closed', {
93
116
  bubbles: true,
94
117
  composed: true,
95
118
  }));
96
- }
97
119
  }
98
120
  /**
99
121
  * opens the overlay
122
+ *
123
+ * overlay-opened is only dispatched when the overlay was closed, so every
124
+ * overlay-opened is paired with one overlay-closed.
100
125
  */
101
126
  open() {
102
- this.dispatchEvent(new CustomEvent('overlay-opened', {
103
- bubbles: true,
104
- composed: true,
105
- }));
127
+ if(!this._opening && this._overlayClass != 'opened') {
128
+ this.dispatchEvent(new CustomEvent('overlay-opened', {
129
+ bubbles: true,
130
+ composed: true,
131
+ }));
132
+ }
106
133
  if(!this._opening) {
107
134
  this._opening = true;
108
135
  this.updatePosition();
109
- if(this.closeAll) {
110
- this.closeAll();
111
- };
136
+ // Close the other overlays, even if they are still opening.
137
+ if(this.closeOthers) {
138
+ this.closeOthers();
139
+ }
112
140
  this.overlay.style.display = 'block';
113
141
  this.cancelDelay();
114
142
  this.delayId = setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.18.5",
3
+ "version": "3.19.1",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,5 +28,5 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "49f740275cc480045fee98068c8ee8327dcb4d0d"
31
+ "gitHead": "33fa981c48f488be6c9a381f1a32eeffa5b92b24"
32
32
  }