@vanilla-bean/components 1.1.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/Component/Component.js +222 -36
  2. package/Component/Component.test.js +425 -38
  3. package/Component/README.md +480 -0
  4. package/Elem/README.md +373 -0
  5. package/README.md +1 -1
  6. package/components/BottomSheet/BottomSheet.js +8 -16
  7. package/components/Button/Button.js +12 -18
  8. package/components/Calendar/Calendar.js +139 -59
  9. package/components/Calendar/CalendarEvent.js +9 -4
  10. package/components/Calendar/Toolbar.js +28 -20
  11. package/components/Calendar/index.js +1 -0
  12. package/components/Code/Code.js +26 -30
  13. package/components/ColorPicker/ColorPicker.js +57 -57
  14. package/components/Dialog/Dialog.js +78 -72
  15. package/components/Form/Form.js +12 -8
  16. package/components/Icon/Icon.js +19 -11
  17. package/components/Input/Input.js +85 -67
  18. package/components/Input/README.md +0 -2
  19. package/components/Keyboard/Key.js +7 -10
  20. package/components/Keyboard/Keyboard.js +38 -46
  21. package/components/Label/Label.js +52 -50
  22. package/components/Link/Link.js +14 -20
  23. package/components/List/List.js +28 -30
  24. package/components/Menu/Menu.js +19 -10
  25. package/components/Menu/Menu.lld.md +2 -2
  26. package/components/Notify/Notify.js +29 -19
  27. package/components/Popover/Popover.js +49 -44
  28. package/components/RadioButton/RadioButton.js +33 -29
  29. package/components/Router/Router.js +19 -21
  30. package/components/Select/Select.js +24 -30
  31. package/components/Table/Table.js +55 -36
  32. package/components/TagList/Tag.js +16 -14
  33. package/components/TagList/TagList.js +15 -8
  34. package/components/Tooltip/Tooltip.js +12 -25
  35. package/components/TooltipWrapper/TooltipWrapper.js +55 -49
  36. package/components/Whiteboard/Whiteboard.js +45 -43
  37. package/devTools/build.js +43 -0
  38. package/devTools/buildTypes.js +322 -0
  39. package/devTools/createComponent.js +155 -0
  40. package/devTools/extractJSDoc.js +395 -0
  41. package/devTools/processTemplate.js +500 -0
  42. package/devTools/updateComponentIndex.js +16 -0
  43. package/devTools/updateDemoViewIndex.js +90 -0
  44. package/eslint.config.cjs +6 -3
  45. package/index.d.ts +117 -59
  46. package/package.json +67 -22
  47. package/spellcheck.config.cjs +4 -0
  48. package/styled/README.md +329 -0
  49. package/theme/colors.js +15 -12
  50. package/theme/colors.test.js +28 -0
  51. package/utils/element.js +2 -2
  52. package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
