@shortfuse/materialdesignweb 0.7.6 → 0.8.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.
Files changed (114) hide show
  1. package/README.md +57 -68
  2. package/components/Badge.js +2 -2
  3. package/components/BottomAppBar.js +3 -5
  4. package/components/Box.js +33 -3
  5. package/components/Button.js +48 -21
  6. package/components/Button.md +9 -9
  7. package/components/Card.js +9 -16
  8. package/components/Checkbox.js +45 -36
  9. package/components/CheckboxIcon.js +2 -2
  10. package/components/Chip.js +1 -1
  11. package/components/Dialog.js +228 -359
  12. package/components/DialogActions.js +2 -2
  13. package/components/Divider.js +3 -3
  14. package/components/ExtendedFab.js +4 -8
  15. package/components/Fab.js +1 -2
  16. package/components/FilterChip.js +4 -4
  17. package/components/Headline.js +1 -1
  18. package/components/Icon.js +8 -8
  19. package/components/IconButton.js +9 -14
  20. package/components/Input.js +273 -1
  21. package/components/Layout.js +485 -16
  22. package/components/List.js +6 -4
  23. package/components/ListItem.js +12 -12
  24. package/components/ListOption.js +21 -5
  25. package/components/Listbox.js +239 -0
  26. package/components/Menu.js +77 -526
  27. package/components/MenuItem.js +12 -14
  28. package/components/Nav.js +0 -2
  29. package/components/NavBar.js +8 -79
  30. package/components/NavDrawer.js +12 -11
  31. package/components/NavDrawerItem.js +2 -1
  32. package/components/NavItem.js +18 -8
  33. package/components/NavRail.js +15 -7
  34. package/components/NavRailItem.js +3 -1
  35. package/components/Popup.js +20 -0
  36. package/components/Progress.js +24 -23
  37. package/components/Radio.js +42 -35
  38. package/components/RadioIcon.js +3 -3
  39. package/components/Ripple.js +2 -3
  40. package/components/Search.js +85 -0
  41. package/components/SegmentedButton.js +1 -10
  42. package/components/SegmentedButtonGroup.js +16 -10
  43. package/components/Select.js +4 -4
  44. package/components/Shape.js +1 -1
  45. package/components/Slider.js +43 -50
  46. package/components/Snackbar.js +4 -5
  47. package/components/Surface.js +3 -3
  48. package/components/Switch.js +55 -21
  49. package/components/SwitchIcon.js +10 -8
  50. package/components/Tab.js +11 -9
  51. package/components/TabContent.js +4 -3
  52. package/components/TabList.js +2 -2
  53. package/components/TabPanel.js +11 -8
  54. package/components/TextArea.js +38 -35
  55. package/components/Tooltip.js +2 -2
  56. package/components/TopAppBar.js +65 -147
  57. package/core/Composition.js +985 -628
  58. package/core/CompositionAdapter.js +315 -0
  59. package/core/CustomElement.js +153 -90
  60. package/core/DomAdapter.js +586 -0
  61. package/core/ICustomElement.d.ts +2 -2
  62. package/core/css.js +8 -7
  63. package/core/customTypes.js +53 -31
  64. package/{utils → core}/jsonMergePatch.js +36 -14
  65. package/core/observe.js +111 -57
  66. package/core/optimizations.js +23 -0
  67. package/core/template.js +17 -11
  68. package/core/test.js +126 -0
  69. package/core/typings.d.ts +11 -5
  70. package/core/uid.js +13 -0
  71. package/dist/index.min.js +83 -152
  72. package/dist/index.min.js.map +4 -4
  73. package/dist/meta.json +1 -1
  74. package/mixins/AriaReflectorMixin.js +1 -2
  75. package/mixins/AriaToolbarMixin.js +2 -3
  76. package/mixins/ControlMixin.js +25 -17
  77. package/mixins/DensityMixin.js +0 -1
  78. package/mixins/FlexableMixin.js +1 -2
  79. package/mixins/FormAssociatedMixin.js +13 -10
  80. package/mixins/InputMixin.js +2 -9
  81. package/mixins/KeyboardNavMixin.js +14 -1
  82. package/mixins/PopupMixin.js +757 -0
  83. package/mixins/RTLObserverMixin.js +0 -1
  84. package/mixins/ResizeObserverMixin.js +0 -1
  85. package/mixins/RippleMixin.js +3 -4
  86. package/mixins/ScrollListenerMixin.js +41 -32
  87. package/mixins/SemiStickyMixin.js +151 -0
  88. package/mixins/ShapeMixin.js +29 -24
  89. package/mixins/StateMixin.js +11 -6
  90. package/mixins/SurfaceMixin.js +3 -57
  91. package/mixins/TextFieldMixin.js +57 -65
  92. package/mixins/ThemableMixin.js +78 -156
  93. package/mixins/TooltipTriggerMixin.js +7 -13
  94. package/mixins/TouchTargetMixin.js +4 -3
  95. package/package.json +9 -5
  96. package/theming/index.js +1 -1
  97. package/theming/themableMixinLoader.js +12 -0
  98. package/utils/{hct → material-color}/blend.js +8 -10
  99. package/utils/{hct → material-color/hct}/Cam16.js +196 -69
  100. package/utils/{hct → material-color/hct}/Hct.js +61 -19
  101. package/utils/{hct → material-color/hct}/ViewingConditions.js +3 -3
  102. package/utils/{hct → material-color/hct}/hctSolver.js +9 -16
  103. package/utils/{hct → material-color}/helper.js +11 -18
  104. package/utils/{hct → material-color/palettes}/CorePalette.js +79 -19
  105. package/utils/{hct → material-color/palettes}/TonalPalette.js +12 -4
  106. package/utils/material-color/scheme/Scheme.js +376 -0
  107. package/utils/{hct/colorUtils.js → material-color/utils/color.js} +61 -1
  108. package/utils/popup.js +46 -25
  109. package/components/ListSelect.js +0 -220
  110. package/components/Option.js +0 -91
  111. package/components/Pane.js +0 -281
  112. package/core/identify.js +0 -40
  113. package/utils/hct/Scheme.js +0 -587
  114. /package/utils/{hct/mathUtils.js → material-color/utils/math.js} +0 -0
