native-document 1.0.186 → 1.0.188

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 (32) hide show
  1. package/package.json +1 -1
  2. package/router.js +2 -2
  3. package/src/components/avatar/Avatar.js +7 -2
  4. package/src/components/button/CopyButton.js +37 -0
  5. package/src/components/button/PasteButton.js +32 -0
  6. package/src/components/button/ResetButton.js +5 -0
  7. package/src/components/button/SubmitButton.js +5 -0
  8. package/src/components/button/index.js +8 -0
  9. package/src/components/button/types/Button.d.ts +0 -2
  10. package/src/components/button/types/CopyButton.d.ts +16 -0
  11. package/src/components/button/types/PasteButton.d.ts +16 -0
  12. package/src/components/button/types/ResetButton.d.ts +6 -0
  13. package/src/components/button/types/SubmitButton.d.ts +6 -0
  14. package/src/components/dropdown/Dropdown.js +1 -1
  15. package/src/components/dropdown/DropdownItem.js +9 -0
  16. package/src/components/dropdown/helpers.js +6 -0
  17. package/src/components/dropdown/types/DropdownItem.d.ts +1 -0
  18. package/src/components/form/field/types/RadioField.js +10 -0
  19. package/src/components/form/types/fields/RadioField.d.ts +2 -0
  20. package/src/components/index.d.ts +4 -0
  21. package/src/components/table/DataTable.js +8 -4
  22. package/src/core/data/ObservableArray.js +7 -3
  23. package/src/core/data/ObservableItem.js +6 -1
  24. package/src/core/wrappers/AttributesWrapper.js +7 -7
  25. package/src/core/wrappers/template-cloner/NodeCloner.js +72 -62
  26. package/src/router/Route.js +14 -3
  27. package/src/ui/components/button/CopyButtonRender.js +13 -0
  28. package/src/ui/components/button/PasteButtonRender.js +14 -0
  29. package/src/ui/components/form/fields/FieldRender.js +10 -8
  30. package/src/ui/components/form/fields/RadioFieldRender.js +7 -2
  31. package/src/ui/components/popover/PopoverRender.js +2 -1
  32. package/src/ui/index.js +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.186",
3
+ "version": "1.0.188",
4
4
  "description": "A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features",
5
5
  "author": "AfroCodeur <https://github.com/afrocodeur>",
6
6
  "license": "MIT",