@@ -120,10 +120,6 @@ const StyledLabel = styled(
120
120
  `,
121
121
  );
122
122
 
123
- const variant_enum = Object.freeze(['overlay', 'collapsible', 'inline', 'inline-after', 'simple']);
124
-
125
- const defaultOptions = { variant: 'simple' };
126
-
127
123
  /**
128
124
  * Label component with multiple display variants and input association.
129
125
  *
@@ -139,20 +135,61 @@ const defaultOptions = { variant: 'simple' };
139
135
  * @returns {Label} Label component instance
140
136
  */
141
137
  class Label extends StyledLabel {
142
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
143
- variant_enum = variant_enum;
138
+ static schema = {
139
+ variant: {
140
+ default: 'simple',
141
+ enum: ['overlay', 'collapsible', 'inline', 'inline-after', 'simple'],
142
+ set(value) {
143
+ this.removeClass(/\bvariant-\S+\b/g);
144
+ this.addClass(`variant-${value}`);
145
+
146
+ if (this._labelText) {
147
+ const shouldBeAfter = value === 'inline-after';
148
+ const isAfter = this._labelText.elem === this.elem.lastElementChild;
149
+ if (shouldBeAfter !== isAfter) this[shouldBeAfter ? 'append' : 'prepend'](this._labelText);
150
+ if (value === 'collapsible') {
151
+ this._labelText.elem.setAttribute('aria-expanded', this.hasClass('collapsed') ? 'false' : 'true');
152
+ }
153
+ }
154
+ },
155
+ },
156
+ label: {
157
+ set(value) {
158
+ if (typeof value === 'object') this._labelText?.setOptions(value);
159
+ else if (this._labelText) this._labelText.options.content = value;
160
+ },
161
+ },
162
+ collapsed: {
163
+ set(value) {
164
+ this[value ? 'addClass' : 'removeClass']('collapsed');
165
+ if (this.options.variant === 'collapsible' && this._labelText) {
166
+ this._labelText.elem.setAttribute('aria-expanded', value ? 'false' : 'true');
167
+ }
168
+ },
169
+ },
170
+ for: {
171
+ set(value) {
172
+ let forId = typeof value === 'string' ? value : value?.id || value?.elem?.id;
144
173
 
145
- constructor(options = {}, ...children) {
146
- if (typeof options === 'string') options = { label: options };
174
+ if (!forId && value?._component) {
175
+ forId = value.elem.id = value.uniqueId;
176
+ }
147
177
 
148
- super(
149
- {
150
- ...(children.length > 0 && { for: children[0] }),
151
- ...defaultOptions,
152
- ...options,
178
+ if (this._labelText) this._labelText.elem.htmlFor = forId ?? '';
153
179
  },
154
- ...children,
155
- );
180
+ },
181
+ };
182
+
183
+ static prepareOptions(options, children) {
184
+ return {
185
+ ...(children.length > 0 && { for: children[0] }),
186
+ ...options,
187
+ };
188
+ }
189
+
190
+ constructor(options = {}, ...children) {
191
+ // String shorthand must resolve before Component spreads options
192
+ super(typeof options === 'string' ? { label: options } : options, ...children);
156
193
  }
157
194
 
158
195
  build() {
@@ -174,41 +211,6 @@ class Label extends StyledLabel {
174
211
 
175
212
  this[this.options.variant === 'inline-after' ? 'append' : 'prepend'](this._labelText);
176
213
  }
177
-
178
- static handlers = {
179
- label(value) {
180
- if (typeof value === 'object') this._labelText?.setOptions(value);
181
- else if (this._labelText) this._labelText.options.content = value;
182
- },
183
- collapsed(value) {
184
- this[value ? 'addClass' : 'removeClass']('collapsed');
185
- if (this.options.variant === 'collapsible' && this._labelText) {
186
- this._labelText.elem.setAttribute('aria-expanded', value ? 'false' : 'true');
187
- }
188
- },
189
- variant(value) {
190
- this.removeClass(/\bvariant-\S+\b/g);
191
- this.addClass(`variant-${value}`);
192
-
193
- if (this._labelText) {
194
- const shouldBeAfter = value === 'inline-after';
195
- const isAfter = this._labelText.elem === this.elem.lastElementChild;
196
- if (shouldBeAfter !== isAfter) this[shouldBeAfter ? 'append' : 'prepend'](this._labelText);
197
- if (value === 'collapsible') {
198
- this._labelText.elem.setAttribute('aria-expanded', this.hasClass('collapsed') ? 'false' : 'true');
199
- }
200
- }
201
- },
202
- for(value) {
203
- let forId = typeof value === 'string' ? value : value?.id || value?.elem?.id;
204
-
205
- if (!forId && value?._component) {
206
- forId = value.elem.id = value.uniqueId;
207
- }
208
-
209
- if (this._labelText) this._labelText.elem.htmlFor = forId ?? '';
210
- },
211
- };
212
214
  }
213
215
 
214
216
  export default Label;
@@ -1,14 +1,5 @@
1
1
  import { TooltipWrapper } from '../TooltipWrapper';
2
2
 
3
- const defaultOptions = {
4
- tag: 'a',
5
- variant: 'link',
6
- get tooltip() {
7
- return { icon: 'link', style: { fontSize: '12px' } };
8
- },
9
- };
10
- const variant_enum = Object.freeze(['link', 'button']);
11
-
12
3
  /**
13
4
  * Link component with tooltip support and display variants.
14
5
  *
@@ -25,17 +16,20 @@ const variant_enum = Object.freeze(['link', 'button']);
25
16
  * @returns {Link} Link component instance
26
17
  */
27
18
  class Link extends TooltipWrapper {
28
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
29
- variant_enum = variant_enum;
30
-
31
- constructor(options = {}, ...children) {
32
- super({ ...defaultOptions, ...options }, ...children);
33
- }
34
-
35
- static handlers = {
36
- variant(value) {
37
- this.removeClass(/\bvariant-\S+\b/g);
38
- this.addClass(`variant-${value}`);
19
+ static schema = {
20
+ tag: { default: 'a' },
21
+ variant: {
22
+ default: 'link',
23
+ enum: ['link', 'button'],
24
+ set(value) {
25
+ this.removeClass(/\bvariant-\S+\b/g);
26
+ this.addClass(`variant-${value}`);
27
+ },
28
+ },
29
+ tooltip: {
30
+ get default() {
31
+ return { icon: 'link', style: { fontSize: '12px' } };
32
+ },
39
33
  },
40
34
  };
41
35
  }
@@ -42,41 +42,39 @@ const ListItem = styled(
42
42
  * @returns {List} List component instance
43
43
  */
44
44
  export default class List extends StyledComponent {
45
- constructor(options = {}, ...children) {
46
- super(
47
- {
48
- ...options,
49
- tag: 'ul',
45
+ static schema = {
46
+ tag: { default: 'ul' },
47
+ noStyle: {
48
+ set(value) {
49
+ this.toggleClass('no-style', !!value);
50
50
  },
51
- ...children,
52
- );
53
- }
54
-
55
- static handlers = {
56
- noStyle(value) {
57
- this.toggleClass('no-style', !!value);
58
51
  },
59
- items(value) {
60
- if (!value) {
61
- this.empty();
62
- return;
63
- }
52
+ items: {
53
+ set(value) {
54
+ if (!value) {
55
+ this.empty();
56
+ return;
57
+ }
64
58
 
65
- this.content(
66
- value.map(item => {
67
- const isContent = typeof item === 'string' || item?.elem || Array.isArray(item);
68
- const listItem = new ListItem({ tag: 'li', ...item?.listItemOptions });
59
+ this.content(
60
+ value.map(item => {
61
+ const isContent = typeof item === 'string' || item?.elem || Array.isArray(item);
62
+ const listItem = new ListItem({ tag: 'li', ...item?.listItemOptions });
63
+ // listItemOptions belongs to the li wrapper - strip it before passing item options along.
64
+ const itemOptions = isContent ? item : { ...item };
65
+ if (!isContent) delete itemOptions.listItemOptions;
69
66
 
70
- if (this.options.ListItemComponent) {
71
- listItem.content(new this.options.ListItemComponent(isContent ? { content: item } : item));
72
- } else if (item.ListItemComponent) {
73
- listItem.content(new item.ListItemComponent(item));
74
- } else if (isContent) listItem.content(item);
75
- else listItem.setOptions(item);
67
+ if (this.options.ListItemComponent) {
68
+ listItem.content(new this.options.ListItemComponent(isContent ? { content: item } : itemOptions));
69
+ } else if (item.ListItemComponent) {
70
+ listItem.content(new item.ListItemComponent(itemOptions));
71
+ } else if (isContent) listItem.content(item);
72
+ else listItem.setOptions(itemOptions);
76
73
 
77
- return listItem;
78
- }),
79
- );
74
+ return listItem;
75
+ }),
76
+ );
77
+ },
80
78
  },
81
79
  };
82
80
  }
@@ -37,23 +37,32 @@ const StyledList = styled(
37
37
  * borders, and click handling. Provides consistent menu appearance and behavior.
38
38
  * @param {object} [options={}] - Menu configuration options
39
39
  * @param {Array<*>} [options.items] - Array of menu items to render
40
- * @param {Function} [options.onSelect] - Handler called when menu item is selected
40
+ * @param {Function} [options.onSelect] - Handler for the "select" CustomEvent fired when a menu item is activated; the activation event is in event.detail
41
41
  * @param {Component} [options.ListItemComponent] - Custom component class for rendering menu items
42
42
  * @param {...(Component|HTMLElement|string)} children - Child elements to append
43
43
  * @returns {Menu} Menu component instance
44
44
  */
45
45
  export default class Menu extends StyledList {
46
- static handlers = {
47
- items(value, next) {
48
- next(value);
49
- if (!value) return;
50
- const items = Array.from(this.elem.children).filter(el => el.tagName === 'LI');
51
- items.forEach((li, i) => li.setAttribute('tabindex', i === 0 ? '0' : '-1'));
46
+ static schema = {
47
+ role: { default: 'menu' },
48
+ items: {
49
+ set(value, next) {
50
+ next(value);
51
+ if (!value) return;
52
+ const items = Array.from(this.elem.children).filter(el => el.tagName === 'LI');
53
+ items.forEach((li, i) => li.setAttribute('tabindex', i === 0 ? '0' : '-1'));
54
+ },
52
55
  },
53
56
  };
54
57
 
55
- constructor(options = {}, ...children) {
56
- super({ role: 'menu', ...options, onPointerPress: options.onSelect }, ...children);
58
+ static events = ['select'];
59
+
60
+ build() {
61
+ this.on({
62
+ targetEvent: 'pointerdown',
63
+ id: 'menuSelect',
64
+ callback: event => this.emit('select', event),
65
+ });
57
66
 
58
67
  this.on({
59
68
  targetEvent: 'keydown',
@@ -78,7 +87,7 @@ export default class Menu extends StyledList {
78
87
  next = items[items.length - 1];
79
88
  } else if ((e.key === 'Enter' || e.key === ' ') && currentIndex >= 0) {
80
89
  e.preventDefault();
81
- this.options.onSelect?.(e);
90
+ this.emit('select', e);
82
91
  return;
83
92
  }
84
93
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  > ./Menu.js
4
4
 
5
- Styled list whose items trigger an `onSelect` callback. The design decision: Menu routes selection through the same unified pointer+keyboard activation path as Button; keyboard and pointer users get the same experience without separate handlers.
5
+ Styled list whose items emit a registered `select` CustomEvent, with the activation event in `detail`. The design decision: Menu routes selection through the same unified pointer+keyboard activation path as Button; keyboard and pointer users get the same experience without separate handlers. `onSelect` is the option-form listener for the event.
6
6
 
7
7
  ## Selecting a menu item works the same way regardless of input method
8
8
 
9
- - clicking, touching, Space/Enter, and screen reader activation all invoke `onSelect`; one handler, no separate keyboard path
9
+ - clicking, touching, Space/Enter, and screen reader activation all emit `select`; one handler, no separate keyboard path
10
10
  - does clicking a menu item invoke onSelect?
11
11
 
12
12
  ## Menu renders any item format that List supports
@@ -1,8 +1,6 @@
1
1
  import { styled } from '../../styled';
2
2
  import { Popover } from '../Popover';
3
3
 
4
- const type_enum = Object.freeze(['info', 'success', 'warning', 'error']);
5
-
6
4
  const StyledPopover = styled(
7
5
  Popover,
8
6
  ({ colors, fonts }) => `
@@ -68,29 +66,41 @@ const StyledPopover = styled(
68
66
  * @returns {Notify} Notify component instance
69
67
  */
70
68
  export default class Notify extends StyledPopover {
71
- type_enum = type_enum;
69
+ static schema = {
70
+ type: { default: 'success', enum: ['info', 'success', 'warning', 'error'] },
71
+ timeout: {
72
+ set(value) {
73
+ clearTimeout(this.timeout);
74
+ if (!value) return;
75
+ this.timeout = setTimeout(() => this.destroy(), value);
76
+ this.replaceCleanup('timeout', () => clearTimeout(this.timeout));
77
+ },
78
+ },
79
+ };
72
80
 
73
- constructor(options = {}) {
74
- const { timeout, type = 'success' } = options;
75
- const icon =
76
- options.icon ||
77
- { info: 'circle-info', success: 'check', warning: 'triangle-exclamation', error: 'skull-crossbones' }[type];
81
+ static prepareOptions(options) {
82
+ const { type } = options;
78
83
 
79
- super({
84
+ return {
80
85
  role: type === 'error' || type === 'warning' ? 'alert' : 'status',
81
86
  'aria-atomic': 'true',
82
- onPointerPress: e => {
83
- if (!e.target.closest('button, [role="button"]')) this.destroy();
84
- },
85
- state: 'manual',
86
87
  ...options,
87
88
  addClass: [type].concat(options.addClass),
88
- icon,
89
- });
89
+ icon:
90
+ options.icon ||
91
+ { info: 'circle-info', success: 'check', warning: 'triangle-exclamation', error: 'skull-crossbones' }[type],
92
+ };
93
+ }
90
94
 
91
- if (timeout) {
92
- this.timeout = setTimeout(() => this.destroy(), timeout);
93
- this.addCleanup('timeout', () => clearTimeout(this.timeout));
94
- }
95
+ build() {
96
+ super.build();
97
+
98
+ this.on({
99
+ targetEvent: 'pointerdown',
100
+ id: 'notifyDismiss',
101
+ callback: e => {
102
+ if (!e.target.closest('button, [role="button"]')) this.destroy();
103
+ },
104
+ });
95
105
  }
96
106
  }
@@ -18,19 +18,6 @@ const StyledIcon = styled(
18
18
  `,
19
19
  );
20
20
 
21
- const defaultOptions = {
22
- uniqueId: true,
23
- state: 'manual',
24
- outsideClose: false,
25
- get viewport() {
26
- return document.documentElement;
27
- },
28
- get appendTo() {
29
- return document.body;
30
- },
31
- };
32
- const state_enum = Object.freeze(['auto', 'manual']);
33
-
34
21
  /**
35
22
  * Popover component using native HTML popover API with edge-aware positioning.
36
23
  *
@@ -50,45 +37,63 @@ const state_enum = Object.freeze(['auto', 'manual']);
50
37
  * @returns {Popover} Popover component instance
51
38
  */
52
39
  export default class Popover extends StyledIcon {
53
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
54
- state_enum = state_enum;
55
-
56
- constructor({ autoOpen = true, onConnected: userOnConnected, ...options } = {}, ...children) {
57
- super(
58
- {
59
- ...defaultOptions,
60
- onConnected: () => {
61
- if (autoOpen) {
62
- const timeoutId = setTimeout(() => this.show(), 200);
63
- this.replaceCleanup('autoOpen', () => clearTimeout(timeoutId));
64
- }
65
- userOnConnected?.();
66
- },
67
- ...options,
40
+ static schema = {
41
+ uniqueId: { default: true },
42
+ state: {
43
+ default: 'manual',
44
+ enum: ['auto', 'manual'],
45
+ set(value) {
46
+ this.elem.popover = value;
47
+ },
48
+ },
49
+ autoOpen: { default: true },
50
+ // Positioning and dismissal config read from this.options by show()/edgeAwarePlacement()
51
+ outsideClose: { default: false },
52
+ viewport: {
53
+ get default() {
54
+ return document.documentElement;
55
+ },
56
+ },
57
+ appendTo: {
58
+ get default() {
59
+ return document.body;
60
+ },
61
+ },
62
+ maxWidth: { default: 264 },
63
+ maxHeight: { default: 132 },
64
+ x: {
65
+ set() {
66
+ this._placeAtPoint();
67
+ },
68
+ },
69
+ y: {
70
+ set() {
71
+ this._placeAtPoint();
68
72
  },
69
- ...children,
70
- );
73
+ },
74
+ };
71
75
 
72
- if (this.options.x !== undefined && this.options.y !== undefined) this.edgeAwarePlacement(this.options);
76
+ build() {
77
+ this.on({
78
+ targetEvent: 'connected',
79
+ id: 'popoverAutoOpen',
80
+ callback: () => {
81
+ if (!this.options.autoOpen) return;
82
+ const timeoutId = setTimeout(() => this.show(), 200);
83
+ this.replaceCleanup('autoOpen', () => clearTimeout(timeoutId));
84
+ },
85
+ });
73
86
  }
74
87
 
75
- static handlers = {
76
- state(value) {
77
- this.elem.popover = value;
78
- },
79
- outsideClose() {},
80
- viewport() {},
81
- maxWidth() {},
82
- maxHeight() {},
83
- x() {},
84
- y() {},
85
- };
88
+ _placeAtPoint() {
89
+ if (this.options.x !== undefined && this.options.y !== undefined) this.edgeAwarePlacement(this.options);
90
+ }
86
91
 
87
92
  edgeAwarePlacement({
88
93
  x,
89
94
  y,
90
- maxHeight = this.options.maxHeight ?? 132,
91
- maxWidth = this.options.maxWidth ?? 264,
95
+ maxHeight = this.options.maxHeight,
96
+ maxWidth = this.options.maxWidth,
92
97
  padding = 24,
93
98
  viewport = this.options.viewport || this.options.appendTo,
94
99
  }) {
@@ -69,38 +69,42 @@ const RadioButtonInput = styled(
69
69
  * @returns {RadioButton} RadioButton component instance
70
70
  */
71
71
  class RadioButton extends Component {
72
- static handlers = {
73
- options(value) {
74
- this.empty();
72
+ static schema = {
73
+ options: {
74
+ set(value) {
75
+ this.empty();
75
76
 
76
- if (!value) return;
77
+ if (!value) return;
77
78
 
78
- this.append(
79
- value.map(
80
- option =>
81
- new RadioButtonLabel({
82
- tag: 'label',
83
- append: [
84
- new RadioButtonInput({
85
- tag: 'input',
86
- type: 'radio',
87
- value: option?.value || option,
88
- name: this.uniqueId,
89
- checked: this.options.value === (option?.value || option),
90
- onChange: event => {
91
- if (event.target.checked) this.options.value = event.target.value;
92
- },
93
- }),
94
- option?.label || option,
95
- ],
96
- }),
97
- ),
98
- );
79
+ this.append(
80
+ value.map(
81
+ option =>
82
+ new RadioButtonLabel({
83
+ tag: 'label',
84
+ append: [
85
+ new RadioButtonInput({
86
+ tag: 'input',
87
+ type: 'radio',
88
+ value: option?.value || option,
89
+ name: this.uniqueId,
90
+ checked: this.options.value === (option?.value || option),
91
+ onChange: event => {
92
+ if (event.target.checked) this.options.value = event.target.value;
93
+ },
94
+ }),
95
+ option?.label || option,
96
+ ],
97
+ }),
98
+ ),
99
+ );
100
+ },
99
101
  },
100
- value(value) {
101
- this.elem.querySelectorAll('input[type="radio"]').forEach(input => {
102
- input.checked = input.value === String(value);
103
- });
102
+ value: {
103
+ set(value) {
104
+ this.elem.querySelectorAll('input[type="radio"]').forEach(input => {
105
+ input.checked = input.value === String(value);
106
+ });
107
+ },
104
108
  },
105
109
  };
106
110
  }
@@ -16,20 +16,26 @@ import { routeToRegex } from './utils';
16
16
  * @returns {Router} Router component instance
17
17
  */
18
18
  class Router extends Component {
19
- constructor(options = {}, ...children) {
20
- super(
21
- {
22
- defaultPath: Object.keys(options.views)[0],
23
- ...options,
24
- style: {
25
- display: 'flex',
26
- flex: 1,
27
- overflow: 'hidden',
28
- ...options.style,
29
- },
19
+ // Routing config read from this.options by the path getters and renderView()
20
+ static schema = {
21
+ views: {},
22
+ defaultPath: {},
23
+ notFound: {},
24
+ onRenderView: {},
25
+ mode: { default: 'hash', enum: ['hash', 'history'] },
26
+ };
27
+
28
+ static prepareOptions(options) {
29
+ return {
30
+ defaultPath: Object.keys(options.views || {})[0],
31
+ ...options,
32
+ style: {
33
+ display: 'flex',
34
+ flex: 1,
35
+ overflow: 'hidden',
36
+ ...options.style,
30
37
  },
31
- ...children,
32
- );
38
+ };
33
39
  }
34
40
 
35
41
  /**
@@ -102,14 +108,6 @@ class Router extends Component {
102
108
  return parameters;
103
109
  }
104
110
 
105
- static handlers = {
106
- views() {},
107
- defaultPath() {},
108
- notFound() {},
109
- onRenderView() {},
110
- mode() {},
111
- };
112
-
113
111
  build() {
114
112
  const reRenderView = () => this.renderView();
115
113