native-document 1.0.166 → 1.0.169

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 (139) hide show
  1. package/.vitepress/config.js +166 -0
  2. package/CHANGELOG.md +187 -0
  3. package/components.js +4 -1
  4. package/dist/native-document.components.min.js +593 -300
  5. package/dist/native-document.dev.js +99 -75
  6. package/dist/native-document.dev.js.map +1 -1
  7. package/dist/native-document.min.js +1 -1
  8. package/docs/advanced-components.md +213 -608
  9. package/docs/anchor.md +173 -312
  10. package/docs/cache.md +95 -803
  11. package/docs/cli.md +179 -0
  12. package/docs/components/accordion.md +172 -0
  13. package/docs/components/alert.md +99 -0
  14. package/docs/components/avatar.md +160 -0
  15. package/docs/components/badge.md +102 -0
  16. package/docs/components/breadcrumb.md +89 -0
  17. package/docs/components/button.md +183 -0
  18. package/docs/components/card.md +69 -0
  19. package/docs/components/context-menu.md +118 -0
  20. package/docs/components/data-table.md +345 -0
  21. package/docs/components/dropdown.md +214 -0
  22. package/docs/components/form/autocomplete-field.md +81 -0
  23. package/docs/components/form/checkbox-field.md +41 -0
  24. package/docs/components/form/checkbox-group-field.md +54 -0
  25. package/docs/components/form/color-field.md +64 -0
  26. package/docs/components/form/date-field.md +92 -0
  27. package/docs/components/form/field-collection.md +63 -0
  28. package/docs/components/form/file-field.md +203 -0
  29. package/docs/components/form/form-control.md +87 -0
  30. package/docs/components/form/image-field.md +90 -0
  31. package/docs/components/form/index.md +115 -0
  32. package/docs/components/form/number-field.md +65 -0
  33. package/docs/components/form/radio-field.md +51 -0
  34. package/docs/components/form/select-field.md +123 -0
  35. package/docs/components/form/slider.md +136 -0
  36. package/docs/components/form/string-field.md +134 -0
  37. package/docs/components/form/textarea-field.md +65 -0
  38. package/docs/components/form-fields.md +372 -0
  39. package/docs/components/getting-started.md +264 -0
  40. package/docs/components/icons.md +321 -0
  41. package/docs/components/index.md +337 -0
  42. package/docs/components/layout.md +279 -0
  43. package/docs/components/list.md +73 -0
  44. package/docs/components/menu.md +215 -0
  45. package/docs/components/modal.md +156 -0
  46. package/docs/components/pagination.md +95 -0
  47. package/docs/components/popover.md +131 -0
  48. package/docs/components/progress.md +111 -0
  49. package/docs/components/shortcut-manager.md +221 -0
  50. package/docs/components/simple-table.md +107 -0
  51. package/docs/components/skeleton.md +155 -0
  52. package/docs/components/spinner.md +100 -0
  53. package/docs/components/splitter.md +133 -0
  54. package/docs/components/stepper.md +163 -0
  55. package/docs/components/switch.md +113 -0
  56. package/docs/components/tabs.md +153 -0
  57. package/docs/components/toast.md +119 -0
  58. package/docs/components/tooltip.md +151 -0
  59. package/docs/components/traits.md +261 -0
  60. package/docs/conditional-rendering.md +170 -588
  61. package/docs/contributing.md +300 -25
  62. package/docs/core-concepts.md +205 -374
  63. package/docs/elements.md +251 -367
  64. package/docs/extending-native-document-element.md +192 -207
  65. package/docs/filters.md +153 -1122
  66. package/docs/getting-started.md +193 -267
  67. package/docs/i18n.md +241 -0
  68. package/docs/index.md +76 -0
  69. package/docs/lifecycle-events.md +143 -75
  70. package/docs/list-rendering.md +227 -852
  71. package/docs/memory-management.md +134 -47
  72. package/docs/native-document-element.md +337 -186
  73. package/docs/native-fetch.md +99 -630
  74. package/docs/observable-resource.md +364 -0
  75. package/docs/observables.md +592 -526
  76. package/docs/routing.md +244 -653
  77. package/docs/state-management.md +134 -241
  78. package/docs/svg-elements.md +231 -0
  79. package/docs/theming.md +409 -0
  80. package/docs/tutorials/.gitkeep +0 -0
  81. package/docs/validation.md +95 -97
  82. package/docs/vitepress-conventions.md +219 -0
  83. package/package.json +34 -13
  84. package/readme.md +269 -89
  85. package/src/components/card/Card.js +93 -39
  86. package/src/components/card/index.js +1 -1
  87. package/src/components/form/field/types/FileField.js +3 -3
  88. package/src/components/icon/Icon.js +107 -0
  89. package/src/components/icon/icon-getters.js +142 -0
  90. package/src/components/icon/icons.js +171 -0
  91. package/src/components/icon/index.js +17 -0
  92. package/src/components/icon/types/Icon.d.ts +191 -0
  93. package/src/components/index.d.ts +6 -1
  94. package/src/components/list/HasListItem.js +171 -0
  95. package/src/components/list/List.js +41 -107
  96. package/src/components/list/ListDivider.js +39 -0
  97. package/src/components/list/ListGroup.js +76 -59
  98. package/src/components/list/ListItem.js +117 -69
  99. package/src/components/list/index.js +3 -1
  100. package/src/components/list/types/List.d.ts +45 -32
  101. package/src/components/list/types/ListDivider.ts +16 -0
  102. package/src/components/list/types/ListGroup.d.ts +51 -29
  103. package/src/components/list/types/ListItem.d.ts +40 -30
  104. package/src/components/spacer/Spacer.js +1 -1
  105. package/src/core/data/ObservableResource.js +5 -0
  106. package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
  107. package/src/core/elements/content-formatter.js +16 -2
  108. package/src/core/elements/form.js +1 -1
  109. package/src/core/elements/img.js +1 -1
  110. package/src/core/elements/medias.js +2 -2
  111. package/src/core/elements/meta-data.js +1 -1
  112. package/src/core/wrappers/AttributesWrapper.js +37 -22
  113. package/src/core/wrappers/ElementCreator.js +9 -16
  114. package/src/core/wrappers/HtmlElementWrapper.js +26 -7
  115. package/src/core/wrappers/NDElement.js +12 -1
  116. package/src/core/wrappers/prototypes/attributes-extensions.js +24 -24
  117. package/src/core/wrappers/prototypes/nd-element-extensions.js +1 -8
  118. package/src/ui/components/card/CardRender.js +133 -0
  119. package/src/ui/components/card/card.css +169 -0
  120. package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
  121. package/src/ui/components/icon/material/MaterialIconRender.js +71 -0
  122. package/src/ui/components/icon/material/material.css +15 -0
  123. package/src/ui/components/icon/material/material.map.js +170 -0
  124. package/src/ui/components/icon/phosphor/PhosphorIconRender.js +71 -0
  125. package/src/ui/components/icon/phosphor/phosphor.css +17 -0
  126. package/src/ui/components/icon/phosphor/phosphor.map.js +165 -0
  127. package/src/ui/components/icon/tabler/TablerIconRender.js +59 -0
  128. package/src/ui/components/icon/tabler/tabler.css +14 -0
  129. package/src/ui/components/icon/tabler/tabler.map.js +165 -0
  130. package/src/ui/components/list/ListRender.js +18 -0
  131. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  132. package/src/ui/components/list/divider/list-divider.css +12 -0
  133. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  134. package/src/ui/components/list/group/list-group.css +62 -0
  135. package/src/ui/components/list/item/ListItemRender.js +238 -0
  136. package/src/ui/components/list/item/list-item.css +191 -0
  137. package/src/ui/components/list/list.css +24 -0
  138. package/src/ui/components/spacer/SpacerRender.js +10 -0
  139. package/src/ui/index.js +13 -0
