@nyaruka/temba-components 0.49.1 → 0.49.2
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/CHANGELOG.md +7 -0
- package/demo/index.html +28 -4
- package/dist/{a2536ddc.js → ac47779f.js} +246 -101
- package/dist/index.js +246 -101
- package/dist/static/svg/index.svg +1 -1
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/dist/templates/components-body.html +1 -1
- package/dist/templates/components-head.html +1 -1
- package/out-tsc/src/colorpicker/ColorPicker.js +264 -0
- package/out-tsc/src/colorpicker/ColorPicker.js.map +1 -0
- package/out-tsc/src/list/TembaMenu.js +0 -4
- package/out-tsc/src/list/TembaMenu.js.map +1 -1
- package/out-tsc/src/utils/index.js +12 -0
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +9 -1
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-color-picker.test.js +49 -0
- package/out-tsc/test/temba-color-picker.test.js.map +1 -0
- package/package.json +2 -2
- package/screenshots/truth/colorpicker/default.png +0 -0
- package/screenshots/truth/colorpicker/focused.png +0 -0
- package/screenshots/truth/colorpicker/initialized.png +0 -0
- package/screenshots/truth/colorpicker/selected.png +0 -0
- package/src/colorpicker/ColorPicker.ts +260 -0
- package/src/list/TembaMenu.ts +0 -5
- package/src/untyped.d.ts +1 -0
- package/src/utils/index.ts +13 -0
- package/src/vectoricon/index.ts +10 -1
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/browser.svg +1 -0
- package/static/svg/work/traced/calendar.svg +1 -0
- package/static/svg/work/traced/edit-05.svg +1 -0
- package/static/svg/work/traced/image-01.svg +1 -0
- package/static/svg/work/traced/list.svg +1 -0
- package/static/svg/work/traced/palette.svg +1 -0
- package/static/svg/work/traced/sliders-02.svg +1 -0
- package/static/svg/work/used/browser.svg +3 -0
- package/static/svg/work/used/calendar.svg +3 -0
- package/static/svg/work/used/edit-05.svg +3 -0
- package/static/svg/work/used/image-01.svg +3 -0
- package/static/svg/work/used/list.svg +3 -0
- package/static/svg/work/used/palette.svg +6 -0
- package/static/svg/work/used/sliders-02.svg +3 -0
- package/temba-modules.ts +2 -0
- package/test/temba-color-picker.test.ts +57 -0
- package/web-test-runner.config.mjs +9 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { html, css } from 'lit';
|
|
2
|
+
import { FormElement } from '../FormElement';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { getClasses, hslToHex } from '../utils';
|
|
5
|
+
import { TextInput } from '../textinput/TextInput';
|
|
6
|
+
|
|
7
|
+
export class ColorPicker extends FormElement {
|
|
8
|
+
@property({ type: Boolean })
|
|
9
|
+
expanded = false;
|
|
10
|
+
|
|
11
|
+
@property({ type: String })
|
|
12
|
+
previewColor = '#ffffff';
|
|
13
|
+
|
|
14
|
+
@property({ type: String })
|
|
15
|
+
labelColor = '#ffffffee';
|
|
16
|
+
|
|
17
|
+
@property({ type: Boolean })
|
|
18
|
+
selecting = false;
|
|
19
|
+
|
|
20
|
+
@property({ type: Number }) hue: number;
|
|
21
|
+
@property({ type: Number }) saturation = 100;
|
|
22
|
+
@property({ type: Number }) lightness = 50;
|
|
23
|
+
@property({ type: String }) hex = '';
|
|
24
|
+
|
|
25
|
+
static get styles() {
|
|
26
|
+
return css`
|
|
27
|
+
:host {
|
|
28
|
+
color: var(--color-text);
|
|
29
|
+
display: inline-block;
|
|
30
|
+
--curvature: 0.55em;
|
|
31
|
+
width: 100%;
|
|
32
|
+
|
|
33
|
+
--temba-textinput-padding: 0.4em;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
temba-textinput {
|
|
37
|
+
margin-left: 0.3em;
|
|
38
|
+
width: 5em;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
temba-field {
|
|
42
|
+
display: block;
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.wrapper {
|
|
47
|
+
border: 1px solid var(--color-widget-border);
|
|
48
|
+
padding: calc(var(--curvature) / 2);
|
|
49
|
+
border-radius: calc(var(--curvature) * 1.5);
|
|
50
|
+
transition: all calc(var(--transition-speed) * 2) var(--bounce);
|
|
51
|
+
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-grow: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.picker-wrapper {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: row;
|
|
59
|
+
align-items: stretch;
|
|
60
|
+
transition: all calc(var(--transition-speed) * 2) var(--bounce);
|
|
61
|
+
flex-grow: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.preview {
|
|
65
|
+
width: initial;
|
|
66
|
+
border-radius: var(--curvature);
|
|
67
|
+
padding: 0.2em 0.5em;
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
border: 2px solid rgba(0, 0, 0, 0.1);
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition: transform calc(var(--transition-speed) * 0.5) var(--bounce);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.preview.selecting {
|
|
76
|
+
transform: scale(1.05);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.wrapper.expanded {
|
|
80
|
+
flex-grow: 1 !important;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.wrapper.expanded .picker-wrapper {
|
|
84
|
+
flex-grow: 1 !important;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.wrapper.expanded .preview {
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.wrapper.expanded .color-picker {
|
|
92
|
+
margin-left: calc(var(--curvature) / 2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.wrapper.expanded temba-textinput {
|
|
96
|
+
display: block;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.color-picker {
|
|
100
|
+
border-radius: var(--curvature);
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
transition: all calc(var(--transition-speed) * 2) var(--bounce);
|
|
103
|
+
flex-grow: 1;
|
|
104
|
+
position: relative;
|
|
105
|
+
width: 100%;
|
|
106
|
+
height: 100%;
|
|
107
|
+
background-image: linear-gradient(
|
|
108
|
+
to bottom,
|
|
109
|
+
rgba(0, 0, 0, 0) 60%,
|
|
110
|
+
rgba(0, 0, 0, 0.5) 90%,
|
|
111
|
+
rgba(0, 0, 0, 1)
|
|
112
|
+
),
|
|
113
|
+
linear-gradient(
|
|
114
|
+
to top,
|
|
115
|
+
rgba(255, 255, 255, 0) 60%,
|
|
116
|
+
rgba(255, 255, 255, 0.8) 90%,
|
|
117
|
+
rgba(255, 255, 255, 1)
|
|
118
|
+
),
|
|
119
|
+
linear-gradient(
|
|
120
|
+
to right,
|
|
121
|
+
hsla(0, 100%, 50%, 1),
|
|
122
|
+
hsla(60, 100%, 50%, 1),
|
|
123
|
+
hsla(120, 100%, 50%, 1),
|
|
124
|
+
hsla(180, 100%, 50%, 1),
|
|
125
|
+
hsla(240, 100%, 50%, 1),
|
|
126
|
+
hsla(300, 100%, 50%, 1),
|
|
127
|
+
hsla(360, 100%, 50%, 1)
|
|
128
|
+
);
|
|
129
|
+
mix-blend-mode: multiply;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.color-picker:focus {
|
|
133
|
+
outline: none;
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public updated(changed: Map<string, any>): void {
|
|
139
|
+
super.updated(changed);
|
|
140
|
+
|
|
141
|
+
if (changed.has('value')) {
|
|
142
|
+
this.previewColor = this.value || '#9c9c9c';
|
|
143
|
+
this.hex = this.value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (changed.has('selecting')) {
|
|
147
|
+
if (this.selecting) {
|
|
148
|
+
window.setTimeout(() => {
|
|
149
|
+
this.selecting = false;
|
|
150
|
+
}, 100);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (changed.has('previewLabel') && this.hue) {
|
|
155
|
+
this.hex = hslToHex(this.hue, this.saturation, this.lightness);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private handleBlur(event: FocusEvent) {
|
|
160
|
+
if (this.expanded) {
|
|
161
|
+
this.expanded = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private handleMouseOut(event: MouseEvent) {
|
|
166
|
+
this.previewColor = this.value;
|
|
167
|
+
this.hex = this.value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private handleMouseMove(event: MouseEvent) {
|
|
171
|
+
if (this.expanded) {
|
|
172
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
173
|
+
const x = event.clientX - rect.left;
|
|
174
|
+
const y = event.clientY - rect.top;
|
|
175
|
+
this.hue = (x / rect.width) * 360;
|
|
176
|
+
this.lightness = 100 - (y / rect.height) * 100;
|
|
177
|
+
this.previewColor = `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, 1)`;
|
|
178
|
+
this.hex = hslToHex(this.hue, this.saturation, this.lightness);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private handlePreviewClick(event: MouseEvent) {
|
|
183
|
+
this.expanded = !this.expanded;
|
|
184
|
+
this.selecting = true;
|
|
185
|
+
(this.shadowRoot.querySelector('.color-picker') as HTMLDivElement).focus();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private handleColorClick(event: MouseEvent) {
|
|
189
|
+
if (this.expanded) {
|
|
190
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
191
|
+
const x = event.clientX - rect.left;
|
|
192
|
+
const y = event.clientY - rect.top;
|
|
193
|
+
this.hue = (x / rect.width) * 360;
|
|
194
|
+
this.lightness = 100 - (y / rect.height) * 100;
|
|
195
|
+
this.previewColor = `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, 1)`;
|
|
196
|
+
this.setValue(this.hex);
|
|
197
|
+
this.selecting = true;
|
|
198
|
+
this.expanded = false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!this.expanded) {
|
|
202
|
+
//
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private handleHexInput(event: InputEvent) {
|
|
207
|
+
const hex = (event.target as TextInput).value;
|
|
208
|
+
if (hex.startsWith('#')) {
|
|
209
|
+
this.previewColor = hex;
|
|
210
|
+
this.setValue(hex);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public serializeValue(value: any): string {
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public render() {
|
|
219
|
+
return html`
|
|
220
|
+
<temba-field
|
|
221
|
+
name=${this.name}
|
|
222
|
+
.helpText=${this.helpText}
|
|
223
|
+
.errors=${this.errors}
|
|
224
|
+
.widgetOnly=${this.widgetOnly}
|
|
225
|
+
.hideLabel=${this.hideLabel}
|
|
226
|
+
.disabled=${this.disabled}
|
|
227
|
+
>
|
|
228
|
+
<div style="display:flex" tabindex="0">
|
|
229
|
+
<div class=${getClasses({ wrapper: true, expanded: this.expanded })}>
|
|
230
|
+
<div class=${getClasses({ 'picker-wrapper': true })}>
|
|
231
|
+
<div
|
|
232
|
+
class=${getClasses({
|
|
233
|
+
preview: true,
|
|
234
|
+
selecting: this.selecting,
|
|
235
|
+
})}
|
|
236
|
+
style="color:${this.labelColor};background:${this.previewColor}"
|
|
237
|
+
@click=${this.handlePreviewClick}
|
|
238
|
+
>
|
|
239
|
+
${this.label}
|
|
240
|
+
</div>
|
|
241
|
+
<div
|
|
242
|
+
class="color-picker"
|
|
243
|
+
tabindex="0"
|
|
244
|
+
@blur=${this.handleBlur}
|
|
245
|
+
@mousemove=${this.handleMouseMove}
|
|
246
|
+
@mouseout=${this.handleMouseOut}
|
|
247
|
+
@click=${this.handleColorClick}
|
|
248
|
+
></div>
|
|
249
|
+
</div>
|
|
250
|
+
<temba-textinput
|
|
251
|
+
value=${this.hex}
|
|
252
|
+
@input=${this.handleHexInput}
|
|
253
|
+
placeholder="#000000"
|
|
254
|
+
></temba-textinput>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</temba-field>
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
}
|
package/src/list/TembaMenu.ts
CHANGED
|
@@ -679,11 +679,6 @@ export class TembaMenu extends RapidElement {
|
|
|
679
679
|
this.wait = true;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
-
// once we've set our items check if we need to auto-select
|
|
683
|
-
if (event && item.items.length > 0) {
|
|
684
|
-
this.handleItemClicked(event, item.items[0]);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
682
|
this.requestUpdate('root');
|
|
688
683
|
this.scrollSelectedIntoView();
|
|
689
684
|
}
|
package/src/untyped.d.ts
CHANGED
|
@@ -15,5 +15,6 @@ declare function waitFor(millis: number);
|
|
|
15
15
|
declare function moveMouse(x: number, y: number);
|
|
16
16
|
declare function mouseDown();
|
|
17
17
|
declare function mouseUp();
|
|
18
|
+
declare function mouseClick(x: number, y: number);
|
|
18
19
|
declare function setViewport({}: any);
|
|
19
20
|
declare function waitForNetworkIdle();
|
package/src/utils/index.ts
CHANGED
|
@@ -647,6 +647,19 @@ export const getFullName = (user: User) => {
|
|
|
647
647
|
return user.email;
|
|
648
648
|
};
|
|
649
649
|
|
|
650
|
+
export const hslToHex = (h, s, l) => {
|
|
651
|
+
l /= 100;
|
|
652
|
+
const a = (s * Math.min(l, 1 - l)) / 100;
|
|
653
|
+
const f = n => {
|
|
654
|
+
const k = (n + h / 30) % 12;
|
|
655
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
656
|
+
return Math.round(255 * color)
|
|
657
|
+
.toString(16)
|
|
658
|
+
.padStart(2, '0'); // convert to Hex and prefix "0" if needed
|
|
659
|
+
};
|
|
660
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
661
|
+
};
|
|
662
|
+
|
|
650
663
|
export const renderAvatar = (input: {
|
|
651
664
|
name?: string;
|
|
652
665
|
user?: User;
|
package/src/vectoricon/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// for cache busting we dynamically generate a fingerprint, use yarn svg to update
|
|
2
|
-
export const SVG_FINGERPRINT = '
|
|
2
|
+
export const SVG_FINGERPRINT = 'f469b4b2409b0601ce9d11e1ca9e36e2';
|
|
3
3
|
|
|
4
4
|
// only icons below are included in the sprite sheet
|
|
5
5
|
export enum Icon {
|
|
@@ -49,6 +49,7 @@ export enum Icon {
|
|
|
49
49
|
checkbox = 'square',
|
|
50
50
|
checkbox_checked = 'check-square',
|
|
51
51
|
compose = 'send-01',
|
|
52
|
+
colors = 'palette',
|
|
52
53
|
contact = 'user-01',
|
|
53
54
|
contact_archived = 'archive',
|
|
54
55
|
contact_blocked = 'message-x-square',
|
|
@@ -76,6 +77,7 @@ export enum Icon {
|
|
|
76
77
|
group_smart = 'atom-01',
|
|
77
78
|
help = 'help-circle',
|
|
78
79
|
home = 'settings-02',
|
|
80
|
+
image = 'image-01',
|
|
79
81
|
inbox = 'inbox-01',
|
|
80
82
|
info = 'user-square',
|
|
81
83
|
label = 'tag-01',
|
|
@@ -146,4 +148,11 @@ export enum Icon {
|
|
|
146
148
|
bothub = 'bothub',
|
|
147
149
|
chatbase = 'chatbase',
|
|
148
150
|
dtone = 'dtone',
|
|
151
|
+
|
|
152
|
+
// demo
|
|
153
|
+
default = 'list',
|
|
154
|
+
datepicker = 'calendar',
|
|
155
|
+
slider = 'sliders-02',
|
|
156
|
+
select = 'browser',
|
|
157
|
+
input = 'edit-05',
|
|
149
158
|
}
|