@vanilla-bean/components 1.1.0 → 2.0.0
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.
- package/Component/Component.js +226 -40
- package/Component/Component.scenarios.js +2 -2
- package/Component/Component.test.js +427 -40
- package/Component/README.md +480 -0
- package/Component/observeElementConnection.js +1 -1
- package/Elem/README.md +373 -0
- package/README.md +2 -2
- package/components/BottomSheet/BottomSheet.js +8 -16
- package/components/BottomSheet/README.md +1 -1
- package/components/Button/Button.js +12 -18
- package/components/Button/Button.lld.md +1 -1
- package/components/Calendar/Calendar.js +139 -59
- package/components/Calendar/CalendarEvent.js +9 -4
- package/components/Calendar/Toolbar.js +28 -20
- package/components/Calendar/index.js +1 -0
- package/components/Code/Code.js +26 -30
- package/components/Code/Code.lld.md +1 -1
- package/components/ColorPicker/ColorPicker.js +58 -58
- package/components/Dialog/Dialog.js +79 -73
- package/components/Dialog/Dialog.lld.md +1 -1
- package/components/Dialog/README.md +10 -10
- package/components/Form/Form.js +21 -17
- package/components/Form/Form.lld.md +2 -2
- package/components/Form/README.md +3 -3
- package/components/Icon/Icon.js +19 -11
- package/components/Input/Input.js +85 -67
- package/components/Input/README.md +7 -9
- package/components/Keyboard/Key.js +7 -10
- package/components/Keyboard/Keyboard.js +38 -46
- package/components/Keyboard/Keyboard.lld.md +1 -1
- package/components/Label/Label.js +52 -50
- package/components/Link/Link.js +14 -20
- package/components/List/List.js +28 -30
- package/components/Menu/Menu.js +19 -10
- package/components/Menu/Menu.lld.md +2 -2
- package/components/Notify/Notify.js +29 -19
- package/components/Notify/Notify.lld.md +2 -2
- package/components/Page/Page.lld.md +1 -1
- package/components/Popover/Popover.js +49 -44
- package/components/RadioButton/RadioButton.js +33 -29
- package/components/RadioButton/RadioButton.lld.md +1 -1
- package/components/Router/README.md +12 -12
- package/components/Router/Router.js +19 -21
- package/components/Select/README.md +5 -5
- package/components/Select/Select.js +25 -31
- package/components/Table/README.md +4 -4
- package/components/Table/Table.js +55 -36
- package/components/Table/Table.lld.md +1 -1
- package/components/TagList/Tag.js +16 -14
- package/components/TagList/TagList.js +15 -8
- package/components/TagList/TagList.lld.md +1 -1
- package/components/Tooltip/Tooltip.js +12 -25
- package/components/TooltipWrapper/TooltipWrapper.js +55 -49
- package/components/TooltipWrapper/TooltipWrapper.lld.md +2 -2
- package/components/Whiteboard/Whiteboard.js +45 -43
- package/components/Whiteboard/Whiteboard.lld.md +1 -1
- package/devTools/build.js +43 -0
- package/devTools/buildTypes.js +322 -0
- package/devTools/createComponent.js +155 -0
- package/devTools/extractJSDoc.js +395 -0
- package/devTools/processTemplate.js +500 -0
- package/devTools/updateComponentIndex.js +16 -0
- package/devTools/updateDemoViewIndex.js +90 -0
- package/eslint.config.cjs +5 -2
- package/index.d.ts +120 -62
- package/package.json +23 -22
- package/spellcheck.config.cjs +3 -0
- package/styled/README.md +329 -0
- package/theme/.test.js +2 -2
- package/theme/README.md +8 -8
- package/theme/colors.js +16 -13
- package/theme/colors.test.js +28 -0
- package/utils/browser.js +1 -1
- package/utils/element.js +2 -2
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
|
@@ -3,7 +3,7 @@ import { styled } from '../../styled';
|
|
|
3
3
|
import { Component } from '../../Component';
|
|
4
4
|
|
|
5
5
|
import CalendarEvent from './CalendarEvent';
|
|
6
|
-
import Toolbar from './Toolbar';
|
|
6
|
+
import Toolbar, { VIEWS } from './Toolbar';
|
|
7
7
|
import { getDaysInMonth, toNth } from './utils';
|
|
8
8
|
|
|
9
9
|
const StyledComponent = styled(
|
|
@@ -18,12 +18,17 @@ const StyledComponent = styled(
|
|
|
18
18
|
& tr.title {
|
|
19
19
|
font-size: 1.3em;
|
|
20
20
|
height: 1.3em;
|
|
21
|
-
color: ${colors.
|
|
22
|
-
background-color: ${colors.
|
|
21
|
+
color: ${colors.lightest(colors.gray)};
|
|
22
|
+
background-color: ${colors.darkest(colors.gray)};
|
|
23
23
|
|
|
24
24
|
& td {
|
|
25
25
|
text-align: center;
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
& td:hover {
|
|
29
|
+
background-color: unset;
|
|
30
|
+
color: unset;
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -47,11 +52,11 @@ const StyledComponent = styled(
|
|
|
47
52
|
& div.time-block {
|
|
48
53
|
height: 3em;
|
|
49
54
|
cursor: pointer;
|
|
50
|
-
color:
|
|
51
|
-
background-color: ${colors.
|
|
55
|
+
color: ${colors.lighter(colors.gray)};
|
|
56
|
+
background-color: ${colors.darker(colors.gray)};
|
|
52
57
|
|
|
53
58
|
&:nth-child(odd) {
|
|
54
|
-
background-color: ${colors.
|
|
59
|
+
background-color: ${colors.darkest(colors.gray)};
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
&:last-of-type{
|
|
@@ -61,8 +66,15 @@ const StyledComponent = styled(
|
|
|
61
66
|
& span.time {
|
|
62
67
|
font-size: 12px;
|
|
63
68
|
margin: 2px;
|
|
69
|
+
padding: 1px 3px;
|
|
64
70
|
pointer-events: none;
|
|
65
71
|
}
|
|
72
|
+
|
|
73
|
+
&:hover span.time {
|
|
74
|
+
background-color: ${colors.light(colors.selected)};
|
|
75
|
+
color: ${colors.mostReadable(colors.light(colors.selected), [colors.white, colors.black])};
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
}
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
`,
|
|
@@ -99,40 +111,63 @@ const MonthDayCell = styled(
|
|
|
99
111
|
position: relative;
|
|
100
112
|
border-collapse: collapse;
|
|
101
113
|
vertical-align: top;
|
|
102
|
-
background-color: ${colors.
|
|
114
|
+
background-color: ${colors.darker(colors.gray)};
|
|
103
115
|
cursor: pointer;
|
|
104
116
|
padding: 3px;
|
|
117
|
+
/* definite height so the inner flex column can fill the cell */
|
|
118
|
+
height: 1px;
|
|
119
|
+
|
|
120
|
+
& div.cell-body {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
height: 100%;
|
|
124
|
+
}
|
|
105
125
|
|
|
106
126
|
& span.day-title {
|
|
107
127
|
display: block;
|
|
108
128
|
font-size: 1em;
|
|
109
129
|
padding: 2px;
|
|
110
|
-
background-color: ${colors.black
|
|
111
|
-
color: ${colors.
|
|
130
|
+
background-color: ${colors.alpha(colors.black, 0.2)};
|
|
131
|
+
color: ${colors.lightest(colors.gray)};
|
|
112
132
|
cursor: pointer;
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
|
|
116
|
-
background
|
|
135
|
+
& div.event-container {
|
|
136
|
+
background: ${colors.alpha(colors.black, 0.05)};
|
|
137
|
+
flex: 1;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
margin-top: 2px;
|
|
140
|
+
border-radius: 6px;
|
|
141
|
+
padding: 3px;
|
|
142
|
+
}
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
144
|
+
/* Pin the box color - the page theme's global td:hover darkening must not show through */
|
|
145
|
+
&:hover {
|
|
146
|
+
background-color: ${colors.darker(colors.gray)};
|
|
121
147
|
}
|
|
122
148
|
|
|
123
|
-
|
|
124
|
-
|
|
149
|
+
&:hover span.day-title,
|
|
150
|
+
&.today:hover span.day-title,
|
|
151
|
+
&.not-in-month:hover span.day-title {
|
|
152
|
+
background-color: ${colors.light(colors.selected)};
|
|
153
|
+
color: ${colors.mostReadable(colors.light(colors.selected), [colors.white, colors.black])};
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
}
|
|
125
156
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
157
|
+
&.today span.day-title {
|
|
158
|
+
box-shadow: inset 0 -3px 0 ${colors.selected};
|
|
159
|
+
color: ${colors.white};
|
|
160
|
+
font-weight: 700;
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
&.not-in-month {
|
|
132
|
-
background: #222;
|
|
133
|
-
|
|
134
164
|
& span.day-title {
|
|
135
165
|
color: ${colors.gray};
|
|
166
|
+
background: ${colors.alpha(colors.black, 0.3)};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
& div.event-container {
|
|
170
|
+
background: ${colors.alpha(colors.black, 0.3)};
|
|
136
171
|
}
|
|
137
172
|
}
|
|
138
173
|
`,
|
|
@@ -142,19 +177,28 @@ const WeekDayCell = styled(
|
|
|
142
177
|
Component,
|
|
143
178
|
({ colors }) => `
|
|
144
179
|
cursor: pointer;
|
|
145
|
-
background-color: ${colors.
|
|
180
|
+
background-color: ${colors.darker(colors.gray)};
|
|
146
181
|
min-height: 14.285714285714286%;
|
|
147
182
|
|
|
148
|
-
&.today {
|
|
149
|
-
background-color: ${colors.blue};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
183
|
& div.day-title {
|
|
153
184
|
cursor: pointer;
|
|
154
185
|
padding: 6px;
|
|
155
|
-
background-color: ${colors.black
|
|
186
|
+
background-color: ${colors.alpha(colors.black, 0.2)};
|
|
156
187
|
font-size: 1em;
|
|
157
|
-
color:
|
|
188
|
+
color: ${colors.lightest(colors.gray)};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
&:hover div.day-title,
|
|
192
|
+
&.today:hover div.day-title {
|
|
193
|
+
background-color: ${colors.light(colors.selected)};
|
|
194
|
+
color: ${colors.mostReadable(colors.light(colors.selected), [colors.white, colors.black])};
|
|
195
|
+
font-weight: 700;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
&.today div.day-title {
|
|
199
|
+
box-shadow: inset 0 -3px 0 ${colors.selected};
|
|
200
|
+
color: ${colors.white};
|
|
201
|
+
font-weight: 700;
|
|
158
202
|
}
|
|
159
203
|
|
|
160
204
|
& .event {
|
|
@@ -209,27 +253,64 @@ export const MONTHS = [
|
|
|
209
253
|
* @returns {Calendar} Calendar component instance
|
|
210
254
|
*/
|
|
211
255
|
class Calendar extends StyledComponent {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
height
|
|
217
|
-
registeredEvents: new Set(['selectDay', 'selectTime', 'selectEvent', 'newEvent']),
|
|
218
|
-
...options,
|
|
219
|
-
events: (options.events || []).map(eventItem => new CalendarEvent(eventItem)),
|
|
256
|
+
static schema = {
|
|
257
|
+
height: {
|
|
258
|
+
default: '420px',
|
|
259
|
+
set(value) {
|
|
260
|
+
this.elem.style.height = value;
|
|
220
261
|
},
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
},
|
|
263
|
+
// Date state read from this.options by the view renderers; navigation methods
|
|
264
|
+
// (setDate, next, previous) mutate several keys then re-render once
|
|
265
|
+
view: {
|
|
266
|
+
default: 'month',
|
|
267
|
+
enum: ['month', 'week', 'day'],
|
|
268
|
+
set() {
|
|
269
|
+
if (!this.rendered) return;
|
|
270
|
+
this.adjustDateToView();
|
|
271
|
+
this.renderView();
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
views: { default: VIEWS },
|
|
275
|
+
events: {
|
|
276
|
+
get default() {
|
|
277
|
+
return [];
|
|
278
|
+
},
|
|
279
|
+
set(value) {
|
|
280
|
+
// Normalize raw event objects; the re-assignment re-enters this set with instances only
|
|
281
|
+
if (value?.some(eventItem => !(eventItem instanceof CalendarEvent))) {
|
|
282
|
+
this.options.events = value.map(eventItem =>
|
|
283
|
+
eventItem instanceof CalendarEvent ? eventItem : new CalendarEvent(eventItem),
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
224
287
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
this.elem.style.height = value;
|
|
288
|
+
if (this.rendered) this.renderView();
|
|
289
|
+
},
|
|
228
290
|
},
|
|
291
|
+
year: {},
|
|
292
|
+
month: {},
|
|
293
|
+
day: {},
|
|
294
|
+
weekday: {},
|
|
295
|
+
display24h: { default: false },
|
|
229
296
|
};
|
|
230
297
|
|
|
298
|
+
static events = ['selectDay', 'selectTime', 'selectEvent', 'newEvent'];
|
|
299
|
+
|
|
300
|
+
static prepareOptions(options) {
|
|
301
|
+
return {
|
|
302
|
+
...options,
|
|
303
|
+
events: options.events.map(eventItem => new CalendarEvent(eventItem)),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
231
307
|
build() {
|
|
232
|
-
this.toolbar = new Toolbar({
|
|
308
|
+
this.toolbar = new Toolbar({
|
|
309
|
+
appendTo: this,
|
|
310
|
+
calendar: this,
|
|
311
|
+
views: this.options.views,
|
|
312
|
+
view: this.options.subscriber('view'),
|
|
313
|
+
});
|
|
233
314
|
this.wrapper = new CalendarWrapper({ appendTo: this });
|
|
234
315
|
|
|
235
316
|
if (this.options.month == null) {
|
|
@@ -240,9 +321,7 @@ class Calendar extends StyledComponent {
|
|
|
240
321
|
this.adjustDateToView();
|
|
241
322
|
}
|
|
242
323
|
|
|
243
|
-
|
|
244
|
-
else if (this.options.view === 'week') this.renderWeek();
|
|
245
|
-
else this.renderMonth();
|
|
324
|
+
this.renderView();
|
|
246
325
|
|
|
247
326
|
this.wrapper.elem.scrollTop = 0;
|
|
248
327
|
|
|
@@ -360,7 +439,6 @@ class Calendar extends StyledComponent {
|
|
|
360
439
|
|
|
361
440
|
const weekdayCell = new WeekDayCell({
|
|
362
441
|
addClass: isToday ? 'today' : undefined,
|
|
363
|
-
data: { at: cDay.getTime() },
|
|
364
442
|
onPointerPress: ({ target }) => {
|
|
365
443
|
this.emit('selectDay', { target, date: cellDate });
|
|
366
444
|
},
|
|
@@ -449,12 +527,12 @@ class Calendar extends StyledComponent {
|
|
|
449
527
|
const td = new MonthDayCell({
|
|
450
528
|
tag: 'td',
|
|
451
529
|
addClass: isCurrentMonth && currentDay === dayX ? 'today' : undefined,
|
|
452
|
-
data: { at: date.getTime() },
|
|
453
530
|
appendTo: tr,
|
|
454
531
|
onPointerPress: event => {
|
|
455
532
|
this.emit('selectDay', { target: event.target, date });
|
|
456
533
|
},
|
|
457
|
-
append:
|
|
534
|
+
append: new Component(
|
|
535
|
+
{ addClass: 'cell-body' },
|
|
458
536
|
new Component({
|
|
459
537
|
tag: 'span',
|
|
460
538
|
textContent: date.getDate(),
|
|
@@ -464,7 +542,7 @@ class Calendar extends StyledComponent {
|
|
|
464
542
|
},
|
|
465
543
|
}),
|
|
466
544
|
eventContainer,
|
|
467
|
-
|
|
545
|
+
),
|
|
468
546
|
});
|
|
469
547
|
|
|
470
548
|
if (dayX <= 0 || dayX > month.numberOfDays) td.addClass('not-in-month');
|
|
@@ -620,18 +698,20 @@ class Calendar extends StyledComponent {
|
|
|
620
698
|
* @returns {Calendar} This calendar instance for method chaining
|
|
621
699
|
*/
|
|
622
700
|
setView(view) {
|
|
623
|
-
if (this.options.view !== view)
|
|
624
|
-
this.options.view = view;
|
|
701
|
+
if (this.options.view !== view) this.options.view = view;
|
|
625
702
|
|
|
626
|
-
|
|
627
|
-
|
|
703
|
+
return this;
|
|
704
|
+
}
|
|
628
705
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
706
|
+
/**
|
|
707
|
+
* Clears the wrapper and renders the current view.
|
|
708
|
+
*/
|
|
709
|
+
renderView() {
|
|
710
|
+
this.wrapper.empty();
|
|
633
711
|
|
|
634
|
-
|
|
712
|
+
if (this.options.view === 'day') this.renderDay();
|
|
713
|
+
else if (this.options.view === 'week') this.renderWeek();
|
|
714
|
+
else this.renderMonth();
|
|
635
715
|
}
|
|
636
716
|
|
|
637
717
|
/**
|
|
@@ -7,12 +7,18 @@ const StyledEvent = styled(
|
|
|
7
7
|
Component,
|
|
8
8
|
({ colors }) => `
|
|
9
9
|
cursor: pointer;
|
|
10
|
-
color: ${colors.black};
|
|
11
|
-
background-color: ${colors.
|
|
10
|
+
color: ${colors.mostReadable(colors.selected, [colors.white, colors.black])};
|
|
11
|
+
background-color: ${colors.selected};
|
|
12
12
|
|
|
13
13
|
& div {
|
|
14
14
|
pointer-events: none;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
&:hover,
|
|
18
|
+
&:focus-visible {
|
|
19
|
+
outline: 2px solid ${colors.light(colors.selected)};
|
|
20
|
+
outline-offset: -2px;
|
|
21
|
+
}
|
|
16
22
|
`,
|
|
17
23
|
);
|
|
18
24
|
|
|
@@ -51,10 +57,9 @@ export default class CalendarEvent {
|
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
this.eventElem = new StyledEvent({
|
|
54
|
-
data: { at: this.at },
|
|
55
60
|
className: 'event',
|
|
56
61
|
role: 'button',
|
|
57
|
-
|
|
62
|
+
tabIndex: 0,
|
|
58
63
|
'aria-label': `${this.label}, ${formattedTime}${this.duration ? formatDuration(this.duration, ', ') : ''}`,
|
|
59
64
|
style: { backgroundColor: this.color },
|
|
60
65
|
append: [
|
|
@@ -22,24 +22,30 @@ const Right = styled(
|
|
|
22
22
|
`,
|
|
23
23
|
);
|
|
24
24
|
|
|
25
|
-
const VIEWS = ['day', 'week', 'month'];
|
|
25
|
+
export const VIEWS = Object.freeze(['day', 'week', 'month']);
|
|
26
26
|
|
|
27
27
|
class Toolbar extends Component {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
28
|
+
static schema = {
|
|
29
|
+
views: { default: VIEWS },
|
|
30
|
+
calendar: {},
|
|
31
|
+
view: {
|
|
32
|
+
set(value) {
|
|
33
|
+
for (const [view, button] of Object.entries(this._viewButtons || {})) {
|
|
34
|
+
button.toggleClass('pressed', view === value);
|
|
35
|
+
}
|
|
37
36
|
},
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
},
|
|
38
|
+
};
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
static prepareOptions(options) {
|
|
41
|
+
return {
|
|
42
|
+
...options,
|
|
43
|
+
style: {
|
|
44
|
+
display: 'flex',
|
|
45
|
+
justifyContent: 'space-between',
|
|
46
|
+
...options.style,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
build() {
|
|
@@ -51,7 +57,7 @@ class Toolbar extends Component {
|
|
|
51
57
|
textContent: { next: '>', today: 'Today', previous: '<' }[action],
|
|
52
58
|
'aria-label': action,
|
|
53
59
|
onPointerPress: () => {
|
|
54
|
-
this.calendar[action]();
|
|
60
|
+
this.options.calendar[action]();
|
|
55
61
|
},
|
|
56
62
|
}),
|
|
57
63
|
),
|
|
@@ -59,17 +65,19 @@ class Toolbar extends Component {
|
|
|
59
65
|
|
|
60
66
|
this.title = new Title({ appendTo: this });
|
|
61
67
|
|
|
68
|
+
this._viewButtons = {};
|
|
69
|
+
|
|
62
70
|
new Right({
|
|
63
71
|
appendTo: this,
|
|
64
|
-
append:
|
|
72
|
+
append: this.options.views.map(
|
|
65
73
|
view =>
|
|
66
|
-
new Button({
|
|
67
|
-
addClass: [`set-${view}
|
|
74
|
+
(this._viewButtons[view] = new Button({
|
|
75
|
+
addClass: [`set-${view}`],
|
|
68
76
|
textContent: capitalize(view),
|
|
69
77
|
onPointerPress: () => {
|
|
70
|
-
this.calendar.setView(view);
|
|
78
|
+
this.options.calendar.setView(view);
|
|
71
79
|
},
|
|
72
|
-
}),
|
|
80
|
+
})),
|
|
73
81
|
),
|
|
74
82
|
});
|
|
75
83
|
}
|
package/components/Code/Code.js
CHANGED
|
@@ -4,8 +4,6 @@ import { Component } from '../../Component';
|
|
|
4
4
|
import { Button } from '../Button';
|
|
5
5
|
import { Notify } from '../Notify';
|
|
6
6
|
|
|
7
|
-
const defaultOptions = { tag: 'code', language: 'javascript', multiline: 'auto' };
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* Code display component with syntax highlighting and optional copy functionality.
|
|
11
9
|
*
|
|
@@ -21,21 +19,34 @@ const defaultOptions = { tag: 'code', language: 'javascript', multiline: 'auto'
|
|
|
21
19
|
* @returns {Code} Code component instance
|
|
22
20
|
*/
|
|
23
21
|
class Code extends Component {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
static schema = {
|
|
23
|
+
language: {
|
|
24
|
+
default: 'javascript',
|
|
25
|
+
set(value) {
|
|
26
|
+
if (this.options.multiline && this._code) {
|
|
27
|
+
this._code.removeClass(/\blanguage-\S+\b/g);
|
|
28
|
+
this._code.addClass(`language-${value}`);
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
...defaultOptions,
|
|
34
|
-
...options,
|
|
35
|
-
...(options.multiline ? { tag: 'pre' } : { tag: 'code' }),
|
|
31
|
+
this.removeClass(/\blanguage-\S+\b/g);
|
|
32
|
+
this.addClass(`language-${value}`);
|
|
36
33
|
},
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
},
|
|
35
|
+
code: {
|
|
36
|
+
set(value) {
|
|
37
|
+
(this.options.multiline && this._code ? this._code : this).elem.textContent = value;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
// Resolved in prepareOptions (decides the tag); read from this.options in build()
|
|
41
|
+
multiline: { default: 'auto' },
|
|
42
|
+
copyButton: { default: false },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
static prepareOptions(options) {
|
|
46
|
+
const multiline =
|
|
47
|
+
(options.multiline ?? 'auto') === 'auto' ? (options.code || '').includes('\n') : options.multiline;
|
|
48
|
+
|
|
49
|
+
return { ...options, multiline, tag: multiline ? 'pre' : 'code' };
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
build() {
|
|
@@ -64,21 +75,6 @@ class Code extends Component {
|
|
|
64
75
|
}),
|
|
65
76
|
);
|
|
66
77
|
}
|
|
67
|
-
|
|
68
|
-
static handlers = {
|
|
69
|
-
language(value) {
|
|
70
|
-
if (this.options.multiline && this._code) {
|
|
71
|
-
this._code.removeClass(/\blanguage-\S+\b/g);
|
|
72
|
-
this._code.addClass(`language-${value}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this.removeClass(/\blanguage-\S+\b/g);
|
|
76
|
-
this.addClass(`language-${value}`);
|
|
77
|
-
},
|
|
78
|
-
code(value) {
|
|
79
|
-
(this.options.multiline && this._code ? this._code : this).elem.textContent = value;
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
export default Code;
|
|
@@ -15,7 +15,7 @@ Code display that automatically promotes from inline to block when the content s
|
|
|
15
15
|
- the `language` option adds a `language-{name}` class; highlighting libraries target this class by convention
|
|
16
16
|
- does the language option add an identifiable class to the code element?
|
|
17
17
|
|
|
18
|
-
## Copy confirms success visually
|
|
18
|
+
## Copy confirms success visually - clipboard writes can fail silently
|
|
19
19
|
|
|
20
20
|
- the copy button shows a notification on success so the user knows the copy worked rather than discovering it failed on paste
|
|
21
21
|
- does a successful copy show the user a confirmation?
|
|
@@ -143,8 +143,6 @@ const ColorSwatch = styled(
|
|
|
143
143
|
{ icon: 'fill-drip' },
|
|
144
144
|
);
|
|
145
145
|
|
|
146
|
-
const defaultOptions = { tag: 'div', value: '#666', onChange: () => {} };
|
|
147
|
-
|
|
148
146
|
/**
|
|
149
147
|
* Interactive color picker component with HSL controls and color swatches.
|
|
150
148
|
*
|
|
@@ -159,19 +157,53 @@ const defaultOptions = { tag: 'div', value: '#666', onChange: () => {} };
|
|
|
159
157
|
* @returns {ColorPicker} ColorPicker component instance
|
|
160
158
|
*/
|
|
161
159
|
class ColorPicker extends StyledInput {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
160
|
+
static schema = {
|
|
161
|
+
tag: { default: 'div' },
|
|
162
|
+
value: {
|
|
163
|
+
default: '#666',
|
|
164
|
+
set(value) {
|
|
165
|
+
if (!value) return;
|
|
166
|
+
|
|
167
|
+
const { h, sv, v, color, hslString } = this.parseValue(value);
|
|
168
|
+
|
|
169
|
+
this.hslString = hslString;
|
|
170
|
+
this.color = color;
|
|
171
|
+
// Preserve hue when achromatic (sv=0 loses hue info) or when TinyColor
|
|
172
|
+
// wraps 360 to 0 (same color, different position on the slider)
|
|
173
|
+
const isCircularWrap = sv > 0 && h === 0 && this.hue > 350;
|
|
174
|
+
if (!isCircularWrap) this.hue = sv > 0 ? h : (this.hue ?? h);
|
|
175
|
+
this.sv = sv;
|
|
176
|
+
this.v = v;
|
|
177
|
+
|
|
178
|
+
this.elem.style.backgroundColor = hslString;
|
|
179
|
+
|
|
180
|
+
this.pickerArea?.elem && (this.pickerArea.elem.style.backgroundColor = `hsl(${this.hue}, 100%, 50%)`);
|
|
181
|
+
|
|
182
|
+
const tryPosition = () => {
|
|
183
|
+
if (this.pickerArea?.elem?.clientWidth > 0) {
|
|
184
|
+
this._positionIndicators();
|
|
185
|
+
} else {
|
|
186
|
+
requestAnimationFrame(tryPosition);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
requestAnimationFrame(tryPosition);
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
onChange: { default: () => {} },
|
|
193
|
+
swatches: {
|
|
194
|
+
get default() {
|
|
195
|
+
return [];
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
170
199
|
|
|
171
|
-
|
|
200
|
+
static prepareOptions(options) {
|
|
201
|
+
return options.value === 'random' ? { ...options, value: randomColor().toHslString() } : options;
|
|
172
202
|
}
|
|
173
203
|
|
|
174
204
|
build() {
|
|
205
|
+
this.pointers = {};
|
|
206
|
+
|
|
175
207
|
this.textInput = new Input({
|
|
176
208
|
type: 'text',
|
|
177
209
|
value: this.options.subscriber('value', value => this.hslString || value),
|
|
@@ -204,23 +236,21 @@ class ColorPicker extends StyledInput {
|
|
|
204
236
|
sizeObserver.observe(this.pickerArea.elem);
|
|
205
237
|
this.addCleanup('sizeObserver', () => sizeObserver.disconnect());
|
|
206
238
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}),
|
|
221
|
-
});
|
|
239
|
+
this.options.swatches.forEach(color => {
|
|
240
|
+
new ColorSwatch({
|
|
241
|
+
appendTo: this.elem,
|
|
242
|
+
|
|
243
|
+
onPointerPress: () => this.change(color),
|
|
244
|
+
...(color === 'random'
|
|
245
|
+
? { addClass: ['rainbow'] }
|
|
246
|
+
: {
|
|
247
|
+
style: {
|
|
248
|
+
backgroundColor: color,
|
|
249
|
+
color: theme.colors.mostReadable(color, [theme.colors.white, theme.colors.black]),
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
222
252
|
});
|
|
223
|
-
}
|
|
253
|
+
});
|
|
224
254
|
}
|
|
225
255
|
|
|
226
256
|
/**
|
|
@@ -283,7 +313,7 @@ class ColorPicker extends StyledInput {
|
|
|
283
313
|
|
|
284
314
|
const clamp = (val, min, max) => Math.min(max, Math.max(min, val));
|
|
285
315
|
|
|
286
|
-
// HSV: x = saturation, y = (1 - value)
|
|
316
|
+
// HSV: x = saturation, y = (1 - value) - matches the gradient exactly
|
|
287
317
|
const pickerX = clamp((this.sv ?? 0) * pickerW, 0, pickerW);
|
|
288
318
|
const pickerY = clamp((1 - (this.v ?? 1)) * pickerH, 0, pickerH);
|
|
289
319
|
this.pickerIndicator.elem.style.transform = `translate3d(${pickerX}px, ${pickerY}px, 0)`;
|
|
@@ -292,36 +322,6 @@ class ColorPicker extends StyledInput {
|
|
|
292
322
|
this._setHueBackground(this.hue ?? 0, hueW);
|
|
293
323
|
}
|
|
294
324
|
|
|
295
|
-
static handlers = {
|
|
296
|
-
value(value) {
|
|
297
|
-
if (!value) return;
|
|
298
|
-
|
|
299
|
-
const { h, sv, v, color, hslString } = this.parseValue(value);
|
|
300
|
-
|
|
301
|
-
this.hslString = hslString;
|
|
302
|
-
this.color = color;
|
|
303
|
-
// Preserve hue when achromatic (sv=0 loses hue info) or when TinyColor
|
|
304
|
-
// wraps 360 to 0 (same color, different position on the slider)
|
|
305
|
-
const isCircularWrap = sv > 0 && h === 0 && this.hue > 350;
|
|
306
|
-
if (!isCircularWrap) this.hue = sv > 0 ? h : (this.hue ?? h);
|
|
307
|
-
this.sv = sv;
|
|
308
|
-
this.v = v;
|
|
309
|
-
|
|
310
|
-
this.elem.style.backgroundColor = hslString;
|
|
311
|
-
|
|
312
|
-
this.pickerArea?.elem && (this.pickerArea.elem.style.backgroundColor = `hsl(${this.hue}, 100%, 50%)`);
|
|
313
|
-
|
|
314
|
-
const tryPosition = () => {
|
|
315
|
-
if (this.pickerArea?.elem?.clientWidth > 0) {
|
|
316
|
-
this._positionIndicators();
|
|
317
|
-
} else {
|
|
318
|
-
requestAnimationFrame(tryPosition);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
requestAnimationFrame(tryPosition);
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
|
|
325
325
|
getPosition({ clientX, clientY }) {
|
|
326
326
|
return { x: clientX, y: clientY };
|
|
327
327
|
}
|