@aurelia-mdc-web/all 9.3.0-au2 → 9.3.1-au2
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/dist/chips/mdc-chip/mdc-chip.js.map +1 -1
- package/dist/data-table/mdc-data-table.js.map +1 -1
- package/dist/dialog/mdc-dialog-service.js.map +1 -1
- package/dist/expandable/mdc-expandable.js.map +1 -1
- package/dist/form-field/mdc-form-field.js.map +1 -1
- package/dist/list/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.js.map +1 -1
- package/dist/list/mdc-list-item/mdc-list-item.js.map +1 -1
- package/dist/menu/mdc-menu.js.map +1 -1
- package/dist/segmented-button/mdc-segmented-button-segment/mdc-segmented-button-segment.js.map +1 -1
- package/dist/select/mdc-select-value-observer.js.map +1 -1
- package/dist/select/mdc-select.js.map +1 -1
- package/dist/text-field/mdc-text-field.js.map +1 -1
- package/dist/tree-view/mdc-tree-view.js.map +1 -1
- package/package.json +2 -2
- package/src/chips/mdc-chip/mdc-chip.ts +290 -290
- package/src/data-table/mdc-data-table.ts +432 -432
- package/src/dialog/mdc-dialog-service.ts +80 -80
- package/src/expandable/mdc-expandable.ts +104 -104
- package/src/form-field/mdc-form-field.ts +60 -60
- package/src/list/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.ts +108 -108
- package/src/list/mdc-list-item/mdc-list-item.ts +136 -136
- package/src/menu/mdc-menu.ts +340 -340
- package/src/segmented-button/mdc-segmented-button-segment/mdc-segmented-button-segment.ts +170 -170
- package/src/select/mdc-select-value-observer.ts +346 -346
- package/src/select/mdc-select.ts +480 -480
- package/src/text-field/mdc-text-field.ts +535 -535
- package/src/tree-view/mdc-tree-view.ts +147 -147
|
@@ -1,346 +1,346 @@
|
|
|
1
|
-
import {
|
|
2
|
-
subscriberCollection,
|
|
3
|
-
AccessorType,
|
|
4
|
-
} from '@aurelia/runtime';
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
ICollectionObserver,
|
|
8
|
-
IndexMap,
|
|
9
|
-
IObserver,
|
|
10
|
-
IObserverLocator,
|
|
11
|
-
ISubscriber,
|
|
12
|
-
ISubscriberCollection,
|
|
13
|
-
} from '@aurelia/runtime';
|
|
14
|
-
|
|
15
|
-
import { INode, CustomElement } from '@aurelia/runtime-html';
|
|
16
|
-
import { IMdcSelectElement, MdcSelect } from './mdc-select';
|
|
17
|
-
|
|
18
|
-
// const hasOwn = Object.prototype.hasOwnProperty;
|
|
19
|
-
const childObserverOptions = {
|
|
20
|
-
childList: true,
|
|
21
|
-
subtree: true,
|
|
22
|
-
characterData: true
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// function defaultMatcher(a: unknown, b: unknown): boolean {
|
|
26
|
-
// return a === b;
|
|
27
|
-
// }
|
|
28
|
-
|
|
29
|
-
export interface IOptionElement extends HTMLOptionElement {
|
|
30
|
-
model?: unknown;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface MdcSelectValueObserver extends
|
|
34
|
-
ISubscriberCollection { }
|
|
35
|
-
|
|
36
|
-
export interface INodeObserverConfigBase {
|
|
37
|
-
/**
|
|
38
|
-
* Indicates the list of events can be used to observe a particular property
|
|
39
|
-
*/
|
|
40
|
-
readonly events: string[];
|
|
41
|
-
/**
|
|
42
|
-
* Indicates whether this property is readonly, so observer wont attempt to assign value
|
|
43
|
-
* example: input.files
|
|
44
|
-
*/
|
|
45
|
-
readonly readonly?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* A default value to assign to the corresponding property if the incoming value is null/undefined
|
|
48
|
-
*/
|
|
49
|
-
readonly default?: unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export interface INodeObserver extends IObserver {
|
|
54
|
-
/**
|
|
55
|
-
* Instruct this node observer event observation behavior
|
|
56
|
-
*/
|
|
57
|
-
useConfig(config: INodeObserverConfigBase): void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@subscriberCollection()
|
|
61
|
-
export class MdcSelectValueObserver implements INodeObserver {
|
|
62
|
-
public currentValue: unknown = void 0;
|
|
63
|
-
public oldValue: unknown = void 0;
|
|
64
|
-
|
|
65
|
-
public readonly obj: IMdcSelectElement;
|
|
66
|
-
public config: INodeObserverConfigBase;
|
|
67
|
-
|
|
68
|
-
public hasChanges: boolean = false;
|
|
69
|
-
// ObserverType.Layout is not always true
|
|
70
|
-
// but for simplicity, always treat as such
|
|
71
|
-
public type: AccessorType = (AccessorType.Node | AccessorType.Observer | AccessorType.Layout) as AccessorType;
|
|
72
|
-
|
|
73
|
-
public arrayObserver?: ICollectionObserver<'array'> = void 0;
|
|
74
|
-
public nodeObserver?: MutationObserver = void 0;
|
|
75
|
-
|
|
76
|
-
private observing: boolean = false;
|
|
77
|
-
private listened: boolean = false;
|
|
78
|
-
|
|
79
|
-
public constructor(
|
|
80
|
-
obj: INode,
|
|
81
|
-
// deepscan-disable-next-line
|
|
82
|
-
_key: PropertyKey,
|
|
83
|
-
config: INodeObserverConfigBase,
|
|
84
|
-
_: IObserverLocator
|
|
85
|
-
) {
|
|
86
|
-
this.obj = obj as unknown as IMdcSelectElement;
|
|
87
|
-
this.config = config;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
optionsWereSet: boolean;
|
|
91
|
-
|
|
92
|
-
setElementValue(skipNotify: boolean) {
|
|
93
|
-
// do not pass the value to the element if options has never been set
|
|
94
|
-
// the value will be passed on when options do arrive
|
|
95
|
-
if (this.optionsWereSet) {
|
|
96
|
-
CustomElement.for<MdcSelect>(this.obj).viewModel.setValue(this.currentValue, skipNotify);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public getValue(): unknown {
|
|
101
|
-
// is it safe to assume the observer has the latest value?
|
|
102
|
-
// todo: ability to turn on/off cache based on type
|
|
103
|
-
return this.observing
|
|
104
|
-
? this.currentValue
|
|
105
|
-
// : this.obj.multiple
|
|
106
|
-
// ? Array.from(this.obj.options).map(o => o.value)
|
|
107
|
-
: this.obj.value;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public setValue(newValue: unknown): void {
|
|
111
|
-
this.currentValue = newValue;
|
|
112
|
-
this.hasChanges = newValue !== this.oldValue;
|
|
113
|
-
// this.observeArray(newValue instanceof Array ? newValue : null);
|
|
114
|
-
if (this.optionsWereSet) {
|
|
115
|
-
this.flushChanges();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
120
|
-
public flushChanges(): void {
|
|
121
|
-
if (this.hasChanges) {
|
|
122
|
-
this.hasChanges = false;
|
|
123
|
-
this.synchronizeOptions();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public handleCollectionChange(): void {
|
|
128
|
-
// always sync "selected" property of <options/>
|
|
129
|
-
// immediately whenever the array notifies its mutation
|
|
130
|
-
this.synchronizeOptions();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
public notify(): void {
|
|
134
|
-
const oldValue = this.oldValue;
|
|
135
|
-
const newValue = this.currentValue;
|
|
136
|
-
if (newValue === oldValue) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
this.subs.notify(newValue, oldValue);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
public handleEvent(): void {
|
|
143
|
-
const shouldNotify = this.synchronizeValue();
|
|
144
|
-
if (shouldNotify) {
|
|
145
|
-
this.subs.notify(this.currentValue, this.oldValue);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
150
|
-
public synchronizeOptions(_indexMap?: IndexMap): void {
|
|
151
|
-
this.setElementValue(true);
|
|
152
|
-
// const { currentValue, obj } = this;
|
|
153
|
-
// // const isArray = Array.isArray(currentValue);
|
|
154
|
-
// // const matcher = obj.matcher !== void 0 ? obj.matcher : defaultMatcher;
|
|
155
|
-
// // const matcher = defaultMatcher;
|
|
156
|
-
// const options = CustomElement.for<MdcSelect>(obj).viewModel.items;
|
|
157
|
-
// let i = options.length;
|
|
158
|
-
|
|
159
|
-
// while (i-- > 0) {
|
|
160
|
-
// // const option = options[i];
|
|
161
|
-
// // const optionValue = hasOwn.call(option, 'model') ? option.model : option.value;
|
|
162
|
-
// // const optionValue = option.value;
|
|
163
|
-
// // if (isArray) {
|
|
164
|
-
// // option.selected = (currentValue as unknown[]).findIndex(item => !!matcher(optionValue, item)) !== -1;
|
|
165
|
-
// // continue;
|
|
166
|
-
// // }
|
|
167
|
-
// if (!this.optionsWereSet) {
|
|
168
|
-
// this.optionsWereSet = true;
|
|
169
|
-
// this.setElementValue(true);
|
|
170
|
-
// }
|
|
171
|
-
// // option.selected = !!matcher(optionValue, currentValue);
|
|
172
|
-
// }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
public synchronizeValue(): boolean {
|
|
176
|
-
// Spec for synchronizing value from `<select/>` to `SelectObserver`
|
|
177
|
-
// When synchronizing value to observed <select/> element, do the following steps:
|
|
178
|
-
// A. If `<select/>` is multiple
|
|
179
|
-
// 1. Check if current value, called `currentValue` is an array
|
|
180
|
-
// a. If not an array, return true to signal value has changed
|
|
181
|
-
// b. If is an array:
|
|
182
|
-
// i. gather all current selected <option/>, in to array called `values`
|
|
183
|
-
// ii. loop through the `currentValue` array and remove items that are nolonger selected based on matcher
|
|
184
|
-
// iii. loop through the `values` array and add items that are selected based on matcher
|
|
185
|
-
// iv. Return false to signal value hasn't changed
|
|
186
|
-
// B. If the select is single
|
|
187
|
-
// 1. Let `value` equal the first selected option, if no option selected, then `value` is `null`
|
|
188
|
-
// 2. assign `this.currentValue` to `this.oldValue`
|
|
189
|
-
// 3. assign `value` to `this.currentValue`
|
|
190
|
-
// 4. return `true` to signal value has changed
|
|
191
|
-
const obj = this.obj;
|
|
192
|
-
const options = CustomElement.for<MdcSelect>(obj).viewModel.items ?? [];
|
|
193
|
-
const len = options.length;
|
|
194
|
-
// const currentValue = this.currentValue;
|
|
195
|
-
let i = 0;
|
|
196
|
-
|
|
197
|
-
/*
|
|
198
|
-
if (obj.multiple) {
|
|
199
|
-
// A.
|
|
200
|
-
if (!(currentValue instanceof Array)) {
|
|
201
|
-
// A.1.a
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
// A.1.b
|
|
205
|
-
// multi select
|
|
206
|
-
let option: IOptionElement;
|
|
207
|
-
const matcher = obj.matcher ?? defaultMatcher;
|
|
208
|
-
// A.1.b.i
|
|
209
|
-
const values: unknown[] = [];
|
|
210
|
-
while (i < len) {
|
|
211
|
-
option = options[i];
|
|
212
|
-
if (option.selected) {
|
|
213
|
-
values.push(hasOwn.call(option, 'model')
|
|
214
|
-
? option.model
|
|
215
|
-
: option.value
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
++i;
|
|
219
|
-
}
|
|
220
|
-
// A.1.b.ii
|
|
221
|
-
i = 0;
|
|
222
|
-
while (i < currentValue.length) {
|
|
223
|
-
const a = currentValue[i];
|
|
224
|
-
// Todo: remove arrow fn
|
|
225
|
-
if (values.findIndex(b => !!matcher(a, b)) === -1) {
|
|
226
|
-
currentValue.splice(i, 1);
|
|
227
|
-
} else {
|
|
228
|
-
++i;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// A.1.b.iii
|
|
232
|
-
i = 0;
|
|
233
|
-
while (i < values.length) {
|
|
234
|
-
const a = values[i];
|
|
235
|
-
// Todo: remove arrow fn
|
|
236
|
-
if (currentValue.findIndex(b => !!matcher(a, b)) === -1) {
|
|
237
|
-
currentValue.push(a);
|
|
238
|
-
}
|
|
239
|
-
++i;
|
|
240
|
-
}
|
|
241
|
-
// A.1.b.iv
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
*/
|
|
245
|
-
// B. single select
|
|
246
|
-
// B.1
|
|
247
|
-
let value: unknown = null;
|
|
248
|
-
while (i < len) {
|
|
249
|
-
const option = options[i];
|
|
250
|
-
if (option.value === this.obj.value) {
|
|
251
|
-
// value = hasOwn.call(option, 'model') ? option.model : option.value;
|
|
252
|
-
value = option.value;
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
++i;
|
|
256
|
-
}
|
|
257
|
-
// B.2
|
|
258
|
-
this.oldValue = this.currentValue;
|
|
259
|
-
// B.3
|
|
260
|
-
this.currentValue = value;
|
|
261
|
-
// B.4
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private start(): void {
|
|
266
|
-
const vm = CustomElement.for<MdcSelect>(this.obj).viewModel;
|
|
267
|
-
vm.initialised.then(() => {
|
|
268
|
-
(this.nodeObserver = new this.obj.ownerDocument.defaultView!.MutationObserver(records => this.handleNodeChange(records)))
|
|
269
|
-
.observe(vm.menu.root, childObserverOptions);
|
|
270
|
-
// this.observeArray(this.currentValue instanceof Array ? this.currentValue : null);
|
|
271
|
-
this.observing = true;
|
|
272
|
-
if (vm.items?.length) {
|
|
273
|
-
this.optionsWereSet = true;
|
|
274
|
-
this.synchronizeOptions();
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private stop(): void {
|
|
280
|
-
this.optionsWereSet = false;
|
|
281
|
-
this.nodeObserver?.disconnect();
|
|
282
|
-
this.arrayObserver?.unsubscribe(this);
|
|
283
|
-
this.nodeObserver
|
|
284
|
-
= this.arrayObserver
|
|
285
|
-
= void 0;
|
|
286
|
-
this.observing = false;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// todo: observe all kind of collection
|
|
290
|
-
// private observeArray(array: unknown[] | null): void {
|
|
291
|
-
// this.arrayObserver?.unsubscribe(this);
|
|
292
|
-
// this.arrayObserver = void 0;
|
|
293
|
-
// if (array !== null) {
|
|
294
|
-
// if (!this.obj.multiple) {
|
|
295
|
-
// throw new Error('Only null or Array instances can be bound to a multi-select.');
|
|
296
|
-
// }
|
|
297
|
-
// (this.arrayObserver = this.observerLocator.getArrayObserver(array)).subscribe(this);
|
|
298
|
-
// }
|
|
299
|
-
// }
|
|
300
|
-
|
|
301
|
-
public handleNodeChange(records: MutationRecord[]): void {
|
|
302
|
-
if (records.find(x => x.type === 'childList'
|
|
303
|
-
&& (Array.from(x.addedNodes).find(y => (y as HTMLElement).tagName === 'MDC-LIST-ITEM')
|
|
304
|
-
|| Array.from(x.removedNodes).find(y => (y as HTMLElement).tagName === 'MDC-LIST-ITEM'))
|
|
305
|
-
)) {
|
|
306
|
-
this.optionsWereSet = true;
|
|
307
|
-
this.synchronizeOptions();
|
|
308
|
-
const shouldNotify = this.synchronizeValue();
|
|
309
|
-
if (shouldNotify) {
|
|
310
|
-
this.notify();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
public subscribe(subscriber: ISubscriber): void {
|
|
316
|
-
if (this.subs.add(subscriber) && this.subs.count === 1) {
|
|
317
|
-
for (const e of this.config.events) {
|
|
318
|
-
this.obj.addEventListener(e, this);
|
|
319
|
-
}
|
|
320
|
-
this.listened = true;
|
|
321
|
-
this.start();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
public unsubscribe(subscriber: ISubscriber): void {
|
|
326
|
-
if (this.subs.remove(subscriber) && this.subs.count === 0) {
|
|
327
|
-
for (const e of this.config.events) {
|
|
328
|
-
this.obj.removeEventListener(e, this);
|
|
329
|
-
}
|
|
330
|
-
this.listened = false;
|
|
331
|
-
this.stop();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
useConfig(config: INodeObserverConfigBase): void {
|
|
336
|
-
this.config = config;
|
|
337
|
-
if (this.listened) {
|
|
338
|
-
for (const e of this.config.events) {
|
|
339
|
-
this.obj.removeEventListener(e, this);
|
|
340
|
-
}
|
|
341
|
-
for (const e of this.config.events) {
|
|
342
|
-
this.obj.addEventListener(e, this);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
subscriberCollection,
|
|
3
|
+
AccessorType,
|
|
4
|
+
} from '@aurelia/runtime';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ICollectionObserver,
|
|
8
|
+
IndexMap,
|
|
9
|
+
IObserver,
|
|
10
|
+
IObserverLocator,
|
|
11
|
+
ISubscriber,
|
|
12
|
+
ISubscriberCollection,
|
|
13
|
+
} from '@aurelia/runtime';
|
|
14
|
+
|
|
15
|
+
import { INode, CustomElement } from '@aurelia/runtime-html';
|
|
16
|
+
import { IMdcSelectElement, MdcSelect } from './mdc-select';
|
|
17
|
+
|
|
18
|
+
// const hasOwn = Object.prototype.hasOwnProperty;
|
|
19
|
+
const childObserverOptions = {
|
|
20
|
+
childList: true,
|
|
21
|
+
subtree: true,
|
|
22
|
+
characterData: true
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// function defaultMatcher(a: unknown, b: unknown): boolean {
|
|
26
|
+
// return a === b;
|
|
27
|
+
// }
|
|
28
|
+
|
|
29
|
+
export interface IOptionElement extends HTMLOptionElement {
|
|
30
|
+
model?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MdcSelectValueObserver extends
|
|
34
|
+
ISubscriberCollection { }
|
|
35
|
+
|
|
36
|
+
export interface INodeObserverConfigBase {
|
|
37
|
+
/**
|
|
38
|
+
* Indicates the list of events can be used to observe a particular property
|
|
39
|
+
*/
|
|
40
|
+
readonly events: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Indicates whether this property is readonly, so observer wont attempt to assign value
|
|
43
|
+
* example: input.files
|
|
44
|
+
*/
|
|
45
|
+
readonly readonly?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* A default value to assign to the corresponding property if the incoming value is null/undefined
|
|
48
|
+
*/
|
|
49
|
+
readonly default?: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export interface INodeObserver extends IObserver {
|
|
54
|
+
/**
|
|
55
|
+
* Instruct this node observer event observation behavior
|
|
56
|
+
*/
|
|
57
|
+
useConfig(config: INodeObserverConfigBase): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@subscriberCollection()
|
|
61
|
+
export class MdcSelectValueObserver implements INodeObserver {
|
|
62
|
+
public currentValue: unknown = void 0;
|
|
63
|
+
public oldValue: unknown = void 0;
|
|
64
|
+
|
|
65
|
+
public readonly obj: IMdcSelectElement;
|
|
66
|
+
public config: INodeObserverConfigBase;
|
|
67
|
+
|
|
68
|
+
public hasChanges: boolean = false;
|
|
69
|
+
// ObserverType.Layout is not always true
|
|
70
|
+
// but for simplicity, always treat as such
|
|
71
|
+
public type: AccessorType = (AccessorType.Node | AccessorType.Observer | AccessorType.Layout) as AccessorType;
|
|
72
|
+
|
|
73
|
+
public arrayObserver?: ICollectionObserver<'array'> = void 0;
|
|
74
|
+
public nodeObserver?: MutationObserver = void 0;
|
|
75
|
+
|
|
76
|
+
private observing: boolean = false;
|
|
77
|
+
private listened: boolean = false;
|
|
78
|
+
|
|
79
|
+
public constructor(
|
|
80
|
+
obj: INode,
|
|
81
|
+
// deepscan-disable-next-line
|
|
82
|
+
_key: PropertyKey,
|
|
83
|
+
config: INodeObserverConfigBase,
|
|
84
|
+
_: IObserverLocator
|
|
85
|
+
) {
|
|
86
|
+
this.obj = obj as unknown as IMdcSelectElement;
|
|
87
|
+
this.config = config;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
optionsWereSet: boolean;
|
|
91
|
+
|
|
92
|
+
setElementValue(skipNotify: boolean) {
|
|
93
|
+
// do not pass the value to the element if options has never been set
|
|
94
|
+
// the value will be passed on when options do arrive
|
|
95
|
+
if (this.optionsWereSet) {
|
|
96
|
+
CustomElement.for<MdcSelect>(this.obj).viewModel.setValue(this.currentValue, skipNotify);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public getValue(): unknown {
|
|
101
|
+
// is it safe to assume the observer has the latest value?
|
|
102
|
+
// todo: ability to turn on/off cache based on type
|
|
103
|
+
return this.observing
|
|
104
|
+
? this.currentValue
|
|
105
|
+
// : this.obj.multiple
|
|
106
|
+
// ? Array.from(this.obj.options).map(o => o.value)
|
|
107
|
+
: this.obj.value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public setValue(newValue: unknown): void {
|
|
111
|
+
this.currentValue = newValue;
|
|
112
|
+
this.hasChanges = newValue !== this.oldValue;
|
|
113
|
+
// this.observeArray(newValue instanceof Array ? newValue : null);
|
|
114
|
+
if (this.optionsWereSet) {
|
|
115
|
+
this.flushChanges();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
120
|
+
public flushChanges(): void {
|
|
121
|
+
if (this.hasChanges) {
|
|
122
|
+
this.hasChanges = false;
|
|
123
|
+
this.synchronizeOptions();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public handleCollectionChange(): void {
|
|
128
|
+
// always sync "selected" property of <options/>
|
|
129
|
+
// immediately whenever the array notifies its mutation
|
|
130
|
+
this.synchronizeOptions();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public notify(): void {
|
|
134
|
+
const oldValue = this.oldValue;
|
|
135
|
+
const newValue = this.currentValue;
|
|
136
|
+
if (newValue === oldValue) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.subs.notify(newValue, oldValue);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public handleEvent(): void {
|
|
143
|
+
const shouldNotify = this.synchronizeValue();
|
|
144
|
+
if (shouldNotify) {
|
|
145
|
+
this.subs.notify(this.currentValue, this.oldValue);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
150
|
+
public synchronizeOptions(_indexMap?: IndexMap): void {
|
|
151
|
+
this.setElementValue(true);
|
|
152
|
+
// const { currentValue, obj } = this;
|
|
153
|
+
// // const isArray = Array.isArray(currentValue);
|
|
154
|
+
// // const matcher = obj.matcher !== void 0 ? obj.matcher : defaultMatcher;
|
|
155
|
+
// // const matcher = defaultMatcher;
|
|
156
|
+
// const options = CustomElement.for<MdcSelect>(obj).viewModel.items;
|
|
157
|
+
// let i = options.length;
|
|
158
|
+
|
|
159
|
+
// while (i-- > 0) {
|
|
160
|
+
// // const option = options[i];
|
|
161
|
+
// // const optionValue = hasOwn.call(option, 'model') ? option.model : option.value;
|
|
162
|
+
// // const optionValue = option.value;
|
|
163
|
+
// // if (isArray) {
|
|
164
|
+
// // option.selected = (currentValue as unknown[]).findIndex(item => !!matcher(optionValue, item)) !== -1;
|
|
165
|
+
// // continue;
|
|
166
|
+
// // }
|
|
167
|
+
// if (!this.optionsWereSet) {
|
|
168
|
+
// this.optionsWereSet = true;
|
|
169
|
+
// this.setElementValue(true);
|
|
170
|
+
// }
|
|
171
|
+
// // option.selected = !!matcher(optionValue, currentValue);
|
|
172
|
+
// }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public synchronizeValue(): boolean {
|
|
176
|
+
// Spec for synchronizing value from `<select/>` to `SelectObserver`
|
|
177
|
+
// When synchronizing value to observed <select/> element, do the following steps:
|
|
178
|
+
// A. If `<select/>` is multiple
|
|
179
|
+
// 1. Check if current value, called `currentValue` is an array
|
|
180
|
+
// a. If not an array, return true to signal value has changed
|
|
181
|
+
// b. If is an array:
|
|
182
|
+
// i. gather all current selected <option/>, in to array called `values`
|
|
183
|
+
// ii. loop through the `currentValue` array and remove items that are nolonger selected based on matcher
|
|
184
|
+
// iii. loop through the `values` array and add items that are selected based on matcher
|
|
185
|
+
// iv. Return false to signal value hasn't changed
|
|
186
|
+
// B. If the select is single
|
|
187
|
+
// 1. Let `value` equal the first selected option, if no option selected, then `value` is `null`
|
|
188
|
+
// 2. assign `this.currentValue` to `this.oldValue`
|
|
189
|
+
// 3. assign `value` to `this.currentValue`
|
|
190
|
+
// 4. return `true` to signal value has changed
|
|
191
|
+
const obj = this.obj;
|
|
192
|
+
const options = CustomElement.for<MdcSelect>(obj).viewModel.items ?? [];
|
|
193
|
+
const len = options.length;
|
|
194
|
+
// const currentValue = this.currentValue;
|
|
195
|
+
let i = 0;
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
if (obj.multiple) {
|
|
199
|
+
// A.
|
|
200
|
+
if (!(currentValue instanceof Array)) {
|
|
201
|
+
// A.1.a
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
// A.1.b
|
|
205
|
+
// multi select
|
|
206
|
+
let option: IOptionElement;
|
|
207
|
+
const matcher = obj.matcher ?? defaultMatcher;
|
|
208
|
+
// A.1.b.i
|
|
209
|
+
const values: unknown[] = [];
|
|
210
|
+
while (i < len) {
|
|
211
|
+
option = options[i];
|
|
212
|
+
if (option.selected) {
|
|
213
|
+
values.push(hasOwn.call(option, 'model')
|
|
214
|
+
? option.model
|
|
215
|
+
: option.value
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
++i;
|
|
219
|
+
}
|
|
220
|
+
// A.1.b.ii
|
|
221
|
+
i = 0;
|
|
222
|
+
while (i < currentValue.length) {
|
|
223
|
+
const a = currentValue[i];
|
|
224
|
+
// Todo: remove arrow fn
|
|
225
|
+
if (values.findIndex(b => !!matcher(a, b)) === -1) {
|
|
226
|
+
currentValue.splice(i, 1);
|
|
227
|
+
} else {
|
|
228
|
+
++i;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// A.1.b.iii
|
|
232
|
+
i = 0;
|
|
233
|
+
while (i < values.length) {
|
|
234
|
+
const a = values[i];
|
|
235
|
+
// Todo: remove arrow fn
|
|
236
|
+
if (currentValue.findIndex(b => !!matcher(a, b)) === -1) {
|
|
237
|
+
currentValue.push(a);
|
|
238
|
+
}
|
|
239
|
+
++i;
|
|
240
|
+
}
|
|
241
|
+
// A.1.b.iv
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
*/
|
|
245
|
+
// B. single select
|
|
246
|
+
// B.1
|
|
247
|
+
let value: unknown = null;
|
|
248
|
+
while (i < len) {
|
|
249
|
+
const option = options[i];
|
|
250
|
+
if (option.value === this.obj.value) {
|
|
251
|
+
// value = hasOwn.call(option, 'model') ? option.model : option.value;
|
|
252
|
+
value = option.value;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
++i;
|
|
256
|
+
}
|
|
257
|
+
// B.2
|
|
258
|
+
this.oldValue = this.currentValue;
|
|
259
|
+
// B.3
|
|
260
|
+
this.currentValue = value;
|
|
261
|
+
// B.4
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private start(): void {
|
|
266
|
+
const vm = CustomElement.for<MdcSelect>(this.obj).viewModel;
|
|
267
|
+
vm.initialised.then(() => {
|
|
268
|
+
(this.nodeObserver = new this.obj.ownerDocument.defaultView!.MutationObserver(records => this.handleNodeChange(records)))
|
|
269
|
+
.observe(vm.menu.root, childObserverOptions);
|
|
270
|
+
// this.observeArray(this.currentValue instanceof Array ? this.currentValue : null);
|
|
271
|
+
this.observing = true;
|
|
272
|
+
if (vm.items?.length) {
|
|
273
|
+
this.optionsWereSet = true;
|
|
274
|
+
this.synchronizeOptions();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private stop(): void {
|
|
280
|
+
this.optionsWereSet = false;
|
|
281
|
+
this.nodeObserver?.disconnect();
|
|
282
|
+
this.arrayObserver?.unsubscribe(this);
|
|
283
|
+
this.nodeObserver
|
|
284
|
+
= this.arrayObserver
|
|
285
|
+
= void 0;
|
|
286
|
+
this.observing = false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// todo: observe all kind of collection
|
|
290
|
+
// private observeArray(array: unknown[] | null): void {
|
|
291
|
+
// this.arrayObserver?.unsubscribe(this);
|
|
292
|
+
// this.arrayObserver = void 0;
|
|
293
|
+
// if (array !== null) {
|
|
294
|
+
// if (!this.obj.multiple) {
|
|
295
|
+
// throw new Error('Only null or Array instances can be bound to a multi-select.');
|
|
296
|
+
// }
|
|
297
|
+
// (this.arrayObserver = this.observerLocator.getArrayObserver(array)).subscribe(this);
|
|
298
|
+
// }
|
|
299
|
+
// }
|
|
300
|
+
|
|
301
|
+
public handleNodeChange(records: MutationRecord[]): void {
|
|
302
|
+
if (records.find(x => x.type === 'childList'
|
|
303
|
+
&& (Array.from(x.addedNodes).find(y => (y as HTMLElement).tagName === 'MDC-LIST-ITEM')
|
|
304
|
+
|| Array.from(x.removedNodes).find(y => (y as HTMLElement).tagName === 'MDC-LIST-ITEM'))
|
|
305
|
+
)) {
|
|
306
|
+
this.optionsWereSet = true;
|
|
307
|
+
this.synchronizeOptions();
|
|
308
|
+
const shouldNotify = this.synchronizeValue();
|
|
309
|
+
if (shouldNotify) {
|
|
310
|
+
this.notify();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
public subscribe(subscriber: ISubscriber): void {
|
|
316
|
+
if (this.subs.add(subscriber) && this.subs.count === 1) {
|
|
317
|
+
for (const e of this.config.events) {
|
|
318
|
+
this.obj.addEventListener(e, this);
|
|
319
|
+
}
|
|
320
|
+
this.listened = true;
|
|
321
|
+
this.start();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public unsubscribe(subscriber: ISubscriber): void {
|
|
326
|
+
if (this.subs.remove(subscriber) && this.subs.count === 0) {
|
|
327
|
+
for (const e of this.config.events) {
|
|
328
|
+
this.obj.removeEventListener(e, this);
|
|
329
|
+
}
|
|
330
|
+
this.listened = false;
|
|
331
|
+
this.stop();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
useConfig(config: INodeObserverConfigBase): void {
|
|
336
|
+
this.config = config;
|
|
337
|
+
if (this.listened) {
|
|
338
|
+
for (const e of this.config.events) {
|
|
339
|
+
this.obj.removeEventListener(e, this);
|
|
340
|
+
}
|
|
341
|
+
for (const e of this.config.events) {
|
|
342
|
+
this.obj.addEventListener(e, this);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|