package/router.js CHANGED
@@ -7,5 +7,5 @@ import { Link } from './src/router/link.js';
7
7
  export {
8
8
  RouteParamPatterns,
9
9
  Router,
10
- Link
11
- }
10
+ Link,
11
+ };
@@ -123,13 +123,18 @@ Avatar.prototype.alt = function(alt) {
123
123
 
124
124
  /**
125
125
  * Sets the name associated with the avatar
126
- * @param {string} name - The name
126
+ * @param {string|Observable} name - The name
127
127
  * @returns {Avatar}
128
128
  */
129
129
  Avatar.prototype.name = function(name) {
130
130
  this.$description.name = name;
131
131
  if(!this.$description.initials) {
132
- this.$description.initials = name.split(' ').map(n => n[0]).join('');
132
+ if(name.__$Observable) {
133
+ this.$description.initials = name.format((value) => value.split(' ').map(n => n[0]).join(''));
134
+ }
135
+ else {
136
+ this.$description.initials = name.split(' ').map(n => n[0]).join('');
137
+ }
133
138
  }
134
139
  return this;
135
140
  };
@@ -0,0 +1,37 @@
1
+ import BaseComponent from '../BaseComponent';
2
+ import Button from './Button';
3
+
4
+
5
+ export default function CopyButton(content, props){
6
+
7
+ if(!(this instanceof CopyButton)) {
8
+ return new CopyButton(content, props);
9
+ }
10
+
11
+ Button.call(this, content, props);
12
+
13
+ Object.assign(this.$description, {
14
+ target: null,
15
+ content,
16
+ props,
17
+ });
18
+
19
+ };
20
+
21
+ BaseComponent.extends(CopyButton, Button);
22
+
23
+ CopyButton.defaultTemplate = null;
24
+ CopyButton.use = function (render){
25
+ CopyButton.defaultTemplate = render;
26
+ };
27
+
28
+ /**
29
+ *
30
+ * @param {Observable} $observable
31
+ * @returns {CopyButton}
32
+ */
33
+ CopyButton.prototype.from = function($observable) {
34
+ this.$description.target = $observable;
35
+ return this;
36
+ };
37
+
@@ -0,0 +1,32 @@
1
+ import Button from './Button';
2
+ import BaseComponent from '../BaseComponent';
3
+
4
+
5
+ export default function PasteButton(content, props) {
6
+
7
+ if(!(this instanceof PasteButton)) {
8
+ return new PasteButton(content, props);
9
+ }
10
+
11
+ Button.call(this, content, props);
12
+
13
+ Object.assign(this.$description, {
14
+ content,
15
+ props,
16
+ target: null,
17
+ });
18
+ }
19
+
20
+ BaseComponent.extends(PasteButton, Button);
21
+
22
+
23
+ PasteButton.defaultTemplate = null;
24
+ PasteButton.use = function (render){
25
+ PasteButton.defaultTemplate = render;
26
+ };
27
+
28
+
29
+ PasteButton.prototype.into = function($observable) {
30
+ this.$description.target = $observable;
31
+ return this;
32
+ };
@@ -0,0 +1,5 @@
1
+ import Button from './Button';
2
+
3
+ export default function ResetButton(content, props) {
4
+ return Button(content, props).type('reset');
5
+ }
@@ -0,0 +1,5 @@
1
+ import Button from './Button';
2
+
3
+ export default function SubmitButton(content, props){
4
+ return Button(content, props).type('submit');
5
+ }
@@ -1,5 +1,13 @@
1
1
  import Button from './Button';
2
+ import SubmitButton from './SubmitButton';
3
+ import ResetButton from './ResetButton';
4
+ import PasteButton from './PasteButton';
5
+ import CopyButton from './CopyButton';
2
6
 
3
7
  export {
4
8
  Button,
9
+ SubmitButton,
10
+ ResetButton,
11
+ PasteButton,
12
+ CopyButton,
5
13
  };
@@ -53,10 +53,8 @@ export interface ButtonInterface extends BaseComponent {
53
53
  export declare function Button(label: ValidChild, props?: Record<string, unknown>): ButtonInterface;
54
54
  export declare namespace Button {
55
55
 
56
-
57
56
  function use(template: (description: ButtonDescription, instance: ButtonInterface) => ValidChild): void;
58
57
  function preset(name: string, callback: (instance: ButtonInterface) => ButtonInterface): void;
59
58
  function presets(presets: Record<string, (instance: ButtonInterface) => ButtonInterface>): void;
60
59
 
61
-
62
60
  }
@@ -0,0 +1,16 @@
1
+ import {ButtonInterface} from "./Button";
2
+ import {ObservableItem} from "../../../../types/observable";
3
+ import {ValidChild} from "../../../../types/elements";
4
+
5
+
6
+ export interface CopyButtonInterface extends ButtonInterface{
7
+
8
+ from($observable: ObservableItem): this;
9
+
10
+ }
11
+ export declare function CopyButton(label: ValidChild, props?: Record<string, unknown>): CopyButtonInterface;
12
+ export declare namespace CopyButton {
13
+
14
+ function use(template: (description: ButtonInterface, instance: ButtonInterface) => ValidChild): void;
15
+
16
+ }
@@ -0,0 +1,16 @@
1
+ import {ButtonDescription, ButtonInterface} from "./Button";
2
+ import {ObservableItem} from "../../../../types/observable";
3
+ import type {ValidChild} from "../../../../types/elements";
4
+
5
+
6
+ export interface PasteButtonInterface extends ButtonInterface{
7
+
8
+ into($observable: ObservableItem): this;
9
+
10
+ }
11
+ export declare function PasteButton(label: ValidChild, props?: Record<string, unknown>): PasteButtonInterface;
12
+ export declare namespace PasteButton {
13
+
14
+ function use(template: (description: ButtonDescription, instance: ButtonInterface) => ValidChild): void;
15
+
16
+ }
@@ -0,0 +1,6 @@
1
+ import {ButtonInterface} from "./Button";
2
+
3
+
4
+ export interface ResetButton extends ButtonInterface {
5
+
6
+ }
@@ -0,0 +1,6 @@
1
+ import {ButtonInterface} from "./Button";
2
+
3
+
4
+ export interface SubmitButton extends ButtonInterface {
5
+
6
+ }
@@ -308,7 +308,7 @@ Dropdown.prototype.trigger = function(trigger, includeTriggerIntoGhost = true) {
308
308
  * @returns {this}
309
309
  */
310
310
  Dropdown.prototype.add = function(item, props = {}) {
311
- this.$description.items.push(normalizeDropdownItem(item, null, props));
311
+ this.$description.items.push(normalizeDropdownItem(item, props));
312
312
  return this;
313
313
  };
314
314
 
@@ -36,6 +36,7 @@ export default function DropdownItem(props = {}) {
36
36
  data: null,
37
37
  render: null,
38
38
  renderContent: null,
39
+ action: null,
39
40
  props,
40
41
  };
41
42
  this.aria = { 'role': 'option', 'tabindex': '-1' };
@@ -115,6 +116,14 @@ DropdownItem.prototype.content = function(content) {
115
116
  this.$description.content = content;
116
117
  return this;
117
118
  };
119
+ /**
120
+ * @param {Function} action
121
+ * @returns {this}
122
+ */
123
+ DropdownItem.prototype.action = function(action) {
124
+ this.$description.action = action;
125
+ return this;
126
+ };
118
127
 
119
128
  /**
120
129
  * @param {*} data
@@ -18,6 +18,12 @@ export const normalizeDropdownItem = (raw, mapper = null, props = {}) => {
18
18
  return raw;
19
19
  }
20
20
 
21
+ if(typeof raw === 'function') {
22
+ const item = new DropdownItem({});
23
+ raw(item);
24
+ return item;
25
+ }
26
+
21
27
  let config;
22
28
 
23
29
  if(typeof mapper === 'function') {
@@ -25,6 +25,7 @@ export interface DropdownItemInterface extends BaseComponent {
25
25
  content(content: ValidChild): this;
26
26
  data(data: unknown): this;
27
27
  getData(): unknown;
28
+ action(action: Function): this;
28
29
  shortcut(shortcut: ValidChild): this;
29
30
  render(template: (description: DropdownItemDescription, instance: DropdownItemInterface) => ValidChild): this;
30
31
  renderContent(callback: (item: DropdownItemInterface) => ValidChild): this;
@@ -39,6 +39,7 @@ export default function RadioField(name, props = {}) {
39
39
  options: [],
40
40
  layout: 'vertical',
41
41
  checked: false,
42
+ renderItem: null,
42
43
  });
43
44
  }
44
45
 
@@ -142,4 +143,13 @@ RadioField.prototype.vertical = function() {
142
143
  RadioField.prototype.grid = function() {
143
144
  this.$description.layout = 'grid';
144
145
  return this;
146
+ };
147
+
148
+ /**
149
+ * @param {Function} renderFn
150
+ * @returns {RadioField}
151
+ */
152
+ RadioField.prototype.renderItem = function(renderFn) {
153
+ this.$description.renderItem = renderFn;
154
+ return this;
145
155
  };
@@ -2,6 +2,7 @@ import type { ValidChild } from '../../../../../types/elements';
2
2
  import type { ObservableItem } from '../../../../../types/observable';
3
3
  import type { GlobalAttributes } from '../../../../../types/globals';
4
4
  import type { FieldInterface } from '../Field';
5
+ import {HTMLInputElement} from "happy-dom";
5
6
 
6
7
  interface RadioOption {
7
8
  value: unknown;
@@ -35,6 +36,7 @@ export interface RadioFieldInterface extends Omit<FieldInterface, 'render' | 'mo
35
36
  horizontal(): this;
36
37
  vertical(): this;
37
38
  grid(): this;
39
+ renderItem(item: (input: HTMLInputElement, data: RadioOption) => ValidChild): this;
38
40
  }
39
41
 
40
42
 
@@ -31,6 +31,10 @@ export type { BreadCrumb, BreadCrumbInterface, BreadCrumbDescription } from './b
31
31
 
32
32
  // --- Button ------------------------------------------------------------------
33
33
  export type { Button, ButtonInterface, ButtonDescription } from './button/types/Button';
34
+ export type { CopyButton } from './button/types/CopyButton';
35
+ export type { PasteButton } from './button/types/PasteButton';
36
+ export type { SubmitButton } from './button/types/SubmitButton';
37
+ export type { ResetButton } from './button/types/ResetButton';
34
38
 
35
39
  // --- Card --------------------------------------------------------------------
36
40
  export type { Card, CardInterface, CardDescription } from './card/types/Card';
@@ -363,20 +363,24 @@ DataTable.prototype.onSort = function(handler) {
363
363
  // ---------------------------------------------
364
364
 
365
365
  /**
366
- * @param {*} [enabled]
366
+ * @param {boolean} [enabled]
367
+ * @param {Observable} [model]
367
368
  * @returns {this}
368
369
  */
369
- DataTable.prototype.searchable = function(enabled = true) {
370
+ DataTable.prototype.searchable = function(enabled = true, model = null) {
370
371
  this.$description.searchable = enabled;
372
+ this.$description.$search = model;
371
373
  return this;
372
374
  };
373
375
 
374
376
  /**
375
- * @param {*} [enabled]
377
+ * @param {boolean} [enabled]
378
+ * @param {Record<string, *>?} filters
376
379
  * @returns {this}
377
380
  */
378
- DataTable.prototype.filterable = function(enabled = true) {
381
+ DataTable.prototype.filterable = function(enabled = true, filters = null) {
379
382
  this.$description.filterable = enabled;
383
+ this.$description.defaultFilter = filters;
380
384
  return this;
381
385
  };
382
386
 
@@ -278,6 +278,7 @@ ObservableArray.prototype.populateAndRender = function(iteration, callback) {
278
278
  * The filtered array updates automatically when source data or predicates change.
279
279
  *
280
280
  * @param {Object} predicates - Object mapping property names to filter conditions or functions
281
+ * @param {Observable[]?} dependencies - dependencies list when predicates is a function
281
282
  * @returns {ObservableArray} A new observable array containing filtered items
282
283
  * @example
283
284
  * const users = Observable.array([
@@ -287,9 +288,9 @@ ObservableArray.prototype.populateAndRender = function(iteration, callback) {
287
288
  *
288
289
  * const adults = users.where({ age: (val) => val >= 18 });
289
290
  */
290
- ObservableArray.prototype.where = function(predicates) {
291
+ ObservableArray.prototype.where = function(predicates, dependencies = null) {
291
292
  if(typeof predicates === 'function') {
292
- predicates = { _: predicates };
293
+ predicates = { _: predicates, dependencies };
293
294
  }
294
295
  const sourceArray = this;
295
296
  const observableDependencies = [sourceArray];
@@ -322,7 +323,10 @@ ObservableArray.prototype.where = function(predicates) {
322
323
  if(key === '_') {
323
324
  if (!callback(item)) return false;
324
325
  } else {
325
- if (!callback(item[key])) return false;
326
+ const valueSource = item[key];
327
+ if (!callback(valueSource?.__$Observable ? valueSource.val() : valueSource)) {
328
+ return false;
329
+ }
326
330
  }
327
331
  }
328
332
  return true;
@@ -174,7 +174,12 @@ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations)
174
174
  ObservableItem.prototype.$runAssocTrigger = function() {
175
175
  this.$firstListener = null;
176
176
  if(this.$watchers?.size && this.$listeners?.length) {
177
- this.$trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
177
+ if(this.$listeners.length === 1) {
178
+ this.$firstListener = this.$listeners[0];
179
+ this.$trigger = this.triggerWatchersAndFirstListener;
180
+ } else {
181
+ this.$trigger = this.triggerAll;
182
+ }
178
183
  return;
179
184
  }
180
185
  if(this.$listeners?.length) {
@@ -171,10 +171,10 @@ const AttributesWrapper = (element, attributes = {}) => {
171
171
  element.setAttribute(attributeName, value);
172
172
  continue;
173
173
  }
174
- if(value.$hydrate) {
175
- value.$hydrate(element, attributeName, 'attribute');
176
- continue;
177
- }
174
+ // if(value.$hydrate) {
175
+ // value.$hydrate(element, attributeName, 'attribute');
176
+ // continue;
177
+ // }
178
178
  // const attributeName = originalAttributeName.toLowerCase();
179
179
  if(value.__$Observable) {
180
180
  if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
@@ -199,9 +199,9 @@ const AttributesWrapper = (element, attributes = {}) => {
199
199
  continue;
200
200
  }
201
201
 
202
- // if(value.__$isTemplateBinding) {
203
- // // value.$hydrate(element, attributeName, 'attribute');
204
- // }
202
+ if(value.__$isTemplateBinding) {
203
+ value.$hydrate(element, attributeName, 'attribute');
204
+ }
205
205
 
206
206
  element.setAttribute(attributeName, value);
207
207
  }
@@ -65,18 +65,15 @@ const getPropertyValue = (callbackOrProperty, data) => {
65
65
  };
66
66
 
67
67
 
68
- const $nodeClonedCallbackData = new WeakMap();
69
-
70
- const $nodeClonerCallbackCaller = (callback, event) => {
71
- const data = $nodeClonedCallbackData.get(event.target);
72
- callback.apply(event.target, [...data, event]);
68
+ const $nodeClonerCallbackCaller = function(callback, event) {
69
+ const data = event.currentTarget.$ndScopeData;
70
+ callback.apply(event.currentTarget, [...data, event]);
73
71
  };
74
72
 
75
73
  NodeCloner.prototype.resolveMethods = function() {
76
74
  for(const methodName in this.$ndMethods) {
77
75
  const callback = this.$ndMethods[methodName];
78
- const uniqueCallback = $nodeClonerCallbackCaller.bind(null, callback);
79
- this.$uniqueCallbacks.set(methodName, uniqueCallback);
76
+ this.$ndMethods[methodName] = $nodeClonerCallbackCaller.bind($nodeClonerCallbackCaller, callback);
80
77
  }
81
78
  };
82
79
  /**
@@ -85,95 +82,108 @@ NodeCloner.prototype.resolveMethods = function() {
85
82
  *
86
83
  * @internal
87
84
  */
88
- NodeCloner.prototype.resolve = function() {
89
- if(this.$content) {
90
- return;
91
- }
92
- this.resolveMethods();
93
- const steps = [];
94
- if(this.$ndMethods) {
95
- const methods = Object.keys(this.$ndMethods);
96
- if(methods.length === 1) {
97
- const methodName = methods[0];
98
- steps.push((clonedNode) => {
99
- const uniqueCallback = this.$uniqueCallbacks.get(methodName);
100
- clonedNode.nd[methodName](uniqueCallback);
101
- });
85
+ NodeCloner.prototype.$cleanResolve = function() {
86
+ const fns = [];
87
+ const fnsParamNames = [];
88
+ const fnsParams = [];
89
+
90
+ const $element = this.$element;
91
+
92
+ if(this.$ndMethods !== null) {
93
+ const methodNames = Object.keys(this.$ndMethods);
94
+ if(methodNames.length === 1) {
95
+ const methodName = methodNames[0];
96
+ const callback = this.$ndMethods[methodName];
97
+ if($element[methodName]) {
98
+ fns.push(`clonedNode.${methodName}(callback)`);
99
+ } else {
100
+ fns.push(`clonedNode.nd.${methodName}(callback)`);
101
+ }
102
+ fnsParamNames.push('callback');
103
+ fnsParams.push(callback);
102
104
  } else {
103
- steps.push((clonedNode) => {
104
- const nd = clonedNode.nd;
105
- for(const methodName in this.$ndMethods) {
106
- const uniqueCallback = this.$uniqueCallbacks.get(methodName);
107
105
 
108
- // callback.length === 0 ? callback : this.$ndMethods[methodName].bind(clonedNode, ...data)
109
- nd[methodName](uniqueCallback);
106
+ for(const methodName in this.$ndMethods) {
107
+ const callbackName = methodName+'Callback';
108
+ const callback = this.$ndMethods[methodName];
109
+ if($element[methodName]) {
110
+ fns.push(`clonedNode.${methodName}(${callbackName})`);
111
+ } else {
112
+ fns.push(`clonedNode.nd.${methodName}(${callbackName})`);
110
113
  }
111
- });
114
+ fnsParamNames.push(callbackName);
115
+ fnsParams.push(callback);
116
+ }
112
117
  }
113
118
  }
114
- if(this.$classes) {
119
+ if(this.$classes !== null) {
115
120
  const cache = {};
116
121
  const keys = Object.keys(this.$classes);
117
122
 
118
123
  if(keys.length === 1) {
119
124
  const key = keys[0];
120
125
  const callback = this.$classes[key];
121
- steps.push((clonedNode, data) => {
122
- cache[key] = getPropertyValue(callback, data);
123
- ElementCreator.processClassAttribute(clonedNode, cache);
124
- });
126
+ fns.push(`ElementCreator.processClassAttribute(clonedNode, { ${key}: getPropertyValue(getPropertyValueCallback, data) })`);
127
+ fnsParamNames.push('getPropertyValueCallback');
128
+ fnsParams.push(callback);
125
129
  } else {
126
- steps.push((clonedNode, data) => {
127
- ElementCreator.processClassAttribute(clonedNode, buildProperties(cache, this.$classes, data));
128
- });
130
+ fns.push(`const classesCache = {}`);
131
+ fns.push(`ElementCreator.processClassAttribute(clonedNode, buildProperties(classesCache, $classes, data))`);
132
+ fnsParamNames.push('$classes');
133
+ fnsParams.push(this.$classes);
129
134
  }
130
135
  }
131
- if(this.$styles) {
136
+ if(this.$styles !== null) {
132
137
  const cache = {};
133
138
  const keys = Object.keys(this.$styles);
134
139
 
135
140
  if(keys.length === 1) {
136
141
  const key = keys[0];
137
142
  const callback = this.$styles[key];
138
- steps.push((clonedNode, data) => {
139
- cache[key] = getPropertyValue(callback, data);
140
- ElementCreator.processStyleAttribute(clonedNode, cache);
141
- });
143
+ fns.push(`ElementCreator.processStyleAttribute(clonedNode, { ${key}: getPropertyValue(getStyleValueCallback, data) } )`);
144
+ fnsParamNames.push('getStyleValueCallback');
145
+ fnsParams.push(callback);
142
146
  } else {
143
- steps.push((clonedNode, data) => {
144
- ElementCreator.processStyleAttribute(clonedNode, buildProperties(cache, this.$styles, data));
145
- });
147
+ fns.push(`const stylesCache = {}`);
148
+ fns.push(`ElementCreator.processStyleAttribute(clonedNode, buildProperties(stylesCache, $styles, data))`);
149
+ fnsParamNames.push('$styles');
150
+ fnsParams.push(this.$styles);
146
151
  }
147
152
  }
148
- if(this.$attrs) {
153
+ if(this.$attrs !== null) {
149
154
  const cache = {};
150
155
  const keys = Object.keys(this.$attrs);
151
156
 
152
157
  if(keys.length === 1) {
153
158
  const key = keys[0];
154
159
  const callback = this.$attrs[key];
155
- steps.push((clonedNode, data) => {
156
- cache[key] = getPropertyValue(callback, data);
157
- ElementCreator.processAttributes(clonedNode, cache);
158
- });
160
+ fns.push(`ElementCreator.processAttributes(clonedNode, { ${key}: getPropertyValue(getAttrValueCallback, data) } )`);
161
+ fnsParamNames.push('getAttrValueCallback');
162
+ fnsParams.push(callback);
159
163
  } else {
160
- steps.push((clonedNode, data) => {
161
- ElementCreator.processAttributes(clonedNode, buildProperties(cache, this.$attrs, data));
162
- });
164
+ fns.push(`const attrsCache = {}`);
165
+ fns.push(`ElementCreator.processAttributes(clonedNode, buildProperties(attrsCache, this.$attrs, data))`);
166
+ fnsParamNames.push('$attrs');
167
+ fnsParams.push(this.$attrs);
163
168
  }
164
169
  }
165
170
 
166
- const stepsCount = steps.length;
167
- const $element = this.$element;
171
+ fnsParamNames.push('ElementCreator', 'buildProperties', 'getPropertyValue', '$element', 'data');
172
+ fnsParams.push(ElementCreator, buildProperties, getPropertyValue, $element);
173
+ fns.unshift('const clonedNode = $element.cloneNode(false)', 'clonedNode.$ndScopeData = data');
174
+ fns.push('return clonedNode');
168
175
 
169
- this.cloneNode = (data) => {
170
- const clonedNode = $element.cloneNode(false);
171
- $nodeClonedCallbackData.set(clonedNode, data);
172
- for(let i = 0; i < stepsCount; i++) {
173
- steps[i](clonedNode, data);
174
- }
175
- return clonedNode;
176
- };
176
+ this.cloneNode = (new Function(fnsParamNames, fns.join(';'))).bind(null, ...fnsParams);
177
+ };
178
+
179
+ NodeCloner.prototype.resolve = function() {
180
+ if(this.$content) {
181
+ return;
182
+ }
183
+ this.resolveMethods();
184
+ this.$cleanResolve();
185
+ this.resolve = this.$cleanResolve();
186
+ return this;
177
187
  };
178
188
 
179
189
  /**
@@ -1,4 +1,5 @@
1
1
  import {trim} from '../core/utils/helpers.js';
2
+ import {Observable} from '../core/data/Observable';
2
3
 
3
4
  export const RouteParamPatterns = {
4
5
  id: '[0-9]+',
@@ -112,16 +113,26 @@ export function Route($path, $component, $options = {}) {
112
113
  /**
113
114
  * @param {{params: ?Object, query: ?Object, basePath: ?string}} configs
114
115
  */
115
- this.url = function(configs) {
116
+ this.url = function(configs = {}) {
116
117
  const path = $path.replace(/\{(.*?)}/ig, (block, definition) => {
117
118
  const description = paramsExtractor(definition);
118
119
  if(configs.params && configs.params[description.name]) {
119
- return configs.params[description.name];
120
+ const value = configs.params[description.name];
121
+ return value?.__$Observable ? value.val() : value;
120
122
  }
121
123
  throw new Error(`Missing parameter '${description.name}'`);
122
124
  });
123
125
 
124
- const queryString = (typeof configs.query === 'object') ? (new URLSearchParams(configs.query)).toString() : null;
126
+ let queries = null;
127
+ if(typeof configs.query === 'object') {
128
+ queries = {};
129
+ for(const property in configs.query) {
130
+ const value = configs.query[property];
131
+ queries[property] = value?.__$Observable ? value.val() : value;
132
+ }
133
+ }
134
+
135
+ const queryString = (queries) ? (new URLSearchParams(queries)).toString() : null;
125
136
  return (configs.basePath ? configs.basePath : '') + (queryString ? `${path}?${queryString}` : path);
126
137
  };
127
138
  }
@@ -0,0 +1,13 @@
1
+ import ButtonRender from './ButtonRender';
2
+
3
+
4
+ export default function CopyButtonRender($desc, instance) {
5
+
6
+ return ButtonRender($desc, instance).onClick(() => {
7
+ if(!$desc.target) {
8
+ return;
9
+ }
10
+
11
+ navigator.clipboard.writeText($desc.target.val());
12
+ });
13
+ }
@@ -0,0 +1,14 @@
1
+ import {ButtonRender} from '../../index';
2
+
3
+
4
+ export default function PasteButtonRender($desc, instance) {
5
+
6
+ return ButtonRender($desc, instance).onClick(() => {
7
+ if(!$desc.target) {
8
+ return;
9
+ }
10
+ navigator.clipboard.readText().then(text => {
11
+ $desc.target.set(text);
12
+ });
13
+ });
14
+ }
@@ -1,5 +1,6 @@
1
1
  import {Div, Label, Input, Span, ShowIf, ForEachArray} from '../../../../../elements';
2
2
  import {buildInputWithSlots} from '../helpers';
3
+ import {Observable} from '../../../../core/data/Observable';
3
4
 
4
5
  import './field.css';
5
6
 
@@ -106,13 +107,14 @@ const setupEvents = (input, $desc, instance) => {
106
107
  };
107
108
 
108
109
  const buildErrors = ($desc) => {
109
- return ShowIf($desc.showErrors, () => {
110
- return ShowIf($desc.hasErrors,
111
- () => Div({class: 'field-errors', ...($desc.elementsProps.error || {})},
112
- ForEachArray($desc.errors, (error) =>
113
- Span({class: 'field-error'}, (typeof error === 'string' ? error.toNdChildren() : error)),
114
- ),
110
+ const $shouldShowErrors = Observable.computed((showErrors, hasErrors) => {
111
+ return showErrors && hasErrors;
112
+ }, [$desc.showErrors, $desc.hasErrors]);
113
+ return ShowIf($shouldShowErrors,
114
+ () => Div({class: 'field-errors', ...($desc.elementsProps.error || {})},
115
+ ForEachArray($desc.errors, (error) =>
116
+ Span({class: 'field-error'}, (typeof error === 'string' ? error.toNdChildren() : error)),
115
117
  ),
116
- );
117
- });
118
+ ),
119
+ );
118
120
  };
@@ -47,7 +47,7 @@ const buildOptions = ($desc, instance) => {
47
47
  instance.validate();
48
48
  };
49
49
 
50
- const buildOption = (option) => {
50
+ const buildOption = ($desc, option) => {
51
51
  const optValue = option.value ?? option;
52
52
  const optLabel = option.label ?? option;
53
53
  const optId = ($desc.id || $desc.name) + '-' + optValue;
@@ -64,6 +64,10 @@ const buildOptions = ($desc, instance) => {
64
64
 
65
65
  input.nd.onChange(() => onSelect(optValue));
66
66
 
67
+ if($desc.renderItem) {
68
+ return Div({class: 'field-radio-wrapper'}, $desc.renderItem(input, option));
69
+ }
70
+
67
71
  return Div({class: 'field-radio-wrapper'}, [
68
72
  input,
69
73
  Label({class: 'field-radio-label', for: optId}, optLabel),
@@ -71,11 +75,12 @@ const buildOptions = ($desc, instance) => {
71
75
  };
72
76
 
73
77
  const isRequired = $desc.rules?.some(rule => rule.fn?.name === 'required');
78
+ const optionBuilder = buildOption.bind(null, $desc);
74
79
  return Div({
75
80
  class: 'field-radio-group is-' + ($desc.layout || 'vertical'),
76
81
  // [a11y] role=radiogroup, aria-required
77
82
  role: 'radiogroup',
78
83
  ...( isRequired ? { 'aria-required': 'true' } : {} ),
79
84
  ...($desc.elementsProps.wrapper || {}),
80
- }, ForEachArray($options, buildOption));
85
+ }, ForEachArray($options, optionBuilder));
81
86
  };
@@ -61,6 +61,7 @@ export default function PopoverRender($desc, instance, classPrefix = 'popover')
61
61
 
62
62
 
63
63
  let position;
64
+ const isSupportsPopover = supportsPopover();
64
65
  const updatePosition = (state) => {
65
66
  if(!state) {
66
67
  return;
@@ -79,7 +80,7 @@ export default function PopoverRender($desc, instance, classPrefix = 'popover')
79
80
  if($desc.showIf && $desc.showIf.val() === false) {
80
81
  return;
81
82
  }
82
- if(supportsPopover()) {
83
+ if(isSupportsPopover) {
83
84
  popover.showPopover();
84
85
  }
85
86
 
package/src/ui/index.js CHANGED
@@ -6,6 +6,8 @@ export { default as DividerRender } from './components/divider/DividerRender';
6
6
  export { default as SkeletonRender } from './components/skeleton/SkeletonRender';
7
7
  export { default as ProgressRender } from './components/progress/ProgressRender';
8
8
  export { default as ButtonRender } from './components/button/ButtonRender';
9
+ export { default as CopyButtonRender } from './components/button/CopyButtonRender';
10
+ export { default as PasteButtonRender } from './components/button/PasteButtonRender';
9
11
  export { default as AlertRender } from './components/alert/AlertRender';
10
12
  export { default as ToastRender } from './components/toast/ToastRender';
11
13
  export { default as PopoverRender } from './components/popover/PopoverRender';