@@ -1,8 +1,5 @@
1
1
  /** @typedef {import('./CustomElement').default} CustomElement */
2
2
 
3
- /** @type {WeakMap<HTMLElement, EventListener>} */
4
- const eventHandlerValues = new Map();
5
-
6
3
  /**
7
4
  * @see https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes
8
5
  * @type {import('./typings.js').ObserverOptions<'function',EventListener, unknown>}
@@ -11,11 +8,10 @@ export const EVENT_HANDLER_TYPE = {
11
8
  type: 'function',
12
9
  reflect: 'read',
13
10
  value: null,
14
- values: eventHandlerValues,
15
11
  parser(v) { return v; },
16
12
  attributeChangedCallback(name, oldValue, newValue) {
17
13
  if (oldValue == null && newValue == null) return;
18
- // Must continue if oldValue === newValue;
14
+ // Must continue even if oldValue === newValue;
19
15
  if (newValue == null) {
20
16
  this[name] = null;
21
17
  return;
@@ -49,39 +45,51 @@ export const WEAKREF_TYPE = {
49
45
  type: 'object',
50
46
  reflect: false,
51
47
  value: null,
52
- values: weakRefValues,
53
48
  parser(v) { return new WeakRef(v); },
54
49
  get() {
55
- return weakRefValues.get(this)?.deref();
50
+ if (weakRefValues.has(this)) {
51
+ return weakRefValues.get(this).deref();
52
+ }
53
+ return undefined;
56
54
  },
57
55
  };
58
56
 
59
- /** @type {WeakMap<any, Animation>} */
60
- const elementStylerLastAnimation = new WeakMap();
61
- /** @type {WeakMap<CustomElement, ElementStylerOptions>} */
62
- const elementStylerValues = new WeakMap();
63
- /** @type {WeakSet<any>} */
64
- const elementStylerHasQueue = new WeakSet();
65
-
66
57
  /**
67
58
  * @typedef {Object} ElementStylerOptions
68
- * @prop {string} target Target ID
59
+ * @prop {string|HTMLElement|null} target tag, element or null (host)
69
60
  * @prop {Keyframe} styles
70
61
  * @prop {EffectTiming} [timing]
71
62
  */
72
63
 