@@ -6,7 +6,7 @@ import NativeDocumentError from '../errors/NativeDocumentError';
6
6
  * Creates an `<img>` element.
7
7
  * @type {function(ImgAttributes=): HTMLImageElement}
8
8
  */
9
- export const BaseImage = HtmlElementWrapper('img');
9
+ export const BaseImage = HtmlElementWrapper('img', null, true);
10
10
 
11
11
  /**
12
12
  * Creates an `<img>` element.
@@ -16,13 +16,13 @@ export const Video = HtmlElementWrapper('video');
16
16
  * Creates a `<source>` element.
17
17
  * @type {function(SourceAttributes=): HTMLSourceElement}
18
18
  */
19
- export const Source = HtmlElementWrapper('source');
19
+ export const Source = HtmlElementWrapper('source', null, true);
20
20
 
21
21
  /**
22
22
  * Creates a `<track>` element.
23
23
  * @type {function(TrackAttributes=): HTMLTrackElement}
24
24
  */
25
- export const Track = HtmlElementWrapper('track');
25
+ export const Track = HtmlElementWrapper('track', null, true);
26
26
 
27
27
  /**
28
28
  * Creates a `<canvas>` element.
@@ -40,4 +40,4 @@ export const Var = HtmlElementWrapper('var');
40
40
  * Creates a `<wbr>` element.
41
41
  * @type {function(GlobalAttributes=): HTMLElement}
42
42
  */
