@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
@@ -1,13 +1,6 @@
1
1
  import { Elem } from '../../Elem';
2
2
  import { Input } from '../Input';
3
3
 
4
- const defaultOptions = {
5
- tag: 'select',
6
- get priorityOptions() {
7
- return new Set(['textContent', 'content', 'appendTo', 'prependTo', 'options']);
8
- },
9
- };
10
-
11
4
  /**
12
5
  * Select dropdown component extending Input with dynamic option management.
13
6
  *
@@ -21,33 +14,34 @@ const defaultOptions = {
21
14
  * @returns {Select} Select component instance
22
15
  */
23
16
  class Select extends Input {
24
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
25
-
26
- constructor(options = {}, ...children) {
27
- super({ ...defaultOptions, ...options }, ...children);
28
- }
17
+ static schema = {
18
+ tag: { default: 'select' },
19
+ autocomplete: { default: 'off' },
20
+ options: {
21
+ set(value) {
22
+ this.empty();
29
23
 
30
- static handlers = {
31
- options(value) {
32
- this.empty();
24
+ if (!value) return;
33
25
 
34
- if (!value) return;
35
-
36
- for (const option of value) {
37
- // Optgroup: { label: 'Group', options: [...] }
38
- if (typeof option === 'object' && Array.isArray(option.options)) {
39
- const group = document.createElement('optgroup');
40
- if (option.label) group.label = option.label;
41
- for (const o of option.options) {
42
- group.append(new Elem({ tag: 'option', ...(typeof o === 'object' ? o : { label: o, value: o }) }).elem);
26
+ for (const option of value) {
27
+ // Optgroup: { label: 'Group', options: [...] }
28
+ if (typeof option === 'object' && Array.isArray(option.options)) {
29
+ const group = document.createElement('optgroup');
30
+ if (option.label) group.label = option.label;
31
+ for (const o of option.options) {
32
+ group.append(new Elem({ tag: 'option', ...(typeof o === 'object' ? o : { label: o, value: o }) }).elem);
33
+ }
34
+ this.elem.append(group);
35
+ } else {
36
+ this.append(
37
+ new Elem({ tag: 'option', ...(typeof option === 'object' ? option : { label: option, value: option }) }),
38
+ );
43
39
  }
44
- this.elem.append(group);
45
- } else {
46
- this.append(
47
- new Elem({ tag: 'option', ...(typeof option === 'object' ? option : { label: option, value: option }) }),
48
- );
49
40
  }
50
- }
41
+
42
+ // Re-apply the current value - option elements may not have existed when value was processed
43
+ if (this.options.value !== undefined) this.elem.value = this.options.value;
44
+ },
51
45
  },
52
46
  };
53
47
 
@@ -4,8 +4,6 @@ import { Elem } from '../../Elem';
4
4
  import { Component } from '../../Component';
5
5
  import { Icon } from '../Icon';
6
6
 
7
- const defaultOptions = { tag: 'table' };
8
-
9
7
  /**
10
8
  * Data table component with sorting, column configuration, and footer support.
11
9
  *
@@ -23,37 +21,55 @@ const defaultOptions = { tag: 'table' };
23
21
  * @returns {Table} Table component instance
24
22
  */
25
23
  class Table extends Component {
26
- constructor(options = {}, ...children) {
27
- super(
28
- {
29
- ...defaultOptions,
30
- onSort: (property, direction) => {
31
- this.options.data = this.options.data.sort(orderBy({ property, direction }));
32
- },
33
- ...options,
34
- columns: (options.columns || []).map(column =>
35
- typeof column === 'string' ? { key: column, content: capitalize(column) } : column,
36
- ),
37
- footer: options.footer?.map(column => (typeof column === 'string' ? { content: capitalize(column) } : column)),
24
+ static schema = {
25
+ tag: { default: 'table' },
26
+ sortDirection: {
27
+ enum: ['asc', 'desc'],
28
+ set(value) {
29
+ if (!this.rendered) return;
30
+ if (this.options.onSort) this.options.onSort(this.options.sortProperty, value);
31
+ else
32
+ this.options.data = this.options.data.sort(
33
+ orderBy({ property: this.options.sortProperty, direction: value }),
34
+ );
38
35
  },
39
- ...children,
40
- );
41
- }
42
-
43
- static handlers = {
44
- sortDirection(value) {
45
- if (this.rendered) this.options.onSort(this.options.sortProperty, value);
46
36
  },
47
- data() {
48
- this._renderTable();
37
+ data: {
38
+ set() {
39
+ this._renderTable();
40
+ },
49
41
  },
50
- selection() {
51
- this._renderTable();
42
+ selection: {
43
+ set() {
44
+ this._renderTable();
45
+ },
52
46
  },
47
+ columns: {
48
+ set() {
49
+ if (this.rendered) this._renderTable();
50
+ },
51
+ },
52
+ footer: {
53
+ set() {
54
+ if (this.rendered) this._renderTable();
55
+ },
56
+ },
57
+ // Read from this.options at sort time; sortProperty changes are always followed
58
+ // by a sortDirection change, which drives the re-render
59
+ sortProperty: {},
60
+ onSort: {},
53
61
  };
54
62
 
55
63
  _renderTable() {
56
64
  const value = this.options.data;
65
+ // Columns and footer accept string shorthand - normalized at read time so
66
+ // construction and reactive assignment take the same path
67
+ const columns = (this.options.columns || []).map(column =>
68
+ typeof column === 'string' ? { key: column, content: capitalize(column) } : column,
69
+ );
70
+ const footer = this.options.footer?.map(column =>
71
+ typeof column === 'string' ? { content: capitalize(column) } : column,
72
+ );
57
73
 
58
74
  this._sortSubscribers?.forEach(sub => sub.destroy?.());
59
75
  this._sortSubscribers = null;
@@ -75,19 +91,22 @@ class Table extends Component {
75
91
  this.thead.append(
76
92
  new Component(
77
93
  { tag: 'tr' },
78
- this.options.columns.map(column => {
79
- const th = new Component({ tag: 'th', ...column, scope: 'col' });
94
+ columns.map(column => {
95
+ // key/sort/dataColumn are column config for the Table itself - keep them off the th elem
96
+ const { key, sort, ...columnOptions } = column;
97
+ delete columnOptions.dataColumn;
98
+ const th = new Component({ tag: 'th', ...columnOptions, scope: 'col' });
80
99
 
81
- if (column.sort) {
100
+ if (sort) {
82
101
  const iconSub = this.options.subscriber('sortDirection', () => {
83
- if (this.options.sortProperty !== column.key) return 'sort';
102
+ if (this.options.sortProperty !== key) return 'sort';
84
103
 
85
104
  return this.options.sortDirection === 'asc' ? 'sort-down' : 'sort-up';
86
105
  });
87
106
  const styleSub = this.options.subscriber('sortDirection', () => ({
88
107
  display: 'inline',
89
108
  marginLeft: '6px',
90
- ...(this.options.sortProperty !== column.key
109
+ ...(this.options.sortProperty !== key
91
110
  ? { color: theme.colors.dark(theme.colors.gray) }
92
111
  : { color: theme.colors.white }),
93
112
  }));
@@ -96,7 +115,7 @@ class Table extends Component {
96
115
 
97
116
  const updateAriaSort = () => {
98
117
  let ariaSort = 'none';
99
- if (this.options.sortProperty === column.key) {
118
+ if (this.options.sortProperty === key) {
100
119
  ariaSort = this.options.sortDirection === 'asc' ? 'ascending' : 'descending';
101
120
  }
102
121
  th.elem.setAttribute('aria-sort', ariaSort);
@@ -121,12 +140,12 @@ class Table extends Component {
121
140
  });
122
141
 
123
142
  th.onPointerPress(() => {
124
- if (this.options.sortProperty === column.key) {
143
+ if (this.options.sortProperty === key) {
125
144
  this.options.sortDirection = this.options.sortDirection === 'asc' ? 'desc' : 'asc';
126
145
  return;
127
146
  }
128
147
 
129
- this.options.sortProperty = column.key;
148
+ this.options.sortProperty = key;
130
149
  this.options.sortDirection = 'desc';
131
150
  });
132
151
  }
@@ -141,7 +160,7 @@ class Table extends Component {
141
160
  rowData =>
142
161
  new Component(
143
162
  { tag: 'tr' },
144
- this.options.columns.map(
163
+ columns.map(
145
164
  column =>
146
165
  new Component(
147
166
  {
@@ -157,11 +176,11 @@ class Table extends Component {
157
176
  ),
158
177
  );
159
178
 
160
- if (this.options.footer) {
179
+ if (footer) {
161
180
  this.tfoot.append(
162
181
  new Component(
163
182
  { tag: 'tr' },
164
- this.options.footer.map(footData => new Component({ tag: 'td', ...footData })),
183
+ footer.map(footData => new Component({ tag: 'td', ...footData })),
165
184
  ),
166
185
  );
167
186
  }
@@ -52,14 +52,23 @@ const StyledComponent = styled(
52
52
  `,
53
53
  );
54
54
 
55
- const defaultOptions = { tag: 'li', readOnly: false };
56
-
57
55
  class Tag extends StyledComponent {
58
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
59
-
60
- constructor(options = {}, ...children) {
61
- super({ tabindex: '0', ...defaultOptions, ...options }, ...children);
62
- }
56
+ static schema = {
57
+ tag: { default: 'li' },
58
+ tabIndex: { default: 0 },
59
+ readOnly: {
60
+ default: false,
61
+ set(value) {
62
+ this.toggleClass('readOnly', !!value);
63
+ },
64
+ },
65
+ textContent: {
66
+ set(value) {
67
+ this.elem.setAttribute('data-value', value);
68
+ this._textSpan.elem.textContent = value;
69
+ },
70
+ },
71
+ };
63
72
 
64
73
  build() {
65
74
  this._textSpan = new Elem({ tag: 'span', appendTo: this });
@@ -72,13 +81,6 @@ class Tag extends StyledComponent {
72
81
  });
73
82
  }
74
83
  }
75
-
76
- static handlers = {
77
- textContent(value) {
78
- this.elem.setAttribute('data-value', value);
79
- this._textSpan.elem.textContent = value;
80
- },
81
- };
82
84
  }
83
85
 
84
86
  export default Tag;
@@ -32,8 +32,6 @@ const TagListInput = styled(
32
32
  `,
33
33
  );
34
34
 
35
- const defaultOptions = { readOnly: false };
36
-
37
35
  /**
38
36
  * Interactive tag list component with add/remove functionality and input support.
39
37
  *
@@ -47,11 +45,20 @@ const defaultOptions = { readOnly: false };
47
45
  * @returns {TagList} TagList component instance
48
46
  */
49
47
  class TagList extends StyledComponent {
50
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
51
-
52
- constructor(options = {}, ...children) {
53
- super({ ...options, tag: 'ul' }, ...children);
54
- }
48
+ static schema = {
49
+ tag: { default: 'ul' },
50
+ readOnly: {
51
+ default: false,
52
+ set(value) {
53
+ this.toggleClass('readOnly', !!value);
54
+ },
55
+ },
56
+ tags: {
57
+ get default() {
58
+ return [];
59
+ },
60
+ },
61
+ };
55
62
 
56
63
  _addTag(value) {
57
64
  value = (value || '').trim();
@@ -91,7 +98,7 @@ class TagList extends StyledComponent {
91
98
  }
92
99
 
93
100
  this.append([
94
- ...(this.options.tags || []).map(textContent => new Tag({ readOnly: this.options.readOnly, textContent })),
101
+ ...this.options.tags.map(textContent => new Tag({ readOnly: this.options.readOnly, textContent })),
95
102
  ...(this.addTag ? [this.addTag] : []),
96
103
  ]);
97
104
  }
@@ -80,8 +80,6 @@ const StyledPopover = styled(
80
80
  `,
81
81
  );
82
82
 
83
- const defaultOptions = { position: 'topRight', autoOpen: false };
84
-
85
83
  const position_enum = Object.freeze([
86
84
  'center',
87
85
  'top',
@@ -108,32 +106,21 @@ const position_enum = Object.freeze([
108
106
  * @returns {Tooltip} Tooltip component instance
109
107
  */
110
108
  class Tooltip extends StyledPopover {
111
- position_enum = position_enum;
112
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
113
-
114
- constructor(options = {}, ...children) {
115
- super(
116
- {
117
- ...defaultOptions,
118
- ...options,
119
- addClass: ['tooltip'].concat(options.addClass),
109
+ static schema = {
110
+ autoOpen: { default: false },
111
+ position: {
112
+ default: 'topRight',
113
+ enum: position_enum,
114
+ set(value) {
115
+ this.removeClass(...position_enum);
116
+ this.addClass(value);
120
117
  },
121
- ...children,
122
- );
123
- }
124
-
125
- static handlers = {
126
- position(value) {
127
- if (!position_enum.includes(value)) {
128
- throw new Error(
129
- `"${value}" is not a valid position. The position must be one of the following values: ${position_enum.join(', ')}`,
130
- );
131
- }
132
-
133
- this.removeClass(...position_enum);
134
- this.addClass(value);
135
118
  },
136
119
  };
120
+
121
+ static prepareOptions(options) {
122
+ return { ...options, addClass: ['tooltip'].concat(options.addClass) };
123
+ }
137
124
  }
138
125
 
139
126
  export default Tooltip;
@@ -29,61 +29,67 @@ const StyledIcon = styled(
29
29
  * @returns {TooltipWrapper} TooltipWrapper component instance
30
30
  */
31
31
  export default class TooltipWrapper extends StyledIcon {
32
- static handlers = {
33
- tooltip(value) {
34
- if (value == null) return;
35
- const tooltipOptions = typeof value === 'object' ? value : { textContent: value };
32
+ static schema = {
33
+ tooltip: {
34
+ set(value) {
35
+ this._applyTooltip(value);
36
+ },
37
+ },
38
+ };
36
39
 
37
- if (this._tooltip) {
38
- this._tooltip.setOptions(tooltipOptions);
39
- } else {
40
- this._tooltip = new Tooltip({
41
- appendTo: this.elem,
42
- ...tooltipOptions,
43
- });
40
+ _applyTooltip(value) {
41
+ if (value == null) return;
42
+ const tooltipOptions = typeof value === 'object' ? value : { textContent: value };
44
43
 
45
- this.addClass('has-tooltip');
44
+ if (this._tooltip) {
45
+ this._tooltip.setOptions(tooltipOptions);
46
+ } else {
47
+ this._tooltip = new Tooltip({
48
+ appendTo: this.elem,
49
+ ...tooltipOptions,
50
+ });
46
51
 
47
- this.on({
48
- targetEvent: 'pointerover',
49
- callback: ({ clientX, clientY }) => {
50
- this.tooltipTimeout = setTimeout(() => this._tooltip?.show({ x: clientX, y: clientY }), 700);
51
- },
52
- });
52
+ this.addClass('has-tooltip');
53
53
 
54
- this.on({
55
- targetEvent: 'pointerout',
56
- callback: () => {
57
- clearTimeout(this.tooltipTimeout);
58
- if (!this.elem.contains(document.activeElement)) this._tooltip?.hide();
59
- },
60
- });
54
+ this.on({
55
+ targetEvent: 'pointerover',
56
+ callback: ({ clientX, clientY }) => {
57
+ this.tooltipTimeout = setTimeout(() => this._tooltip?.show({ x: clientX, y: clientY }), 700);
58
+ },
59
+ });
61
60
 
62
- this.on({
63
- targetEvent: 'focus',
64
- callback: () => {
65
- clearTimeout(this.tooltipTimeout);
66
- const { left, bottom } = this.elem.getBoundingClientRect();
67
- this._tooltip?.show({ x: left, y: bottom });
68
- },
69
- });
61
+ this.on({
62
+ targetEvent: 'pointerout',
63
+ callback: () => {
64
+ clearTimeout(this.tooltipTimeout);
65
+ if (!this.elem.contains(document.activeElement)) this._tooltip?.hide();
66
+ },
67
+ });
70
68
 
71
- this.on({
72
- targetEvent: 'blur',
73
- callback: () => {
74
- clearTimeout(this.tooltipTimeout);
75
- this._tooltip?.hide();
76
- },
77
- });
69
+ this.on({
70
+ targetEvent: 'focus',
71
+ callback: () => {
72
+ clearTimeout(this.tooltipTimeout);
73
+ const { left, bottom } = this.elem.getBoundingClientRect();
74
+ this._tooltip?.show({ x: left, y: bottom });
75
+ },
76
+ });
78
77
 
79
- this.replaceCleanup('tooltip', () => {
80
- if (this._tooltip) {
81
- this._tooltip.destroy?.();
82
- this._tooltip = null;
83
- }
78
+ this.on({
79
+ targetEvent: 'blur',
80
+ callback: () => {
84
81
  clearTimeout(this.tooltipTimeout);
85
- });
86
- }
87
- },
88
- };
82
+ this._tooltip?.hide();
83
+ },
84
+ });
85
+
86
+ this.replaceCleanup('tooltip', () => {
87
+ if (this._tooltip) {
88
+ this._tooltip.destroy?.();
89
+ this._tooltip = null;
90
+ }
91
+ clearTimeout(this.tooltipTimeout);
92
+ });
93
+ }
94
+ }
89
95
  }
@@ -1,17 +1,6 @@
1
1
  import { throttle } from '../../utils/data';
2
2
  import { Component } from '../../Component';
3
3
 
4
- const defaultOptions = {
5
- tag: 'canvas',
6
- background: '#FFF',
7
- color: '#000',
8
- lineWidth: 3,
9
- width: '200px',
10
- height: '200px',
11
- readOnly: false,
12
- registeredEvents: new Set(['line', 'draw']),
13
- };
14
-
15
4
  /**
16
5
  * Interactive drawing canvas component with multi-touch support and line rendering.
17
6
  *
@@ -31,22 +20,52 @@ const defaultOptions = {
31
20
  * @returns {Whiteboard} Whiteboard component instance
32
21
  */
33
22
  export default class Whiteboard extends Component {
34
- defaultOptions = { ...super.defaultOptions, ...defaultOptions };
35
-
36
- constructor(options = {}, ...children) {
37
- super(
38
- {
39
- ...defaultOptions,
40
- ...options,
41
- style: {
42
- cursor: 'crosshair',
43
- touchAction: 'none',
44
- borderRadius: '3px',
45
- ...options.style,
46
- },
23
+ static schema = {
24
+ tag: { default: 'canvas' },
25
+ width: {
26
+ default: '200px',
27
+ set(value) {
28
+ this.elem.width = Number.parseInt(value);
29
+ this.elem.style.width = value;
47
30
  },
48
- ...children,
49
- );
31
+ },
32
+ height: {
33
+ default: '200px',
34
+ set(value) {
35
+ this.elem.height = Number.parseInt(value);
36
+ this.elem.style.height = value;
37
+ },
38
+ },
39
+ background: {
40
+ default: '#FFF',
41
+ set(value) {
42
+ this.elem.style.background = value;
43
+ },
44
+ },
45
+ lines: {
46
+ set(value) {
47
+ value.forEach(line => this.drawLine(line));
48
+ },
49
+ },
50
+ // Drawing config read from this.options during draw interactions
51
+ color: { default: '#000' },
52
+ lineWidth: { default: 3 },
53
+ readOnly: { default: false },
54
+ drawThrottle: {},
55
+ };
56
+
57
+ static events = ['line', 'draw'];
58
+
59
+ static prepareOptions(options) {
60
+ return {
61
+ ...options,
62
+ style: {
63
+ cursor: 'crosshair',
64
+ touchAction: 'none',
65
+ borderRadius: '3px',
66
+ ...options.style,
67
+ },
68
+ };
50
69
  }
51
70
 
52
71
  build() {
@@ -59,23 +78,6 @@ export default class Whiteboard extends Component {
59
78
  this.replaceCleanup('pointerdown', () => this.elem.removeEventListener('pointerdown', boundInteractionInit));
60
79
  }
61
80
 
62
- static handlers = {
63
- width(value) {
64
- this.elem.width = Number.parseInt(value);
65
- this.elem.style.width = value;
66
- },
67
- height(value) {
68
- this.elem.height = Number.parseInt(value);
69
- this.elem.style.height = value;
70
- },
71
- lines(value) {
72
- value.forEach(line => this.drawLine(line));
73
- },
74
- background(value) {
75
- this.elem.style.background = value;
76
- },
77
- };
78
-
79
81
  getPosition({ offsetX, offsetY }) {
80
82
  return { x: offsetX.toFixed(3), y: offsetY.toFixed(3) };
81
83
  }
@@ -0,0 +1,43 @@
1
+ import { watch } from 'fs';
2
+ import { buildLoader as asText } from '../plugins/asText';
3
+ import { buildLoader as markdownLoader } from '../plugins/markdownLoader';
4
+
5
+ const build = async () => {
6
+ console.log('Building...');
7
+
8
+ const buildResults = await Bun.build({
9
+ entrypoints: ['demo/index.html'],
10
+ outdir: 'demo/build',
11
+ define: {
12
+ 'process.env.AUTOPREFIXER_GRID': 'undefined',
13
+ 'process.cwd': 'String',
14
+ },
15
+ plugins: [asText, markdownLoader],
16
+ });
17
+
18
+ console.log(buildResults.success ? 'build.success' : buildResults.logs);
19
+
20
+ return buildResults;
21
+ };
22
+
23
+ const enableWatcher = process.argv[2] === '--watch';
24
+ const watcherIgnore = /\.asText$|^demo\/build|^\.|^img\/|^devTools\//;
25
+
26
+ if (enableWatcher) {
27
+ console.log(`Initializing watcher`);
28
+
29
+ const watcher = watch(`${import.meta.dir}/..`, { recursive: true }, (event, filename) => {
30
+ if (watcherIgnore.test(filename)) return;
31
+
32
+ console.log(`Detected ${event} in ${filename}`);
33
+
34
+ build();
35
+ });
36
+
37
+ process.on('SIGINT', () => {
38
+ watcher.close();
39
+ process.exit(0);
40
+ });
41
+ }
42
+
43
+ await build();