juxscript 1.0.20 → 1.0.21

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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,111 +1,86 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { FormInput, FormInputState } from './base/FormInput.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['change'] as const;
3
6
 
4
- /**
5
- * Radio option
6
- */
7
7
  export interface RadioOption {
8
8
  label: string;
9
9
  value: string;
10
10
  disabled?: boolean;
11
11
  }
12
12
 
13
- /**
14
- * Radio component options
15
- */
16
13
  export interface RadioOptions {
17
14
  options?: RadioOption[];
18
15
  value?: string;
16
+ label?: string;
17
+ required?: boolean;
18
+ disabled?: boolean;
19
19
  name?: string;
20
- orientation?: 'vertical' | 'horizontal';
20
+ orientation?: 'vertical' | 'horizontal'; // ✅ Add orientation
21
21
  style?: string;
22
22
  class?: string;
23
+ onValidate?: (value: string) => boolean | string;
23
24
  }
24
25
 
25
- /**
26
- * Radio component state
27
- */
28
- type RadioState = {
26
+ interface RadioState extends FormInputState {
29
27
  options: RadioOption[];
30
28
  value: string;
31
- name: string;
32
- orientation: string;
33
- style: string;
34
- class: string;
35
- };
36
-
37
- /**
38
- * Radio component - Radio button group
39
- *
40
- * Usage:
41
- * jux.radio('size', {
42
- * options: [
43
- * { label: 'Small', value: 's' },
44
- * { label: 'Medium', value: 'm' },
45
- * { label: 'Large', value: 'l' }
46
- * ],
47
- * value: 'm'
48
- * })
49
- * .bind('change', (e) => console.log(e.target.value))
50
- * .render('#form');
51
- *
52
- * // Two-way binding with state
53
- * const sizeState = state('m');
54
- * jux.radio('size')
55
- * .sync('value', sizeState)
56
- * .render('#form');
57
- */
58
- export class Radio {
59
- state: RadioState;
60
- container: HTMLElement | null = null;
61
- _id: string;
62
- id: string;
63
-
64
- // CRITICAL: Store bind/sync instructions for deferred wiring
65
- private _bindings: Array<{ event: string, handler: Function }> = [];
66
- private _syncBindings: Array<{
67
- property: string,
68
- stateObj: State<any>,
69
- toState?: Function,
70
- toComponent?: Function
71
- }> = [];
29
+ orientation: 'vertical' | 'horizontal'; // ✅ Add to state
30
+ }
72
31
 
73
- constructor(id: string, options: RadioOptions = {}) {
74
- this._id = id;
75
- this.id = id;
32
+ export class Radio extends FormInput<RadioState> {
33
+ private _radioInputs: HTMLInputElement[] = [];
76
34
 
77
- this.state = {
35
+ constructor(id: string, options: RadioOptions = {}) {
36
+ super(id, {
78
37
  options: options.options ?? [],
79
38
  value: options.value ?? '',
39
+ label: options.label ?? '',
40
+ required: options.required ?? false,
41
+ disabled: options.disabled ?? false,
80
42
  name: options.name ?? id,
81
- orientation: options.orientation ?? 'vertical',
43
+ orientation: options.orientation ?? 'vertical', // ✅ Default to vertical
82
44
  style: options.style ?? '',
83
- class: options.class ?? ''
84
- };
45
+ class: options.class ?? '',
46
+ errorMessage: undefined
47
+ });
48
+
49
+ if (options.onValidate) {
50
+ this._onValidate = options.onValidate;
51
+ }
85
52
  }
86
53
 
87
- /* -------------------------
88
- * Fluent API
89
- * ------------------------- */
54
+ protected getTriggerEvents(): readonly string[] {
55
+ return TRIGGER_EVENTS;
56
+ }
90
57
 
91
- options(value: RadioOption[]): this {
92
- this.state.options = value;
93
- return this;
58
+ protected getCallbackEvents(): readonly string[] {
59
+ return CALLBACK_EVENTS;
94
60
  }
95
61
 
96
- addOption(option: RadioOption): this {
97
- this.state.options = [...this.state.options, option];
62
+ /* ═════════════════════════════════════════════════════════════════
63
+ * FLUENT API
64
+ * ═════════════════════════════════════════════════════════════════ */
65
+
66
+ // ✅ Inherited from FormInput/BaseComponent:
67
+ // - label(), required(), name(), onValidate()
68
+ // - validate(), isValid()
69
+ // - style(), class()
70
+ // - bind(), sync(), renderTo()
71
+ // - disabled(), enable(), disable()
72
+
73
+ options(value: RadioOption[]): this {
74
+ this.state.options = value;
98
75
  return this;
99
76
  }
100
77
 
101
78
  value(value: string): this {
102
- this.state.value = value;
103
- this._updateElement();
104
- return this;
79
+ return this.setValue(value);
105
80
  }
106
81
 
107
- name(value: string): this {
108
- this.state.name = value;
82
+ addOption(option: RadioOption): this {
83
+ this.state.options = [...this.state.options, option];
109
84
  return this;
110
85
  }
111
86
 
@@ -114,219 +89,293 @@ export class Radio {
114
89
  return this;
115
90
  }
116
91
 
117
- style(value: string): this {
118
- this.state.style = value;
119
- return this;
120
- }
92
+ /* ═════════════════════════════════════════════════════════════════
93
+ * FORM INPUT IMPLEMENTATION
94
+ * ═════════════════════════════════════════════════════════════════ */
121
95
 
122
- class(value: string): this {
123
- this.state.class = value;
124
- return this;
96
+ getValue(): string {
97
+ return this.state.value;
125
98
  }
126
99
 
127
- /**
128
- * Bind event handler (stores for wiring in render)
129
- * DOM events only: change, click, focus, blur, etc.
130
- */
131
- bind(event: string, handler: Function): this {
132
- this._bindings.push({ event, handler });
100
+ setValue(value: string): this {
101
+ this.state.value = value;
102
+
103
+ // Update all radio inputs
104
+ this._radioInputs.forEach(input => {
105
+ input.checked = input.value === value;
106
+ });
107
+
133
108
  return this;
134
109
  }
135
110
 
136
- /**
137
- * Two-way sync with state (stores for wiring in render)
138
- *
139
- * @param property - Component property to sync ('value', 'options')
140
- * @param stateObj - State object to sync with
141
- * @param toState - Optional transform function when going from component to state
142
- * @param toComponent - Optional transform function when going from state to component
143
- */
144
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
145
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
146
- throw new Error(`Radio.sync: Expected a State object for property "${property}"`);
111
+ protected _validateValue(value: string): boolean | string {
112
+ const { required, options } = this.state;
113
+
114
+ if (required && !value) {
115
+ return 'Please select an option';
147
116
  }
148
- this._syncBindings.push({ property, stateObj, toState, toComponent });
149
- return this;
150
- }
151
117
 
152
- /* -------------------------
153
- * Helpers
154
- * ------------------------- */
118
+ // Validate that value is one of the options
119
+ if (value && !options.some(opt => opt.value === value)) {
120
+ return 'Invalid option selected';
121
+ }
155
122
 
156
- private _updateElement(): void {
157
- const inputs = document.querySelectorAll(`input[name="${this.state.name}"]`);
158
- inputs.forEach((input) => {
159
- const radioInput = input as HTMLInputElement;
160
- radioInput.checked = radioInput.value === this.state.value;
161
- });
123
+ if (this._onValidate) {
124
+ const result = this._onValidate(value);
125
+ if (result !== true) {
126
+ return result || 'Invalid value';
127
+ }
128
+ }
129
+
130
+ return true;
162
131
  }
163
132
 
164
- getValue(): string {
165
- return this.state.value;
133
+ protected _buildInputElement(): HTMLElement {
134
+ // Radio uses a container, not a single input
135
+ const container = document.createElement('div');
136
+ container.className = 'jux-radio-options';
137
+ return container;
166
138
  }
167
139
 
168
- /* -------------------------
169
- * Render
170
- * ------------------------- */
140
+ /* ═════════════════════════════════════════════════════════════════
141
+ * RENDER
142
+ * ═════════════════════════════════════════════════════════════════ */
171
143
 
172
144
  render(targetId?: string): this {
173
- // === 1. SETUP: Get or create container ===
174
- let container: HTMLElement;
175
- if (targetId) {
176
- const target = document.querySelector(targetId);
177
- if (!target || !(target instanceof HTMLElement)) {
178
- throw new Error(`Radio: Target "${targetId}" not found`);
179
- }
180
- container = target;
181
- } else {
182
- container = getOrCreateContainer(this._id);
183
- }
184
- this.container = container;
145
+ const container = this._setupContainer(targetId);
185
146
 
186
- // === 2. PREPARE: Destructure state and check sync flags ===
187
- const { options, value, name, orientation, style, class: className } = this.state;
188
- const hasValueSync = this._syncBindings.some(b => b.property === 'value');
147
+ const { options, value, name, disabled, orientation, style, class: className } = this.state; // Destructure orientation
189
148
 
190
- // === 3. BUILD: Create DOM elements ===
149
+ // Build wrapper
191
150
  const wrapper = document.createElement('div');
192
- wrapper.className = `jux-radio jux-radio-${orientation}`;
151
+ wrapper.className = 'jux-radio';
193
152
  wrapper.id = this._id;
194
153
  if (className) wrapper.className += ` ${className}`;
195
154
  if (style) wrapper.setAttribute('style', style);
196
155
 
197
- const inputs: HTMLInputElement[] = [];
156
+ // Label
157
+ if (this.state.label) {
158
+ wrapper.appendChild(this._renderLabel());
159
+ }
160
+
161
+ // Radio options container
162
+ const optionsContainer = document.createElement('div');
163
+ optionsContainer.className = `jux-radio-options jux-radio-${orientation}`; // ✅ Add orientation class
164
+ this._inputElement = optionsContainer;
198
165
 
199
- options.forEach((option) => {
200
- const radioItem = document.createElement('div');
201
- radioItem.className = 'jux-radio-item';
166
+ this._radioInputs = [];
202
167
 
203
- const label = document.createElement('label');
204
- label.className = 'jux-radio-label';
168
+ options.forEach((option, index) => {
169
+ const radioWrapper = document.createElement('label');
170
+ radioWrapper.className = 'jux-radio-option';
205
171
 
206
172
  const input = document.createElement('input');
207
173
  input.type = 'radio';
208
174
  input.className = 'jux-radio-input';
175
+ input.id = `${this._id}-option-${index}`;
209
176
  input.name = name;
210
177
  input.value = option.value;
211
178
  input.checked = option.value === value;
212
- input.disabled = option.disabled || false;
213
- inputs.push(input);
179
+ input.disabled = disabled || option.disabled || false;
180
+
181
+ this._radioInputs.push(input);
182
+
183
+ const radioMark = document.createElement('span');
184
+ radioMark.className = 'jux-radio-mark';
214
185
 
215
- const checkmark = document.createElement('span');
216
- checkmark.className = 'jux-radio-checkmark';
186
+ const labelText = document.createElement('span');
187
+ labelText.className = 'jux-radio-label-text';
188
+ labelText.textContent = option.label;
217
189
 
218
- const text = document.createElement('span');
219
- text.className = 'jux-radio-text';
220
- text.textContent = option.label;
190
+ radioWrapper.appendChild(input);
191
+ radioWrapper.appendChild(radioMark);
192
+ radioWrapper.appendChild(labelText);
221
193
 
222
- label.appendChild(input);
223
- label.appendChild(checkmark);
224
- label.appendChild(text);
225
- radioItem.appendChild(label);
226
- wrapper.appendChild(radioItem);
194
+ optionsContainer.appendChild(radioWrapper);
227
195
  });
228
196
 
229
- // === 4. WIRE: Attach event listeners and sync bindings ===
197
+ wrapper.appendChild(optionsContainer);
230
198
 
231
- // Default behavior (only if NOT using sync)
232
- if (!hasValueSync) {
233
- inputs.forEach(input => {
234
- input.addEventListener('change', () => {
235
- if (input.checked) {
236
- this.state.value = input.value;
237
- }
238
- });
239
- });
240
- }
199
+ // Error element
200
+ wrapper.appendChild(this._renderError());
241
201
 
242
- // Wire custom bindings from .bind() calls
243
- this._bindings.forEach(({ event, handler }) => {
244
- wrapper.addEventListener(event, handler as EventListener);
245
- });
202
+ // Wire events
203
+ this._wireStandardEvents(wrapper);
246
204
 
247
- // Wire sync bindings from .sync() calls
248
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
249
- if (property === 'value') {
250
- const transformToState = toState || ((v: any) => v);
251
- const transformToComponent = toComponent || ((v: any) => String(v));
205
+ // Wire radio-specific sync
206
+ const valueSync = this._syncBindings.find(b => b.property === 'value');
252
207
 
253
- let isUpdating = false;
208
+ if (valueSync) {
209
+ const { stateObj, toState, toComponent } = valueSync;
210
+
211
+ const transformToState = toState || ((v: string) => v);
212
+ const transformToComponent = toComponent || ((v: any) => String(v));
213
+
214
+ let isUpdating = false;
215
+
216
+ // State → Component
217
+ stateObj.subscribe((val: any) => {
218
+ if (isUpdating) return;
219
+ const transformed = transformToComponent(val);
220
+ this.setValue(transformed);
221
+ });
254
222
 
255
- // StateComponent
256
- stateObj.subscribe((val: any) => {
223
+ // ComponentState
224
+ this._radioInputs.forEach(input => {
225
+ input.addEventListener('change', () => {
257
226
  if (isUpdating) return;
258
- const transformed = transformToComponent(val);
259
- inputs.forEach(input => {
260
- input.checked = input.value === transformed;
261
- });
262
- this.state.value = transformed;
263
- });
227
+ isUpdating = true;
228
+
229
+ const selectedValue = input.value;
230
+ this.state.value = selectedValue;
231
+ this._clearError();
264
232
 
265
- // Component State
266
- inputs.forEach(input => {
267
- input.addEventListener('change', () => {
268
- if (isUpdating || !input.checked) return;
269
- isUpdating = true;
233
+ const transformed = transformToState(selectedValue);
234
+ stateObj.set(transformed);
270
235
 
271
- const transformed = transformToState(input.value);
272
- this.state.value = input.value;
273
- stateObj.set(transformed);
236
+ // 🎯 Fire the change callback event
237
+ this._triggerCallback('change', selectedValue);
274
238
 
275
- setTimeout(() => { isUpdating = false; }, 0);
276
- });
239
+ setTimeout(() => { isUpdating = false; }, 0);
277
240
  });
278
- }
279
- else if (property === 'options') {
280
- const transformToComponent = toComponent || ((v: any) => v);
281
-
282
- stateObj.subscribe((val: any) => {
283
- const transformed = transformToComponent(val);
284
- this.state.options = transformed;
285
-
286
- // Re-render options
287
- wrapper.innerHTML = '';
288
- transformed.forEach((option: any) => {
289
- const radioItem = document.createElement('div');
290
- radioItem.className = 'jux-radio-item';
291
-
292
- const label = document.createElement('label');
293
- label.className = 'jux-radio-label';
294
-
295
- const input = document.createElement('input');
296
- input.type = 'radio';
297
- input.className = 'jux-radio-input';
298
- input.name = this.state.name;
299
- input.value = option.value;
300
- input.checked = option.value === this.state.value;
301
- input.disabled = option.disabled || false;
302
-
303
- const checkmark = document.createElement('span');
304
- checkmark.className = 'jux-radio-checkmark';
305
-
306
- const text = document.createElement('span');
307
- text.className = 'jux-radio-text';
308
- text.textContent = option.label;
309
-
310
- label.appendChild(input);
311
- label.appendChild(checkmark);
312
- label.appendChild(text);
313
- radioItem.appendChild(label);
314
- wrapper.appendChild(radioItem);
315
- });
241
+ });
242
+ } else {
243
+ // Default behavior without sync
244
+ this._radioInputs.forEach(input => {
245
+ input.addEventListener('change', () => {
246
+ this.state.value = input.value;
247
+ this._clearError();
248
+
249
+ // 🎯 Fire the change callback event
250
+ this._triggerCallback('change', input.value);
316
251
  });
317
- }
252
+ });
253
+ }
254
+
255
+ // Always add blur validation
256
+ this._radioInputs.forEach(input => {
257
+ input.addEventListener('blur', () => {
258
+ this.validate();
259
+ });
318
260
  });
319
261
 
320
- // === 5. RENDER: Append to DOM and finalize ===
262
+ // Sync label changes
263
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
264
+ if (labelSync) {
265
+ const transform = labelSync.toComponent || ((v: any) => String(v));
266
+ labelSync.stateObj.subscribe((val: any) => {
267
+ this.label(transform(val));
268
+ });
269
+ }
270
+
321
271
  container.appendChild(wrapper);
272
+ this._injectRadioStyles();
273
+
322
274
  return this;
323
275
  }
324
276
 
325
- renderTo(juxComponent: any): this {
326
- if (!juxComponent?._id) {
327
- throw new Error('Radio.renderTo: Invalid component');
328
- }
329
- return this.render(`#${juxComponent._id}`);
277
+ private _injectRadioStyles(): void {
278
+ const styleId = 'jux-radio-styles';
279
+ if (document.getElementById(styleId)) return;
280
+
281
+ const style = document.createElement('style');
282
+ style.id = styleId;
283
+ style.textContent = `
284
+ .jux-radio {
285
+ margin-bottom: 12px;
286
+ }
287
+
288
+ .jux-radio-options {
289
+ display: flex;
290
+ gap: 8px;
291
+ }
292
+
293
+ /* ✅ Vertical orientation (default) */
294
+ .jux-radio-vertical {
295
+ flex-direction: column;
296
+ }
297
+
298
+ /* ✅ Horizontal orientation */
299
+ .jux-radio-horizontal {
300
+ flex-direction: row;
301
+ flex-wrap: wrap;
302
+ }
303
+
304
+ .jux-radio-option {
305
+ display: inline-flex;
306
+ align-items: center;
307
+ cursor: pointer;
308
+ user-select: none;
309
+ position: relative;
310
+ }
311
+
312
+ .jux-radio-input {
313
+ position: absolute;
314
+ opacity: 0;
315
+ cursor: pointer;
316
+ height: 0;
317
+ width: 0;
318
+ }
319
+
320
+ .jux-radio-mark {
321
+ position: relative;
322
+ height: 20px;
323
+ width: 20px;
324
+ border: 2px solid #d1d5db;
325
+ border-radius: 50%;
326
+ background-color: white;
327
+ transition: all 0.2s;
328
+ display: flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ margin-right: 8px;
332
+ }
333
+
334
+ .jux-radio-mark::after {
335
+ content: '';
336
+ position: absolute;
337
+ width: 10px;
338
+ height: 10px;
339
+ border-radius: 50%;
340
+ background-color: white;
341
+ opacity: 0;
342
+ transition: opacity 0.2s;
343
+ }
344
+
345
+ .jux-radio-input:checked ~ .jux-radio-mark {
346
+ background-color: #3b82f6;
347
+ border-color: #3b82f6;
348
+ }
349
+
350
+ .jux-radio-input:checked ~ .jux-radio-mark::after {
351
+ opacity: 1;
352
+ }
353
+
354
+ .jux-radio-input:disabled ~ .jux-radio-mark {
355
+ background-color: #f3f4f6;
356
+ border-color: #d1d5db;
357
+ cursor: not-allowed;
358
+ }
359
+
360
+ .jux-radio-input:disabled ~ .jux-radio-label-text {
361
+ color: #9ca3af;
362
+ cursor: not-allowed;
363
+ }
364
+
365
+ .jux-radio-input:focus ~ .jux-radio-mark {
366
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
367
+ }
368
+
369
+ .jux-radio-input.jux-input-invalid ~ .jux-radio-mark {
370
+ border-color: #ef4444;
371
+ }
372
+
373
+ .jux-radio-label-text {
374
+ font-size: 14px;
375
+ color: #374151;
376
+ }
377
+ `;
378
+ document.head.appendChild(style);
330
379
  }
331
380
  }
332
381