73
- /** @this {CustomElement} */
74
- function elementStylerRAFCallback() {
75
- let previousAnimation = elementStylerLastAnimation.get(this);
76
- const value = elementStylerValues.get(this);
64
+ /** @type {WeakMap<CustomElement, Set<string>} */
65
+ const queuedPropsByElement = new WeakMap();
66
+
67
+ /** @type {WeakMap<CustomElement, Map<string, Animation>>} */
68
+ const previousAnimationsByElement = new WeakMap();
69
+
70
+ /**
71
+ * @param {string} name
72
+ * @this {CustomElement}
73
+ */
74
+ function elementStylerMicrotaskCallback(name) {
75
+ const previousAnimations = previousAnimationsByElement.get(this);
76
+ /** @type {Animation} */
77
+ let previousAnimation;
78
+ if (previousAnimations?.has(name)) {
79
+ previousAnimation = previousAnimations.get(name);
80
+ }
81
+ const value = this[name];
77
82
  if (!value) {
78
83
  previousAnimation?.cancel();
79
84
  return;
80
85
  }
86
+ const { target, styles, timing } = value;
81
87
  /** @type {HTMLElement} */
82
- const el = value.target ? this.composition.getElement(this.shadowRoot, value.target) : this;
83
- const currentAnimation = el.animate(value.styles, {
84
- ...value.timing,
88
+ const el = target
89
+ ? (typeof target === 'string' ? this.refs[target] : target)
90
+ : this;
91
+ const currentAnimation = el.animate(styles, {
92
+ ...timing,
85
93
  fill: 'forwards',
86
94
  });
87
95
  currentAnimation.onremove = () => {
@@ -93,22 +101,32 @@ function elementStylerRAFCallback() {
93
101
  previousAnimation?.cancel();
94
102
  previousAnimation = null;
95
103
  };
96
- elementStylerLastAnimation.set(this, currentAnimation);
97
- elementStylerHasQueue.delete(this);
104
+ if (previousAnimations) {
105
+ previousAnimations.set(name, currentAnimation);
106
+ } else {
107
+ previousAnimationsByElement.set(this, new Map([[name, currentAnimation]]));
108
+ }
109
+ queuedPropsByElement.get(this).delete(name);
98
110
  }
99
111
 
100
112
  /** @type {import('./typings.js').ObserverOptions<'object',ElementStylerOptions, CustomElement>} */
101
113
  export const ELEMENT_STYLER_TYPE = {
102
114
  type: 'object',
103
115
  reflect: false,
104
- values: elementStylerValues,
105
116
  diff: null, // Skip computing entire change
106
- changedCallback(oldValue, newValue) {
107
- const hasQueue = elementStylerHasQueue.has(this);
117
+ propChangedCallback(name, oldValue, newValue) {
118
+ if (!this.isConnected) return;
119
+ const queuedProps = queuedPropsByElement.get(this);
120
+ let hasQueue = false;
121
+ if (queuedProps?.has(name)) {
122
+ hasQueue = true;
123
+ }
108
124
  if (!newValue) {
109
125
  if (!hasQueue) return;
110
- console.warn('debug needed of cancel needed');
111
- elementStylerHasQueue.delete(this);
126
+ console.warn('debug of cancel needed');
127
+ if (queuedProps) {
128
+ queuedProps.delete(name);
129
+ }
112
130
  return;
113
131
  }
114
132
 
@@ -117,9 +135,13 @@ export const ELEMENT_STYLER_TYPE = {
117
135
  return;
118
136
  }
119
137
 
138
+ if (queuedProps) {
139
+ queuedProps.add(name);
140
+ } else {
141
+ queuedPropsByElement.set(this, new Set([name]));
142
+ }
120
143
  // Animation styles may trickle in steps, so queue a microtask before doing any work.
121
144
  // Using requestAnimationFrame would fire one frame too late for CSS animations already scheduled
122
- queueMicrotask(elementStylerRAFCallback.bind(this));
123
- elementStylerHasQueue.add(this);
145
+ queueMicrotask(elementStylerMicrotaskCallback.bind(this, name));
124
146
  },
125
147
  };
@@ -9,8 +9,8 @@
9
9
  */
10
10
  export function applyMergePatch(target, patch) {
11
11
  if (target === patch) return target;
12
- if (patch == null || typeof patch !== 'object') return patch;
13
- if (target != null && typeof target !== 'object') {
12
+ if (target == null || patch == null || typeof patch !== 'object') return patch;
13
+ if (typeof target !== 'object') {
14
14
  target = {};
15
15
  }
16
16
  for (const [key, value] of Object.entries(patch)) {
@@ -28,26 +28,52 @@ export function applyMergePatch(target, patch) {
28
28
  /**
29
29
  * Creates a JSON Merge patch based
30
30
  * Allows different strategies for arrays
31
- * - `clone`: Per spec, clones all entries with no inspection.
31
+ * - `reference`: Per spec, returns array as is
32
+ * - `clone`: Clones all entries with no inspection.
32
33
  * - `object`: Convert to flattened, array-like objects. Requires
33
34
  * consumer of patch to be aware of the schema beforehand.
34
35
  * @param {object|number|string|boolean} previous
35
36
  * @param {object|number|string|boolean} current
36
- * @param {'clone'|'object'} [arrayStrategy='clone']
37
+ * @param {'clone'|'object'|'reference'} [arrayStrategy='reference']
37
38
  * @return {any} Patch
38
39
  */
39
- export function buildMergePatch(previous, current, arrayStrategy = 'clone') {
40
+ export function buildMergePatch(previous, current, arrayStrategy = 'reference') {
40
41
  if (previous === current) return null;
41
42
  if (current == null || typeof current !== 'object') return current;
42
43
  if (previous == null || typeof previous !== 'object') {
43
44
  return structuredClone(current);
44
45
  }
45
- const isArray = Array.isArray(current);
46
- if (isArray && arrayStrategy === 'clone') {
47
- return structuredClone(current);
48
- }
49
46
 
50
47
  const patch = {};
48
+ if (Array.isArray(current)) {
49
+ if (arrayStrategy === 'reference') {
50
+ return current;
51
+ }
52
+ // Assume previous is array
53
+ if (arrayStrategy === 'clone') {
54
+ return structuredClone(current);
55
+ }
56
+ for (const [index, value] of current.entries()) {
57
+ if (value == null) {
58
+ console.warn('Nullish value found at', index);
59
+ continue;
60
+ }
61
+ const changes = buildMergePatch(previous[index], value, arrayStrategy);
62
+ if (changes === null) {
63
+ continue;
64
+ } else {
65
+ patch[index] = changes;
66
+ }
67
+ }
68
+ // for (let i = current.length; i < previous.length; i++) {
69
+ // patch[i] = null;
70
+ // }
71
+ if (current.length !== previous.length) {
72
+ patch.length = current.length;
73
+ }
74
+ return patch;
75
+ }
76
+
51
77
  const previousKeys = new Set(Object.keys(previous));
52
78
  for (const [key, value] of Object.entries(current)) {
53
79
  previousKeys.delete(key);
@@ -57,7 +83,7 @@ export function buildMergePatch(previous, current, arrayStrategy = 'clone') {
57
83
  }
58
84
  const changes = buildMergePatch(previous[key], value, arrayStrategy);
59
85
  if (changes === null) {
60
- console.log('keeping', key);
86
+ // console.log('keeping', key);
61
87
  } else {
62
88
  patch[key] = changes;
63
89
  }
@@ -67,10 +93,6 @@ export function buildMergePatch(previous, current, arrayStrategy = 'clone') {
67
93
  console.log('removing', key);
68
94
  }
69
95
 
70
- if (isArray && arrayStrategy === 'object' && current.length !== previous.length) {
71
- patch.length = current.length;
72
- }
73
-
74
96
  return patch;
75
97
  }
76
98
 
package/core/observe.js CHANGED
@@ -1,6 +1,5 @@
1
- import { buildMergePatch, hasMergePatch } from '../utils/jsonMergePatch.js';
2
-
3
1
  import { attrNameFromPropName } from './dom.js';
2
+ import { buildMergePatch, hasMergePatch } from './jsonMergePatch.js';
4
3
 
5
4
  /** @typedef {import('./typings.js').ObserverPropertyType} ObserverPropertyType */
6
5
 
@@ -43,15 +42,24 @@ const DEFAULT_OBJECT_PARSER = (o) => o;
43
42
  * @param {T} b
44
43
  * @return {boolean} true if equal
45
44
  */
46
- export const DEFAULT_OBJECT_COMPARATOR = (a, b) => !hasMergePatch(a, b);
45
+ const DEFAULT_OBJECT_COMPARATOR = (a, b) => !hasMergePatch(a, b);
47
46
 
48
47
  /**
48
+ * Always invoke change on set
49
49
  * @template T
50
50
  * @param {T} a
51
51
  * @param {T} b
52
52
  * @return {boolean} true if equal
53
53
  */
54
- export const DEFAULT_OBJECT_DIFF = (a, b) => buildMergePatch(a, b, 'object');
54
+ const DEFAULT_ARRAY_COMPARATOR = (a, b) => false;
55
+
56
+ /**
57
+ * @template T
58
+ * @param {T} a
59
+ * @param {T} b
60
+ * @return {boolean} true if equal
61
+ */
62
+ const DEFAULT_OBJECT_DIFF = (a, b) => buildMergePatch(a, b, 'reference');
55
63
 
56
64
  /**
57
65
  * @param {ObserverPropertyType} type
@@ -78,6 +86,48 @@ function emptyFromType(type) {
78
86
  }
79
87
  }
80
88
 
89
+ /**
90
+ * @template {Object} T
91
+ * @param {T} proxyTarget
92
+ * @param {Set<string>} set
93
+ * @param {Set<string>} deepSet
94
+ * @param {string} [prefix]
95
+ * @return {T}
96
+ */
97
+ function buildProxy(proxyTarget, set, deepSet, prefix) {
98
+ proxyTarget ??= {};
99
+ return new Proxy(proxyTarget, {
100
+ get(target, p) {
101
+ const value = target[p];
102
+ if (typeof p !== 'symbol') {
103
+ const arg = prefix ? `${prefix}.${p}` : p;
104
+ if (prefix) {
105
+ deepSet.add(arg);
106
+ } else {
107
+ set.add(arg);
108
+ }
109
+ if (typeof value === 'object' && value != null) {
110
+ console.debug('tried to arg poke object get', p, value);
111
+ return buildProxy(value, set, deepSet, arg);
112
+ }
113
+ }
114
+ return value;
115
+ },
116
+ has(target, p) {
117
+ const value = Reflect.has(target, p);
118
+ if (typeof p !== 'symbol') {
119
+ const arg = prefix ? `${prefix}.p` : p;
120
+ if (prefix) {
121
+ deepSet.add(arg);
122
+ } else {
123
+ set.add(arg);
124
+ }
125
+ }
126
+ return value;
127
+ },
128
+ });
129
+ }
130
+
81
131
  /**
82
132
  * @param {ObserverPropertyType} type
83
133
  * @return {any}
@@ -98,7 +148,7 @@ function defaultParserFromType(type) {
98
148
  case 'object':
99
149
  return DEFAULT_OBJECT_PARSER;
100
150
  case 'array':
101
- return Array.from;
151
+ return DEFAULT_OBJECT_PARSER;
102
152
  default:
103
153
  case 'string':
104
154
  return DEFAULT_STRING_PARSER;
@@ -166,7 +216,7 @@ export function parseObserverOptions(name, typeOrOptions, object) {
166
216
  if (!isFn) {
167
217
  isFn = parsedType === 'object'
168
218
  ? DEFAULT_OBJECT_COMPARATOR
169
- : Object.is;
219
+ : ((parsedType === 'array') ? DEFAULT_ARRAY_COMPARATOR : Object.is);
170
220
  }
171
221
 
172
222
  const diff = 'diff' in options
@@ -217,57 +267,56 @@ export function parsePropertyValue(value) {
217
267
 
218
268
  /**
219
269
  * @param {(data: Partial<any>) => any} fn
220
- * @param {any} arg0
221
- * @param {any} args[]
222
270
  * @param {...any} args
223
271
  * @this {any}
224
- * @return {{props:Set<string>, defaultValue:any, reusable: boolean}}
272
+ * @return {{
273
+ * props: {
274
+ * this: string[],
275
+ * args: string[][],
276
+ * },
277
+ * deepPropStrings: {
278
+ * this: string[],
279
+ * args: string[][],
280
+ * },
281
+ * deepProps: {
282
+ * this: string[][],
283
+ * args: string[][][],
284
+ * },
285
+ * defaultValue: any,
286
+ * reusable: boolean,
287
+ * }}
225
288
  */
226
- export function observeFunction(fn, arg0, ...args) {
227
- const argPoked = new Set();
289
+ export function observeFunction(fn, ...args) {
290
+ /** @type {Set<string>} */
228
291
  const thisPoked = new Set();
229
-
230
- /**
231
- * @template {Object} T
232
- * @param {T} proxyTarget
233
- * @param {Set<string>} set
234
- * @param {string} [prefix]
235
- * @return {T}
236
- */
237
- function buildProxy(proxyTarget, set, prefix) {
238
- return new Proxy(proxyTarget, {
239
- get(target, p) {
240
- const arg = prefix ? `${prefix}.${p}` : p;
241
- set.add(arg);
242
- const value = Reflect.get(target, p);
243
- if (typeof value === 'object' && value != null) {
244
- console.debug('tried to arg poke object get', p, value);
245
- return buildProxy(value, set, arg);
246
- }
247
- return value;
248
- },
249
- has(target, p) {
250
- const arg = prefix ? `${prefix}.p` : p;
251
- set.add(arg);
252
- const value = Reflect.has(target, p);
253
- return value;
254
- },
255
- });
256
- }
257
-
258
- const argProxy = buildProxy(arg0, argPoked);
259
- const thisProxy = buildProxy(this ?? arg0, thisPoked);
260
- const defaultValue = fn.call(thisProxy, argProxy, ...args);
292
+ /** @type {Set<string>} */
293
+ const thisPokedDeep = new Set();
294
+
295
+ const argWatchers = args.map((arg) => {
296
+ const poked = new Set();
297
+ const pokedDeep = new Set();
298
+ const proxy = buildProxy(arg, poked, pokedDeep);
299
+ return { poked, pokedDeep, proxy };
300
+ });
301
+
302
+ const thisProxy = buildProxy(this ?? {}, thisPoked, thisPokedDeep);
303
+ const defaultValue = fn.apply(thisProxy, argWatchers.map((watcher) => watcher.proxy));
261
304
  /* Arrow functions can reused if they don't poke `this` */
262
305
  const reusable = fn.name ? true : !thisPoked.size;
263
306
 
264
- const props = new Set([
265
- ...argPoked,
266
- ...thisPoked,
267
- ]);
268
-
269
307
  return {
270
- props,
308
+ props: {
309
+ this: [...thisPoked],
310
+ args: argWatchers.map((watcher) => [...watcher.poked]),
311
+ },
312
+ deepPropStrings: {
313
+ this: [...thisPokedDeep],
314
+ args: argWatchers.map((watcher) => [...watcher.pokedDeep]),
315
+ },
316
+ deepProps: {
317
+ this: [...thisPokedDeep].map((deepPropString) => deepPropString.split('.')),
318
+ args: argWatchers.map((watcher) => [...watcher.pokedDeep].map((deepPropString) => deepPropString.split('.'))),
319
+ },
271
320
  defaultValue,
272
321
  reusable,
273
322
  };
@@ -297,7 +346,6 @@ export function defineObservableProperty(object, key, options) {
297
346
  * @return {boolean} changed
298
347
  */
299
348
  function detectChange(oldValue, value) {
300
- if (oldValue === value) return false;
301
349
  if (config.get) {
302
350
  // TODO: Custom getter vs parser
303
351
  }
@@ -310,12 +358,11 @@ export function defineObservableProperty(object, key, options) {
310
358
  if (oldValue == null) {
311
359
  if (newValue == null) return false; // Both nullish
312
360
  } else if (newValue != null) {
313
- if (oldValue === newValue) return false;
361
+ // if (oldValue === newValue) return false;
314
362
  if (config.diff) {
315
363
  changes = config.diff.call(this, oldValue, newValue);
316
364
  if (changes == null) return false;
317
- }
318
- if (config.is.call(this, oldValue, newValue)) return false;
365
+ } else if (config.is.call(this, oldValue, newValue)) return false;
319
366
  }
320
367
 
321
368
  config.values.set(this, newValue);
@@ -356,15 +403,22 @@ export function defineObservableProperty(object, key, options) {
356
403
  if (config.get) {
357
404
  // Custom `get` uses computed values.
358
405
  // Invalidate computed value when dependent `prop` changes
359
- const { props } = observeFunction(config.get.bind(object), object, internalGet.bind(object));
360
- config.watchers.push(
361
- ...[...props].map((prop) => [prop, onInvalidate]),
362
- );
406
+ const observeResult = observeFunction(config.get.bind(object), object, internalGet.bind(object));
407
+ const uniqueProps = new Set([
408
+ ...observeResult.props.this,
409
+ ...observeResult.props.args[0],
410
+ ]);
411
+ for (const prop of uniqueProps) {
412
+ // @ts-ignore keyof C
413
+ config.watchers.push([prop, onInvalidate]);
414
+ }
415
+
363
416
  // TODO: May be able to cache value if props are present
364
417
  }
365
418
  /** @type {Partial<PropertyDescriptor>} */
366
419
  const descriptor = {
367
420
  enumerable: config.enumerable,
421
+ configurable: true,
368
422
  /**
369
423
  * @this {C}
370
424
  * @return {T2}
@@ -0,0 +1,23 @@
1
+ // Micro-optimized functions
2
+
3
+ const BLANK_TEXT = new Text();
4
+ const BLANK_COMMENT = new Comment();
5
+ const BLANK_DIV = document.createElement('div');
6
+
7
+ /** @return {Text} */
8
+ export function createEmptyTextNode() {
9
+ // @ts-ignore
10
+ return BLANK_TEXT.cloneNode();
11
+ }
12
+
13
+ /** @return {HTMLDivElement} */
14
+ export function createEmptyDiv() {
15
+ // @ts-ignore
16
+ return BLANK_DIV.cloneNode();
17
+ }
18
+
19
+ /** @return {Comment} */
20
+ export function createEmptyComment() {
21
+ // @ts-ignore
22
+ return BLANK_COMMENT.cloneNode();
23
+ }
package/core/template.js CHANGED
@@ -1,4 +1,4 @@
1
- import { generateUID } from './identify.js';
1
+ import { generateUID } from './uid.js';
2
2
 
3
3
  /**
4
4
  * Property are bound to an ID+Node
@@ -6,7 +6,10 @@ import { generateUID } from './identify.js';
6
6
  * @template {any} T
7
7
  * @typedef {Object} InlineFunctionEntry
8
8
  * @prop {(data:T) => any} fn
9
- * @prop {Set<keyof T & string>} [props]
9
+ * @prop {string[]} [props]
10
+ * @prop {string[][]} [deepProps]
11
+ * @prop {string[]} [injectionProps]
12
+ * @prop {string[][]} [injectionDeepProps]
10
13
  * @prop {T} [defaultValue]
11
14
  */
12
15
 
@@ -57,7 +60,7 @@ const styleElementCache = new Map();
57
60
 
58
61
  /**
59
62
  * @param {TemplateStringsArray} array
60
- * @param {...(string)} substitutions
63
+ * @param {...any} substitutions
61
64
  * @return {HTMLStyleElement|CSSStyleSheet}
62
65
  */
63
66
  export function css(array, ...substitutions) {
@@ -76,18 +79,20 @@ export function css(array, ...substitutions) {
76
79
  }
77
80
 
78
81
  if (_cssStyleSheetConstructable) {
79
- let sheet = cssStyleSheetsCache.get(content);
80
- if (!sheet) {
81
- sheet = new CSSStyleSheet();
82
- _cssStyleSheetConstructable = true;
83
- sheet.replaceSync(content);
84
- cssStyleSheetsCache.set(content, sheet);
82
+ if (cssStyleSheetsCache.has(content)) {
83
+ return cssStyleSheetsCache.get(content);
85
84
  }
85
+ const sheet = new CSSStyleSheet();
86
+ _cssStyleSheetConstructable = true;
87
+ sheet.replaceSync(content);
88
+ cssStyleSheetsCache.set(content, sheet);
86
89
  return sheet;
87
90
  }
88
91
 
89
- let style = styleElementCache.get(content);
90
- if (!style) {
92
+ let style;
93
+ if (styleElementCache.has(content)) {
94
+ style = styleElementCache.get(content);
95
+ } else {
91
96
  _inactiveDocument ??= document.implementation.createHTMLDocument();
92
97
  style = _inactiveDocument.createElement('style');
93
98
  style.textContent = content;
@@ -146,3 +151,4 @@ export function html(strings, ...substitutions) {
146
151
 
147
152
  return /** @type {DocumentFragment} */ (fragment.cloneNode(true));
148
153
  }
154
+