@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/demo/index.html +28 -4
  3. package/dist/{a2536ddc.js → ac47779f.js} +246 -101
  4. package/dist/index.js +246 -101
  5. package/dist/static/svg/index.svg +1 -1
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/colorpicker/ColorPicker.js +264 -0
  11. package/out-tsc/src/colorpicker/ColorPicker.js.map +1 -0
  12. package/out-tsc/src/list/TembaMenu.js +0 -4
  13. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  14. package/out-tsc/src/utils/index.js +12 -0
  15. package/out-tsc/src/utils/index.js.map +1 -1
  16. package/out-tsc/src/vectoricon/index.js +9 -1
  17. package/out-tsc/src/vectoricon/index.js.map +1 -1
  18. package/out-tsc/temba-modules.js +2 -0
  19. package/out-tsc/temba-modules.js.map +1 -1
  20. package/out-tsc/test/temba-color-picker.test.js +49 -0
  21. package/out-tsc/test/temba-color-picker.test.js.map +1 -0
  22. package/package.json +2 -2
  23. package/screenshots/truth/colorpicker/default.png +0 -0
  24. package/screenshots/truth/colorpicker/focused.png +0 -0
  25. package/screenshots/truth/colorpicker/initialized.png +0 -0
  26. package/screenshots/truth/colorpicker/selected.png +0 -0
  27. package/src/colorpicker/ColorPicker.ts +260 -0
  28. package/src/list/TembaMenu.ts +0 -5
  29. package/src/untyped.d.ts +1 -0
  30. package/src/utils/index.ts +13 -0
  31. package/src/vectoricon/index.ts +10 -1
  32. package/static/svg/index.svg +1 -1
  33. package/static/svg/work/traced/browser.svg +1 -0
  34. package/static/svg/work/traced/calendar.svg +1 -0
  35. package/static/svg/work/traced/edit-05.svg +1 -0
  36. package/static/svg/work/traced/image-01.svg +1 -0
  37. package/static/svg/work/traced/list.svg +1 -0
  38. package/static/svg/work/traced/palette.svg +1 -0
  39. package/static/svg/work/traced/sliders-02.svg +1 -0
  40. package/static/svg/work/used/browser.svg +3 -0
  41. package/static/svg/work/used/calendar.svg +3 -0
  42. package/static/svg/work/used/edit-05.svg +3 -0
  43. package/static/svg/work/used/image-01.svg +3 -0
  44. package/static/svg/work/used/list.svg +3 -0
  45. package/static/svg/work/used/palette.svg +6 -0
  46. package/static/svg/work/used/sliders-02.svg +3 -0
  47. package/temba-modules.ts +2 -0
  48. package/test/temba-color-picker.test.ts +57 -0
  49. 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
+ }
@@ -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();
@@ -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;
@@ -1,5 +1,5 @@
1
1
  // for cache busting we dynamically generate a fingerprint, use yarn svg to update
2
- export const SVG_FINGERPRINT = 'cda7f9d00330ac4f7aa18ecf16bc26b1';
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
  }