43
- export const Wbr = HtmlElementWrapper('wbr');
43
+ export const Wbr = HtmlElementWrapper('wbr', null, true);
@@ -14,27 +14,31 @@ import {Observable} from '../data/Observable';
14
14
  export const bindClassAttribute = (element, data) => {
15
15
  for(const className in data) {
16
16
  const value = data[className];
17
- if(value.__$Observable) {
18
- if(value.__$isObservableChecker) {
19
- let lastClass = value.val();
20
- if(typeof lastClass === 'string') {
21
- element.classes.toggle(lastClass, true);
22
- value.subscribe((currentValue) => {
23
- element.classes.remove(lastClass);
24
- element.classes.toggle(currentValue, true);
25
- lastClass = currentValue;
26
- });
27
- continue;
28
- }
17
+
18
+ if (value.__$isObservableChecker) {
19
+ let lastClass = value.val();
20
+ if (typeof lastClass === 'string') {
21
+ element.classes.toggle(lastClass, true);
22
+ value.subscribe((currentValue) => {
23
+ element.classes.remove(lastClass);
24
+ element.classes.toggle(currentValue, true);
25
+ lastClass = currentValue;
26
+ });
27
+ continue;
29
28
  }
29
+ }
30
+
31
+ if (value.__$Observable) {
30
32
  element.classes.toggle(className, value.val());
31
33
  value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
32
34
  continue;
33
35
  }
34
- if(value.$hydrate) {
36
+
37
+ if (value.$hydrate) {
35
38
  value.$hydrate(element, className);
36
39
  continue;
37
40
  }
41
+
38
42
  element.classes.toggle(className, value);
39
43
  }
40
44
  };
@@ -92,12 +96,12 @@ export const bindStyleAttribute = (element, data) => {
92
96
  * @param {boolean|number|Observable} value
93
97
  */
94
98
  export const bindBooleanAttribute = (element, attributeName, value) => {
95
- const isObservable = value.__$isObservable;
99
+ const isObservable = value.__$Observable;
96
100
  const defaultValue = isObservable? value.val() : value;
97
101
 
98
102
  const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
99
103
 
100
- if(Validator.isBoolean(defaultValue)) {
104
+ if(typeof defaultValue === 'boolean') {
101
105
  element[attributeRealName] = defaultValue;
102
106
  }
103
107
  else {
@@ -126,15 +130,14 @@ export const bindBooleanAttribute = (element, attributeName, value) => {
126
130
  * @param {Observable} value
127
131
  */
128
132
  export const bindAttributeWithObservable = (element, attributeName, value) => {
129
- const applyValue = attributeName === 'value' ? (newValue) => element.value = newValue : (newValue) => element.setAttribute(attributeName, newValue);
130
- value.subscribe(applyValue);
131
-
132
133
  if(attributeName === 'value') {
133
134
  element.value = value.val();
134
135
  element.addEventListener('input', () => value.set(element.value));
136
+ value.subscribe((newValue) => element.value = newValue);
135
137
  return;
136
138
  }
137
139
  element.setAttribute(attributeName, value.val());
140
+ value.subscribe((newValue) => element.setAttribute(attributeName, newValue));
138
141
  };
139
142
 
140
143
  /**
@@ -149,16 +152,25 @@ const AttributesWrapper = (element, attributes) => {
149
152
  }
150
153
 
151
154
  for(const originalAttributeName in attributes) {
152
- const attributeName = originalAttributeName.toLowerCase();
153
155
  const value = attributes[originalAttributeName];
154
156
  if(value == null) {
155
157
  continue;
156
158
  }
157
- if(value.handleNdAttribute) {
158
- value.handleNdAttribute(element, attributeName, value);
159
+ const type = typeof value;
160
+ if(type === 'string' || type === 'number') {
161
+ element.setAttribute(originalAttributeName, value);
162
+ continue;
163
+ }
164
+ const attributeName = originalAttributeName.toLowerCase();
165
+ if(value.__$Observable) {
166
+ if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
167
+ bindBooleanAttribute(element, attributeName, value);
168
+ continue;
169
+ }
170
+ bindAttributeWithObservable(element, originalAttributeName, value);
159
171
  continue;
160
172
  }
161
- if(typeof value === 'object') {
173
+ if(type === 'object') {
162
174
  if(attributeName === 'class') {
163
175
  bindClassAttribute(element, value);
164
176
  continue;
@@ -172,6 +184,9 @@ const AttributesWrapper = (element, attributes) => {
172
184
  bindBooleanAttribute(element, attributeName, value);
173
185
  continue;
174
186
  }
187
+ if(value.__$isTemplateBinding) {
188
+ value.$hydrate(element, attributeName);
189
+ }
175
190
 
176
191
  element.setAttribute(attributeName, value);
177
192
  }
@@ -89,30 +89,23 @@ export const ElementCreator = {
89
89
 
90
90
  },
91
91
  getChild: (child) => {
92
- if(child == null) {
93
- return null;
94
- }
95
- if(child.toNdElement) {
96
- do {
97
- child = child.toNdElement();
98
- if(Validator.isElement(child)) {
99
- return child;
100
- }
101
- } while (child.toNdElement);
92
+ if (child == null) return null;
93
+
94
+ child = child.toNdElement();
95
+ if (child instanceof Node) return child;
96
+
97
+ while (child != null && !(child instanceof Node)) {
98
+ child = child.toNdElement?.();
102
99
  }
103
100
 
104
- return ElementCreator.createStaticTextNode(null, child);
101
+ return child instanceof Node ? child : null;
105
102
  },
106
103
  /**
107
104
  *
108
105
  * @param {HTMLElement} element
109
106
  * @param {Object} attributes
110
107
  */
111
- processAttributes: (element, attributes) => {
112
- if (attributes) {
113
- AttributesWrapper(element, attributes);
114
- }
115
- },
108
+ processAttributes: AttributesWrapper,
116
109
  /**
117
110
  *
118
111
  * @param {HTMLElement} element
@@ -3,7 +3,6 @@ import {ElementCreator} from './ElementCreator';
3
3
  import {normalizeComponentArgs} from '../utils/args-types';
4
4
  import './NdPrototype';
5
5
 
6
- import './NdPrototype';
7
6
  import '../../core/utils/prototypes.js';
8
7
  import '../wrappers/prototypes/nd-element-extensions';
9
8
  import '../wrappers/prototypes/bind-class-extensions';
@@ -27,6 +26,21 @@ export const createTextNode = (value) => {
27
26
  };
28
27
 
29
28
 
29
+ /**
30
+ * Applies attributes to an existing HTMLElement.
31
+ * Used internally by HtmlElementWrapper on each cloned node.
32
+ *
33
+ * @internal
34
+ * @param {HTMLElement} element - Element to configure
35
+ * @param {Object|null} attributes - Attributes object or children if no attrs provided
36
+ * @returns {HTMLElement} The configured element
37
+ */
38
+ export const createVoidHtmlElement = (element, attributes) => {
39
+ ElementCreator.processAttributes(element, attributes);
40
+ return element;
41
+ };
42
+
43
+ const OBJECT_PROTOTYPE = Object.prototype;
30
44
  /**
31
45
  * Applies attributes and children to an existing HTMLElement.
32
46
  * Used internally by HtmlElementWrapper on each cloned node.
@@ -38,7 +52,11 @@ export const createTextNode = (value) => {
38
52
  * @returns {HTMLElement} The configured element
39
53
  */
40
54
  export const createHtmlElement = (element, _attributes, _children = null) => {
41
- const { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
55
+ let attributes = _attributes, children = _children;
56
+ if((!_attributes || !_children) && (typeof _attributes !== 'object' || Array.isArray(_attributes) || _attributes === null || Object.getPrototypeOf(_attributes) !== OBJECT_PROTOTYPE || _attributes.$hydrate)) { // IF it's not a JSON
57
+ attributes = _children;
58
+ children = _attributes;
59
+ }
42
60
 
43
61
  ElementCreator.processAttributes(element, attributes);
44
62
  ElementCreator.processChildren(children, element);
@@ -63,16 +81,17 @@ export const createHtmlElement = (element, _attributes, _children = null) => {
63
81
  * return el;
64
82
  * });
65
83
  */
66
- export default function HtmlElementWrapper(name, customWrapper = null) {
84
+ export default function HtmlElementWrapper(name, customWrapper = null, isVoid = false) {
85
+ const elementCreator = isVoid ? createVoidHtmlElement : createHtmlElement;
67
86
  if(name) {
68
87
  if(customWrapper) {
69
88
  let node = null;
70
89
  let createElement = (attr, children) => {
71
90
  node = document.createElement(name);
72
91
  createElement = (attr, children) => {
73
- return createHtmlElement(customWrapper(node.cloneNode()), attr, children);
92
+ return elementCreator(customWrapper(node.cloneNode()), attr, children);
74
93
  };
75
- return createHtmlElement(customWrapper(node.cloneNode()), attr, children);;
94
+ return elementCreator(customWrapper(node.cloneNode()), attr, children);;
76
95
  };
77
96
 
78
97
  return (attr, children) => createElement(attr, children);
@@ -82,9 +101,9 @@ export default function HtmlElementWrapper(name, customWrapper = null) {
82
101
  let createElement = (attr, children) => {
83
102
  node = document.createElement(name);
84
103
  createElement = (attr, children) => {
85
- return createHtmlElement(node.cloneNode(), attr, children);
104
+ return elementCreator(node.cloneNode(), attr, children);
86
105
  };
87
- return createHtmlElement(node.cloneNode(), attr, children);
106
+ return elementCreator(node.cloneNode(), attr, children);
88
107
  };
89
108
 
90
109
  return (attr, children) => createElement(attr, children);
@@ -38,8 +38,19 @@ NDElement.$getChild = (el) => el;
38
38
  NDElement.prototype.ghostDom = function(element) {
39
39
  if(!this.$attachements) {
40
40
  this.$attachements = document.createDocumentFragment();
41
+ this.toNdElement = () => {
42
+ const fragment = document.createDocumentFragment();
43
+ if(!this.$attachements.contains(this.$element)) {
44
+ fragment.appendChild(this.$element);
45
+ }
46
+ fragment.appendChild(this.$attachements);
47
+ return fragment;
48
+ };
49
+ }
50
+ const child = NDElement.$getChild(element);
51
+ if(child) {
52
+ this.$attachements.appendChild(child);
41
53
  }
42
- this.$attachements.appendChild(NDElement.$getChild(element));
43
54
  return this;
44
55
  };
45
56
 
@@ -1,24 +1,24 @@
1
- import {
2
- bindAttributeWithObservable,
3
- bindBooleanAttribute,
4
- } from '../AttributesWrapper';
5
- import ObservableItem from '../../data/ObservableItem';
6
- import TemplateBinding from '../TemplateBinding';
7
- import {BOOLEAN_ATTRIBUTES} from '../constants';
8
- import ObservableChecker from '../../data/ObservableChecker';
9
-
10
-
11
- ObservableItem.prototype.handleNdAttribute = function(element, attributeName) {
12
- if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
13
- bindBooleanAttribute(element, attributeName, this);
14
- return;
15
- }
16
-
17
- bindAttributeWithObservable(element, attributeName, this);
18
- };
19
-
20
- ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
21
-
22
- TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
23
- this.$hydrate(element, attributeName);
24
- };
1
+ // import {
2
+ // bindAttributeWithObservable,
3
+ // bindBooleanAttribute,
4
+ // } from '../AttributesWrapper';
5
+ // import ObservableItem from '../../data/ObservableItem';
6
+ // import TemplateBinding from '../TemplateBinding';
7
+ // import {BOOLEAN_ATTRIBUTES} from '../constants';
8
+ // import ObservableChecker from '../../data/ObservableChecker';
9
+ //
10
+ //
11
+ // ObservableItem.prototype.handleNdAttribute = function(element, attributeName) {
12
+ // if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
13
+ // bindBooleanAttribute(element, attributeName, this);
14
+ // return;
15
+ // }
16
+ //
17
+ // bindAttributeWithObservable(element, attributeName, this);
18
+ // };
19
+ //
20
+ // ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
21
+ //
22
+ // TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
23
+ // this.$hydrate(element, attributeName);
24
+ // };
@@ -88,14 +88,7 @@ ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
88
88
  * @returns {HTMLElement|DocumentFragment} The underlying DOM node
89
89
  */
90
90
  NDElement.prototype.toNdElement = function () {
91
- const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
92
- if(this.$attachements) {
93
- if(!this.$attachements.contains(this.$element)) {
94
- this.$attachements.append(this.$element);
95
- }
96
- return this.$attachements;
97
- }
98
- return element;
91
+ return this.$element;
99
92
  };
100
93
 
101
94
  /**
@@ -0,0 +1,133 @@
1
+ import { Div, Img, Span } from '../../../core/elements';
2
+
3
+ import './card.css';
4
+ import {Button} from '../../../components/button';
5
+
6
+ export default function CardRender($desc, instance) {
7
+ const imageSlot = buildImage($desc, instance);
8
+ const headerSlot = buildHeader($desc, instance);
9
+ const contentSlot = buildContent($desc, instance);
10
+ const actionsSlot = buildActions($desc, instance);
11
+ const footerSlot = buildFooter($desc, instance, actionsSlot);
12
+
13
+ if ($desc.layout) {
14
+ return $desc.layout(
15
+ { image: imageSlot, header: headerSlot, content: contentSlot, actions: actionsSlot, footer: footerSlot },
16
+ instance,
17
+ );
18
+ }
19
+
20
+ const props = instance.getEditableProps();
21
+
22
+ props.class.add('card');
23
+
24
+ if ($desc.variant) {
25
+ props.class.add('is-' + $desc.variant);
26
+ }
27
+
28
+ props.class.add({
29
+ 'is-horizontal': $desc.horizontal,
30
+ 'is-hoverable': $desc.hoverable,
31
+ 'is-clickable': instance.hasListeners('click'),
32
+ });
33
+
34
+ if ($desc.loading) {
35
+ props.class.add({ 'is-loading': $desc.loading });
36
+ }
37
+
38
+ const children = [
39
+ imageSlot,
40
+ Div({ class: 'card-content'}, [
41
+ Div({ class: 'card-body' }, [headerSlot, contentSlot]),
42
+ footerSlot,
43
+ ]),
44
+ ];
45
+
46
+ const el = Div(instance.resolveProps(), children);
47
+
48
+ el.nd.onClick((e) => instance.emit('click', e));
49
+ el.nd.onMouseEnter((e) => instance.emit('hover', e));
50
+
51
+ return el;
52
+ }
53
+
54
+ function buildImage($desc, instance) {
55
+ if ($desc.renderImage) {
56
+ return $desc.renderImage($desc, instance);
57
+ }
58
+
59
+ if (!$desc.image) {
60
+ return null;
61
+ }
62
+
63
+ return Div({ class: 'card-image-wrapper' }, [
64
+ Img($desc.image, {
65
+ class: `card-image is-${$desc.imagePosition}`,
66
+ alt: '',
67
+ })
68
+ ]);
69
+ }
70
+
71
+ function buildHeader($desc, instance) {
72
+ if ($desc.renderHeader) {
73
+ return $desc.renderHeader($desc, instance);
74
+ }
75
+
76
+ const parts = [];
77
+
78
+ if ($desc.title) {
79
+ parts.push(Span({ class: 'card-title' }, $desc.title));
80
+ }
81
+
82
+ if ($desc.subtitle) {
83
+ parts.push(Span({ class: 'card-subtitle' }, $desc.subtitle));
84
+ }
85
+
86
+ if (!parts.length) {
87
+ return null;
88
+ }
89
+
90
+ return Div({ class: 'card-header' }, parts);
91
+ }
92
+
93
+ function buildContent($desc, instance) {
94
+ if ($desc.renderContent) {
95
+ return $desc.renderContent($desc, instance);
96
+ }
97
+
98
+ if (!$desc.content) {
99
+ return null;
100
+ }
101
+
102
+ return Div({ class: 'card-content' }, $desc.content);
103
+ }
104
+
105
+ function buildActions($desc, instance) {
106
+ if ($desc.renderActions) {
107
+ return $desc.renderActions($desc, instance);
108
+ }
109
+
110
+ if (!$desc.actions?.length) {
111
+ return null;
112
+ }
113
+
114
+ const buttons = $desc.actions.map(({ label, callback }) => {
115
+ const btn = (typeof label === 'string') ? Button(label, { class: 'card-action' }) : label;
116
+ btn.nd.onClick(() => callback?.());
117
+ return btn;
118
+ });
119
+
120
+ return Div({ class: 'card-actions' }, buttons);
121
+ }
122
+
123
+ function buildFooter($desc, instance, actionsSlot) {
124
+ if ($desc.renderFooter) {
125
+ return $desc.renderFooter($desc, instance);
126
+ }
127
+
128
+ if (!actionsSlot) {
129
+ return null;
130
+ }
131
+
132
+ return Div({ class: 'card-footer' }, actionsSlot);
133
+ }
@@ -0,0 +1,169 @@
1
+ :root {
2
+ --card-bg: var(--background);
3
+ --card-border: var(--gray-lite-3);
4
+ --card-radius: var(--radius-card);
5
+ --card-shadow: var(--shadow-card);
6
+ --card-shadow-elevated: var(--shadow-lg);
7
+ --card-shadow-hover: var(--shadow-md);
8
+ --card-font-size: var(--description-size);
9
+ --card-padding: var(--space-comfortable);
10
+ --card-img-height: 200px;
11
+ --card-title-size: var(--text-size);
12
+ --card-title-weight: 600;
13
+ --card-title-color: var(--text-color);
14
+ --card-subtitle-size: var(--note-size);
15
+ --card-subtitle-color: var(--gray);
16
+ --card-footer-padding: var(--space-cozy) var(--space-comfortable);
17
+ --card-footer-border: var(--gray-lite-3);
18
+ --card-footer-gap: var(--space-cozy);
19
+ --card-transition: box-shadow 0.2s ease;
20
+ --card-loading-bg: var(--gray-lite-5);
21
+ --card-horizontal-img-width: 175px;
22
+ --card-clickable-cursor: pointer;
23
+ --card-header-row-gap: .5rem;
24
+ }
25
+
26
+
27
+ .card {
28
+ background: var(--card-bg);
29
+ border: 1px solid var(--card-border);
30
+ border-radius: var(--card-radius);
31
+ overflow: hidden;
32
+ font-size: var(--card-font-size);
33
+ transition: var(--card-transition);
34
+ display: inline-flex;
35
+ flex-direction: column;
36
+
37
+ &.is-elevated {
38
+ border: none;
39
+ box-shadow: var(--card-shadow-elevated);
40
+ }
41
+
42
+ &.is-flat {
43
+ border: none;
44
+ box-shadow: none;
45
+ }
46
+
47
+ &.is-outlined {
48
+ border-width: 2px;
49
+ }
50
+
51
+ &.is-hoverable:hover {
52
+ box-shadow: var(--card-shadow-hover);
53
+ }
54
+
55
+ &.is-clickable {
56
+ cursor: var(--card-clickable-cursor);
57
+
58
+ &:hover {
59
+ box-shadow: var(--card-shadow-hover);
60
+ }
61
+
62
+ &:active {
63
+ opacity: 0.95;
64
+ }
65
+ }
66
+
67
+ &.is-horizontal {
68
+ flex-direction: row;
69
+
70
+ .card-image-wrapper {
71
+ width: var(--card-horizontal-img-width);
72
+ }
73
+ .card-image {
74
+ flex-shrink: 0;
75
+ }
76
+ .card-content {
77
+ display: flex;
78
+ flex-direction: column;
79
+ justify-content: center;
80
+ align-items: flex-start;
81
+ }
82
+ }
83
+
84
+ &.is-loading {
85
+ .card-title,
86
+ .card-subtitle,
87
+ .card-content {
88
+ background: var(--card-loading-bg);
89
+ border-radius: 4px;
90
+ color: transparent;
91
+ user-select: none;
92
+
93
+ * {
94
+ visibility: hidden;
95
+ }
96
+ }
97
+
98
+ .card-title { width: 60%; }
99
+ .card-subtitle { width: 40%; }
100
+ .card-image { background: var(--card-loading-bg); }
101
+ }
102
+ }
103
+ .card-header {
104
+ display: flex;
105
+ flex-direction: column;
106
+ align-items: flex-start;
107
+ justify-content: flex-start;
108
+ row-gap: var(--card-header-row-gap);
109
+ }
110
+ .card-actions {
111
+ width: 100%;
112
+ display: flex;
113
+ justify-content: space-between;
114
+ column-gap: var(--space-tiny);
115
+ > * {
116
+ flex: 1;
117
+ }
118
+ }
119
+ .card-image-wrapper {
120
+ width: 100%;
121
+ height: var(--card-img-height);
122
+ }
123
+ .card-image {
124
+ width: 100%;
125
+ height: 100%;
126
+ object-fit: cover;
127
+ display: block;
128
+
129
+ &.is-top { order: -1; }
130
+ &.is-bottom { order: 10; }
131
+
132
+ .card.is-horizontal &.is-left { order: -1; }
133
+ .card.is-horizontal &.is-right { order: 10; }
134
+ }
135
+
136
+ .card-body {
137
+ padding: var(--card-padding);
138
+ flex: 1;
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: var(--space-cozy);
142
+ }
143
+
144
+ .card-title {
145
+ font-size: var(--card-title-size);
146
+ font-weight: var(--card-title-weight);
147
+ color: var(--card-title-color);
148
+ margin: 0;
149
+ }
150
+
151
+ .card-subtitle {
152
+ font-size: var(--card-subtitle-size);
153
+ color: var(--card-subtitle-color);
154
+ margin: 0;
155
+ }
156
+
157
+ .card-content {
158
+ flex: 1;
159
+ }
160
+
161
+ .card-footer {
162
+ width: 100%;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: flex-end;
166
+ gap: var(--card-footer-gap);
167
+ padding: var(--card-footer-padding);
168
+ border-top: 1px solid var(--card-footer-border);
169
+ }
@@ -18,7 +18,7 @@ export default function ContextMenuRender($desc, instance) {
18
18
 
19
19
  createPortal(container, {name: 'context-menu'});
20
20
 
21
- if($desc.trigger) {
21
+ if($desc.trigger && instance) {
22
22
  contextMenuHandler($desc.trigger, instance);
23
23
  }
24
24