@nectary/components 0.43.0 → 0.44.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/accordion/index.js +3 -3
- package/color-menu/index.js +2 -2
- package/date-picker/index.js +173 -64
- package/date-picker/types.d.ts +6 -0
- package/date-picker/utils.d.ts +6 -3
- package/date-picker/utils.js +19 -4
- package/file-drop/types.d.ts +2 -2
- package/file-picker/types.d.ts +1 -1
- package/package.json +1 -1
- package/segmented-icon-control/index.js +3 -3
- package/select-menu/index.js +3 -3
- package/stop-events/index.js +3 -3
- package/tile-control/index.js +3 -3
- package/utils/csv.d.ts +4 -2
- package/utils/csv.js +14 -13
package/accordion/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineCustomElement, getAttribute, getBooleanAttribute,
|
|
1
|
+
import { defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getReactEventHandler, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv } from '../utils';
|
|
2
2
|
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%}::slotted(sinch-accordion-item){flex-shrink:1}</style><div id="wrapper"><slot></slot></div>';
|
|
3
3
|
const template = document.createElement('template');
|
|
4
4
|
template.innerHTML = templateHTML;
|
|
@@ -69,9 +69,9 @@ defineCustomElement('sinch-accordion', class extends NectaryElement {
|
|
|
69
69
|
};
|
|
70
70
|
#onValueChange(csv) {
|
|
71
71
|
if (this.multiple) {
|
|
72
|
-
const values =
|
|
72
|
+
const values = unpackCsv(csv);
|
|
73
73
|
for (const $option of this.#$slot.assignedElements()) {
|
|
74
|
-
const isChecked = !getBooleanAttribute($option, 'disabled') && values.
|
|
74
|
+
const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
|
|
75
75
|
updateBooleanAttribute($option, 'data-checked', isChecked);
|
|
76
76
|
}
|
|
77
77
|
} else {
|
package/color-menu/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import '../tooltip';
|
|
|
3
3
|
import '../icons/check';
|
|
4
4
|
import { getSwatchColorFg } from '../color-swatch/utils';
|
|
5
5
|
import { lightColorNames, darkColorNames, vibrantColorNames } from '../theme/colors';
|
|
6
|
-
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute,
|
|
6
|
+
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute, unpackCsv, getIntegerAttribute, getReactEventHandler, getRect, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
7
7
|
const optionTemplateHTML = '<div class="option" role="option"><sinch-tooltip inverted class="tooltip"><div class="swatch-wrapper"><sinch-color-swatch class="swatch"></sinch-color-swatch><sinch-icon-check class="check"></sinch-icon-check></div></sinch-tooltip></div>';
|
|
8
8
|
const templateHTML = '<style>:host{display:block;outline:0}#listbox{display:flex;flex-direction:row;flex-wrap:wrap;padding:4px 10px;overflow-y:auto}#listbox:empty{display:none}.option{padding:12px 6px;--sinch-color-icon:var(--sinch-color-stormy-500)}.swatch-wrapper{position:relative;cursor:pointer;width:32px;height:32px}.swatch-wrapper::after{content:"";position:absolute;width:34px;height:34px;inset:-3px;border:2px solid transparent;border-radius:50%;pointer-events:none}.option[data-selected]:not([data-checked]) .swatch-wrapper::after{border-color:var(--sinch-color-border-focus)}.check{display:none;position:absolute;left:4px;top:4px;pointer-events:none;--sinch-size-icon:24px}.option[data-checked] .check{display:block}</style><div id="listbox" role="presentation"></div>';
|
|
9
9
|
import { getParentOption } from './utils';
|
|
@@ -137,7 +137,7 @@ defineCustomElement('sinch-color-menu', class extends NectaryElement {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
this.#prevColorsValue = colorsValue;
|
|
140
|
-
const colorNames =
|
|
140
|
+
const colorNames = unpackCsv(colorsValue ?? `${lightColorNames},${vibrantColorNames},${darkColorNames}`);
|
|
141
141
|
const fragment = document.createDocumentFragment();
|
|
142
142
|
for (const color of colorNames) {
|
|
143
143
|
if (color.length === 0) {
|
package/date-picker/index.js
CHANGED
|
@@ -6,9 +6,9 @@ import '../icons/keyboard-double-arrow-left';
|
|
|
6
6
|
import '../icons/delete-outline';
|
|
7
7
|
import '../icons/today';
|
|
8
8
|
import '../text';
|
|
9
|
-
import { defineCustomElement, getAttribute, getReactEventHandler, getRect, NectaryElement, setClass, updateAttribute, updateBooleanAttribute } from '../utils';
|
|
10
|
-
const templateHTML = '<style>:host{display:block;outline:0}#content{width:fit-content;box-sizing:border-box;padding:16px;display:flex;flex-direction:column;gap:8px}#month{display:flex;flex-direction:column;row-gap:8px}.week{display:flex;flex-direction:row;column-gap:8px}.week.empty{display:none}.day{all:initial;font:var(--sinch-font-text-xs);color:var(--sinch-color-text-default);text-align:center;border-radius:var(--sinch-shape-radius-m);width:24px;height:24px;line-height:22px;cursor:pointer;border:1px solid transparent;background-color:transparent;box-sizing:border-box;user-select:none}.day.today{border:1px solid var(--sinch-color-
|
|
11
|
-
import { areDatesEqual, assertDate, assertLocale, assertMinMax, assertValue, canGoNextMonth, canGoNextYear, canGoPrevMonth, canGoPrevYear, clampMaxDate, clampMinDate, dateToIso, decMonth, decYear, getCalendarMonth, getDayNames, getMonthNames, incMonth, incYear, isDateBetween, isoToDate, isValidDate, today } from './utils';
|
|
9
|
+
import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, getRect, isAttrTrue, NectaryElement, packCsv, setClass, unpackCsv, updateAttribute, updateBooleanAttribute } from '../utils';
|
|
10
|
+
const templateHTML = '<style>:host{display:block;outline:0}#content{width:fit-content;box-sizing:border-box;padding:16px;display:flex;flex-direction:column;gap:8px}#month{display:flex;flex-direction:column;row-gap:8px}.week{display:flex;flex-direction:row;column-gap:8px}.week.empty{display:none}.day{all:initial;font:var(--sinch-font-text-xs);color:var(--sinch-color-text-default);text-align:center;border-radius:var(--sinch-shape-radius-m);width:24px;height:24px;line-height:22px;cursor:pointer;border:1px solid transparent;background-color:transparent;box-sizing:border-box;user-select:none}.day.today{border:1px solid var(--sinch-color-tropical-500)}.day:disabled{cursor:initial;color:var(--sinch-color-snow-700)}.day:focus-visible{outline:1px solid var(--sinch-color-border-focus);outline-offset:1px}@supports not selector(:focus-visible){.day:focus{outline:1px solid var(--sinch-color-border-focus);outline-offset:1px}}.day.range{background-color:var(--sinch-color-tropical-100)}.day.selected{background-color:var(--sinch-color-tropical-500);color:var(--sinch-color-snow-100)}.day:hover:enabled:not(.selected){background-color:var(--sinch-color-tropical-200)}#week-day-names{display:flex;flex-direction:row;gap:8px;height:24px}.week-day-name{font:var(--sinch-font-text-xs);font-weight:var(--sinch-font-weight-emphasized);color:var(--sinch-color-text-default);text-align:center;width:24px;height:24px;line-height:24px;user-select:none;text-transform:uppercase}#content-header{display:flex;flex-direction:row;height:32px;align-items:center}#date{flex:1;text-align:center;text-transform:capitalize}#prev-year{margin-left:-4px}#next-year{margin-right:-4px}</style><div id="content"><div id="content-header"><sinch-icon-button id="prev-year" small><sinch-icon-keyboard-double-arrow-left slot="icon"></sinch-icon-keyboard-double-arrow-left></sinch-icon-button><sinch-icon-button id="prev-month" small><sinch-icon-keyboard-arrow-left slot="icon"></sinch-icon-keyboard-arrow-left></sinch-icon-button><sinch-text id="date" type="m" emphasized aria-live="polite"></sinch-text><sinch-icon-button id="next-month" small><sinch-icon-keyboard-arrow-right slot="icon"></sinch-icon-keyboard-arrow-right></sinch-icon-button><sinch-icon-button id="next-year" small><sinch-icon-keyboard-double-arrow-right slot="icon"></sinch-icon-keyboard-double-arrow-right></sinch-icon-button></div><div id="week-day-names"><div class="week-day-name"></div><div class="week-day-name"></div><div class="week-day-name"></div><div class="week-day-name"></div><div class="week-day-name"></div><div class="week-day-name"></div><div class="week-day-name"></div></div><div id="month"><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div><div class="week"><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button><button class="day"></button></div></div></div>';
|
|
11
|
+
import { areDatesEqual, assertDate, assertLocale, assertMinMax, assertValue, canGoNextMonth, canGoNextYear, canGoPrevMonth, canGoPrevYear, clampMaxDate, clampMinDate, cloneDate, dateToIso, decMonth, decYear, getCalendarMonth, getDayNames, getMonthNames, incMonth, incYear, isDateBetween, isoToDate, isValidDate, sortDates, today } from './utils';
|
|
12
12
|
const template = document.createElement('template');
|
|
13
13
|
template.innerHTML = templateHTML;
|
|
14
14
|
defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
@@ -16,7 +16,9 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
16
16
|
#$weeks;
|
|
17
17
|
#$days;
|
|
18
18
|
#$weekDayNames;
|
|
19
|
-
#
|
|
19
|
+
#uiDate = null;
|
|
20
|
+
#date1 = null;
|
|
21
|
+
#date2 = null;
|
|
20
22
|
#minDate = null;
|
|
21
23
|
#maxDate = null;
|
|
22
24
|
#$prevMonth;
|
|
@@ -25,6 +27,8 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
25
27
|
#$nextYear;
|
|
26
28
|
#$date;
|
|
27
29
|
#monthNames;
|
|
30
|
+
#controller = null;
|
|
31
|
+
#isHoverSubscribed = false;
|
|
28
32
|
constructor() {
|
|
29
33
|
super();
|
|
30
34
|
const shadowRoot = this.attachShadow();
|
|
@@ -48,25 +52,23 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
48
52
|
this.#$weekDayNames = Array.from(shadowRoot.querySelectorAll('#week-day-names > .week-day-name'));
|
|
49
53
|
}
|
|
50
54
|
connectedCallback() {
|
|
51
|
-
this
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.#$
|
|
56
|
-
this.addEventListener('
|
|
57
|
-
|
|
55
|
+
this.#controller = new AbortController();
|
|
56
|
+
const options = {
|
|
57
|
+
signal: this.#controller.signal
|
|
58
|
+
};
|
|
59
|
+
this.#$prevMonth.addEventListener('click', this.#onPrevMonthClick, options);
|
|
60
|
+
this.#$nextMonth.addEventListener('click', this.#onNextMonthClick, options);
|
|
61
|
+
this.#$prevYear.addEventListener('click', this.#onPrevYearClick, options);
|
|
62
|
+
this.#$nextYear.addEventListener('click', this.#onNextYearClick, options);
|
|
63
|
+
this.#$month.addEventListener('click', this.#onDateClick, options);
|
|
64
|
+
this.addEventListener('-change', this.#onChangeReactHandler, options);
|
|
58
65
|
}
|
|
59
|
-
|
|
60
66
|
disconnectedCallback() {
|
|
61
|
-
this
|
|
62
|
-
this
|
|
63
|
-
this.#$prevYear.removeEventListener('click', this.#onPrevYearClick);
|
|
64
|
-
this.#$nextYear.removeEventListener('click', this.#onNextYearClick);
|
|
65
|
-
this.#$month.removeEventListener('click', this.#onDateClick);
|
|
66
|
-
this.removeEventListener('-change', this.#onChangeReactHandler);
|
|
67
|
+
this.#controller.abort();
|
|
68
|
+
this.#unsubscribeRangeHover();
|
|
67
69
|
}
|
|
68
70
|
static get observedAttributes() {
|
|
69
|
-
return ['value', 'min', 'max', 'locale', 'prev-year-aria-label', 'next-year-aria-label', 'prev-month-aria-label', 'next-month-aria-label'];
|
|
71
|
+
return ['value', 'min', 'max', 'locale', 'range', 'prev-year-aria-label', 'next-year-aria-label', 'prev-month-aria-label', 'next-month-aria-label'];
|
|
70
72
|
}
|
|
71
73
|
attributeChangedCallback(name, prevValue, newVal) {
|
|
72
74
|
if (newVal === prevValue) {
|
|
@@ -76,19 +78,7 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
76
78
|
case 'value':
|
|
77
79
|
{
|
|
78
80
|
assertValue(newVal);
|
|
79
|
-
this.#
|
|
80
|
-
if (!isValidDate(this.#date)) {
|
|
81
|
-
this.#date = today();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (this.#minDate !== null) {
|
|
85
|
-
clampMinDate(this.#date, this.#minDate);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (this.#maxDate !== null) {
|
|
89
|
-
clampMaxDate(this.#date, this.#maxDate);
|
|
90
|
-
}
|
|
91
|
-
this.#render();
|
|
81
|
+
this.#onValueChange();
|
|
92
82
|
break;
|
|
93
83
|
}
|
|
94
84
|
case 'min':
|
|
@@ -97,8 +87,8 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
97
87
|
this.#minDate = isoToDate(newVal);
|
|
98
88
|
assertDate(this.#minDate, name, newVal);
|
|
99
89
|
|
|
100
|
-
if (this.#
|
|
101
|
-
clampMinDate(this.#
|
|
90
|
+
if (this.#uiDate !== null) {
|
|
91
|
+
clampMinDate(this.#uiDate, this.#minDate);
|
|
102
92
|
}
|
|
103
93
|
this.#render();
|
|
104
94
|
break;
|
|
@@ -109,8 +99,8 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
109
99
|
this.#maxDate = isoToDate(newVal);
|
|
110
100
|
assertDate(this.#maxDate, name, newVal);
|
|
111
101
|
|
|
112
|
-
if (this.#
|
|
113
|
-
clampMaxDate(this.#
|
|
102
|
+
if (this.#uiDate !== null) {
|
|
103
|
+
clampMaxDate(this.#uiDate, this.#maxDate);
|
|
114
104
|
}
|
|
115
105
|
this.#render();
|
|
116
106
|
break;
|
|
@@ -126,6 +116,16 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
126
116
|
this.#render();
|
|
127
117
|
break;
|
|
128
118
|
}
|
|
119
|
+
case 'range':
|
|
120
|
+
{
|
|
121
|
+
const isRange = isAttrTrue(newVal);
|
|
122
|
+
if (isRange) {
|
|
123
|
+
this.#onValueChange();
|
|
124
|
+
} else {
|
|
125
|
+
this.#unsubscribeRangeHover();
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
129
|
case 'prev-year-aria-label':
|
|
130
130
|
{
|
|
131
131
|
updateAttribute(this.#$prevYear, 'aria-label', newVal);
|
|
@@ -155,7 +155,7 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
155
155
|
updateAttribute(this, 'locale', value);
|
|
156
156
|
}
|
|
157
157
|
get locale() {
|
|
158
|
-
return getAttribute(this, 'locale');
|
|
158
|
+
return getAttribute(this, 'locale', '');
|
|
159
159
|
}
|
|
160
160
|
set value(value) {
|
|
161
161
|
updateAttribute(this, 'value', value);
|
|
@@ -175,6 +175,12 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
175
175
|
get max() {
|
|
176
176
|
return getAttribute(this, 'max', '');
|
|
177
177
|
}
|
|
178
|
+
set range(isRanged) {
|
|
179
|
+
updateBooleanAttribute(this, 'range', isRanged);
|
|
180
|
+
}
|
|
181
|
+
get range() {
|
|
182
|
+
return getBooleanAttribute(this, 'range');
|
|
183
|
+
}
|
|
178
184
|
set prevMonthAriaLabel(value) {
|
|
179
185
|
updateAttribute(this, 'prev-month-aria-label', value);
|
|
180
186
|
}
|
|
@@ -217,50 +223,148 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
217
223
|
}
|
|
218
224
|
#onPrevMonthClick = e => {
|
|
219
225
|
e.stopPropagation();
|
|
220
|
-
decMonth(this.#
|
|
226
|
+
decMonth(this.#uiDate, this.#minDate);
|
|
221
227
|
this.#render();
|
|
222
228
|
};
|
|
223
229
|
#onNextMonthClick = e => {
|
|
224
230
|
e.stopPropagation();
|
|
225
|
-
incMonth(this.#
|
|
231
|
+
incMonth(this.#uiDate, this.#maxDate);
|
|
226
232
|
this.#render();
|
|
227
233
|
};
|
|
228
234
|
#onPrevYearClick = e => {
|
|
229
235
|
e.stopPropagation();
|
|
230
|
-
decYear(this.#
|
|
236
|
+
decYear(this.#uiDate, this.#minDate);
|
|
231
237
|
this.#render();
|
|
232
238
|
};
|
|
233
239
|
#onNextYearClick = e => {
|
|
234
240
|
e.stopPropagation();
|
|
235
|
-
incYear(this.#
|
|
241
|
+
incYear(this.#uiDate, this.#maxDate);
|
|
236
242
|
this.#render();
|
|
237
243
|
};
|
|
244
|
+
#onDateMouseEnter = e => {
|
|
245
|
+
if (this.#date1 !== null && this.#date2 === null) {
|
|
246
|
+
const hoverDateIso = e.target.getAttribute('data-date');
|
|
247
|
+
if (hoverDateIso === null) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const hoverDate = isoToDate(hoverDateIso);
|
|
251
|
+
const todayDate = today();
|
|
252
|
+
for (const week of this.#$days) {
|
|
253
|
+
for (const $day of week) {
|
|
254
|
+
if ($day.hasAttribute('disabled')) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const dayDate = isoToDate($day.getAttribute('data-date'));
|
|
258
|
+
setClass($day, 'range', !areDatesEqual(todayDate, dayDate) && (isDateBetween(dayDate, this.#date1, hoverDate) || isDateBetween(dayDate, hoverDate, this.#date1)));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
238
263
|
#onDateClick = e => {
|
|
239
264
|
e.stopPropagation();
|
|
240
265
|
const dateIso = e.target.getAttribute('data-date');
|
|
241
266
|
if (dateIso === null || dateIso.length === 0) {
|
|
242
267
|
return;
|
|
243
268
|
}
|
|
244
|
-
this
|
|
269
|
+
if (this.range) {
|
|
270
|
+
if (this.#date1 !== null && this.#date2 === null) {
|
|
271
|
+
const date2 = isoToDate(dateIso);
|
|
272
|
+
if (areDatesEqual(this.#date1, date2)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const dateTuple = sortDates([this.#date1, date2]);
|
|
276
|
+
const value = packCsv(dateTuple.map(dateToIso));
|
|
277
|
+
this.#date1 = dateTuple[0];
|
|
278
|
+
this.#date2 = dateTuple[1];
|
|
279
|
+
this.#unsubscribeRangeHover();
|
|
280
|
+
this.#render();
|
|
281
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
282
|
+
detail: value,
|
|
283
|
+
bubbles: true
|
|
284
|
+
}));
|
|
285
|
+
this.dispatchEvent(new CustomEvent('-change', {
|
|
286
|
+
detail: value
|
|
287
|
+
}));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.#date1 = isoToDate(dateIso);
|
|
291
|
+
this.#date2 = null;
|
|
292
|
+
this.#subscribeRangeHover();
|
|
293
|
+
this.#render();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
298
|
+
detail: dateIso,
|
|
299
|
+
bubbles: true
|
|
300
|
+
}));
|
|
301
|
+
this.dispatchEvent(new CustomEvent('-change', {
|
|
302
|
+
detail: dateIso
|
|
303
|
+
}));
|
|
245
304
|
};
|
|
305
|
+
#onValueChange() {
|
|
306
|
+
const value = this.value;
|
|
307
|
+
this.#date1 = null;
|
|
308
|
+
this.#date2 = null;
|
|
309
|
+
if (this.range) {
|
|
310
|
+
const isoDates = unpackCsv(value);
|
|
311
|
+
if (isoDates.length === 2) {
|
|
312
|
+
const date1 = isoToDate(isoDates[0]);
|
|
313
|
+
const date2 = isoToDate(isoDates[1]);
|
|
314
|
+
if (isValidDate(date1) && isValidDate(date2)) {
|
|
315
|
+
this.#date1 = date1;
|
|
316
|
+
this.#date2 = date2;
|
|
317
|
+
|
|
318
|
+
if (this.#uiDate === null) {
|
|
319
|
+
this.#uiDate = cloneDate(this.#date2);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} else if (isoDates.length === 1) {
|
|
323
|
+
const date1 = isoToDate(isoDates[0]);
|
|
324
|
+
if (isValidDate(date1)) {
|
|
325
|
+
this.#uiDate = date1;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
const valueDate = isoToDate(value);
|
|
330
|
+
if (isValidDate(valueDate)) {
|
|
331
|
+
this.#date1 = valueDate;
|
|
332
|
+
this.#uiDate = cloneDate(this.#date1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (this.#uiDate === null) {
|
|
336
|
+
this.#uiDate = today();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (this.#minDate !== null) {
|
|
340
|
+
clampMinDate(this.#uiDate, this.#minDate);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this.#maxDate !== null) {
|
|
344
|
+
clampMaxDate(this.#uiDate, this.#maxDate);
|
|
345
|
+
}
|
|
346
|
+
this.#render();
|
|
347
|
+
}
|
|
246
348
|
#render() {
|
|
247
|
-
if (this.#
|
|
349
|
+
if (this.#uiDate === null || this.#minDate === null || this.#maxDate === null || this.locale === null) {
|
|
248
350
|
return;
|
|
249
351
|
}
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
updateBooleanAttribute(this.#$
|
|
254
|
-
updateBooleanAttribute(this.#$
|
|
255
|
-
updateBooleanAttribute(this.#$
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
352
|
+
const todayDate = today();
|
|
353
|
+
const month = getCalendarMonth(this.#uiDate);
|
|
354
|
+
updateBooleanAttribute(this.#$prevMonth, 'disabled', canGoPrevMonth(this.#uiDate, this.#minDate) === false);
|
|
355
|
+
updateBooleanAttribute(this.#$nextMonth, 'disabled', canGoNextMonth(this.#uiDate, this.#maxDate) === false);
|
|
356
|
+
updateBooleanAttribute(this.#$prevYear, 'disabled', canGoPrevYear(this.#uiDate, this.#minDate) === false);
|
|
357
|
+
updateBooleanAttribute(this.#$nextYear, 'disabled', canGoNextYear(this.#uiDate, this.#maxDate) === false);
|
|
358
|
+
this.#$date.textContent = `${this.#monthNames[this.#uiDate.getMonth()]} ${this.#uiDate.getFullYear()}`;
|
|
359
|
+
for (let wi = 0; wi < this.#$days.length; wi++) {
|
|
360
|
+
const $week = this.#$days[wi];
|
|
259
361
|
let isEmptyWeek = true;
|
|
260
|
-
$week.
|
|
362
|
+
for (let di = 0; di < $week.length; di++) {
|
|
363
|
+
const $day = $week[di];
|
|
261
364
|
const week = month[wi];
|
|
262
365
|
const day = week?.[di];
|
|
263
366
|
$day.classList.remove('selected');
|
|
367
|
+
$day.classList.remove('range');
|
|
264
368
|
$day.classList.remove('today');
|
|
265
369
|
if (day == null) {
|
|
266
370
|
$day.textContent = '';
|
|
@@ -278,25 +382,30 @@ defineCustomElement('sinch-date-picker', class extends NectaryElement {
|
|
|
278
382
|
$day.setAttribute('disabled', '');
|
|
279
383
|
$day.setAttribute('aria-hidden', 'true');
|
|
280
384
|
}
|
|
281
|
-
if (areDatesEqual(day,
|
|
385
|
+
if (areDatesEqual(day, this.#date1) || areDatesEqual(day, this.#date2)) {
|
|
282
386
|
$day.classList.add('selected');
|
|
283
387
|
} else if (areDatesEqual(day, todayDate)) {
|
|
284
388
|
$day.classList.add('today');
|
|
389
|
+
} else if (isDateBetween(day, this.#date1, this.#date2)) {
|
|
390
|
+
$day.classList.add('range');
|
|
285
391
|
}
|
|
286
392
|
isEmptyWeek = false;
|
|
287
393
|
}
|
|
288
|
-
}
|
|
394
|
+
}
|
|
289
395
|
setClass(this.#$weeks[wi], 'empty', isEmptyWeek);
|
|
290
|
-
}
|
|
396
|
+
}
|
|
291
397
|
}
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
398
|
+
#subscribeRangeHover() {
|
|
399
|
+
if (!this.#isHoverSubscribed) {
|
|
400
|
+
this.#$month.addEventListener('mouseover', this.#onDateMouseEnter);
|
|
401
|
+
this.#isHoverSubscribed = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
#unsubscribeRangeHover() {
|
|
405
|
+
if (this.#isHoverSubscribed) {
|
|
406
|
+
this.#$month.removeEventListener('mouseover', this.#onDateMouseEnter);
|
|
407
|
+
this.#isHoverSubscribed = false;
|
|
408
|
+
}
|
|
300
409
|
}
|
|
301
410
|
#onChangeReactHandler = e => {
|
|
302
411
|
getReactEventHandler(this, 'on-change')?.(e);
|
package/date-picker/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare type TSinchDatePickerElement = HTMLElement & {
|
|
|
9
9
|
max: string;
|
|
10
10
|
/** BCP 47 language tag (e.g. en-US), which changes day and month display names in the calendar */
|
|
11
11
|
locale: string;
|
|
12
|
+
/** Date range mode */
|
|
13
|
+
range: boolean;
|
|
12
14
|
/** Label that is used for a11y */
|
|
13
15
|
prevYearAriaLabel: string;
|
|
14
16
|
/** Label that is used for a11y */
|
|
@@ -32,6 +34,8 @@ export declare type TSinchDatePickerElement = HTMLElement & {
|
|
|
32
34
|
setAttribute(name: 'max', value: string): void;
|
|
33
35
|
/** BCP 47 language tag (e.g. en-US), which changes day and month display names in the calendar */
|
|
34
36
|
setAttribute(name: 'locale', value: string): void;
|
|
37
|
+
/** Date range mode */
|
|
38
|
+
setAttribute(name: 'range', value: ''): void;
|
|
35
39
|
/** Label that is used for a11y */
|
|
36
40
|
setAttribute(name: 'prev-year-aria-label', value: string): void;
|
|
37
41
|
/** Label that is used for a11y */
|
|
@@ -50,6 +54,8 @@ export declare type TSinchDatePickerReact = TSinchElementReact<TSinchDatePickerE
|
|
|
50
54
|
max: string;
|
|
51
55
|
/** BCP 47 language tag (e.g. en-US), which changes day and month display names in the calendar */
|
|
52
56
|
locale: string;
|
|
57
|
+
/** Date range mode */
|
|
58
|
+
range?: boolean;
|
|
53
59
|
/** Label that is used for a11y */
|
|
54
60
|
'aria-label': string;
|
|
55
61
|
/** Label that is used for a11y */
|
package/date-picker/utils.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ declare type TCalendarOptions = {
|
|
|
3
3
|
};
|
|
4
4
|
declare type TMaybeDate = Date | null;
|
|
5
5
|
export declare const getCalendarMonth: (date: Date, options?: TCalendarOptions) => TMaybeDate[][];
|
|
6
|
-
export declare const today: () => Date;
|
|
7
6
|
export declare const dateToIso: (date: Date) => string;
|
|
8
7
|
export declare const isoToDate: (value: string) => Date;
|
|
8
|
+
export declare const today: () => Date;
|
|
9
9
|
export declare const getDayNames: (locale: string) => string[];
|
|
10
10
|
export declare const getMonthNames: (locale: string) => string[];
|
|
11
11
|
declare type TAssertMinMax = (value: string | null, attrName: string) => asserts value is string;
|
|
@@ -17,6 +17,7 @@ export declare const assertLocale: TAssertLocale;
|
|
|
17
17
|
declare type TAssertDate = (value: any, attrName: string, attrValue: string) => asserts value is Date;
|
|
18
18
|
export declare const isValidDate: (value: any) => value is Date;
|
|
19
19
|
export declare const assertDate: TAssertDate;
|
|
20
|
+
export declare const compareDates: (a: Date, b: Date) => number;
|
|
20
21
|
export declare const clampMinDate: (date: Date, min: Date) => void;
|
|
21
22
|
export declare const clampMaxDate: (date: Date, max: Date) => void;
|
|
22
23
|
export declare const incMonth: (date: Date, max: Date) => void;
|
|
@@ -27,6 +28,8 @@ export declare const canGoPrevMonth: (date: Date, min: Date) => boolean;
|
|
|
27
28
|
export declare const canGoNextMonth: (date: Date, max: Date) => boolean;
|
|
28
29
|
export declare const canGoNextYear: (date: Date, max: Date) => boolean;
|
|
29
30
|
export declare const canGoPrevYear: (date: Date, min: Date) => boolean;
|
|
30
|
-
export declare const isDateBetween: (date: Date, min: Date, max: Date) => boolean;
|
|
31
|
-
export declare const areDatesEqual: (a: Date, b: Date) => boolean;
|
|
31
|
+
export declare const isDateBetween: (date: Date, min: Date | null, max: Date | null) => boolean;
|
|
32
|
+
export declare const areDatesEqual: (a: Date, b: Date | null) => boolean;
|
|
33
|
+
export declare const cloneDate: (date: Date) => Date;
|
|
34
|
+
export declare const sortDates: (dateTuple: [Date, Date]) => [Date, Date];
|
|
32
35
|
export {};
|
package/date-picker/utils.js
CHANGED
|
@@ -33,15 +33,15 @@ export const getCalendarMonth = (date, options) => {
|
|
|
33
33
|
}
|
|
34
34
|
return month;
|
|
35
35
|
};
|
|
36
|
-
export const today = () => {
|
|
37
|
-
return new Date();
|
|
38
|
-
};
|
|
39
36
|
export const dateToIso = date => {
|
|
40
37
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
41
38
|
};
|
|
42
39
|
export const isoToDate = value => {
|
|
43
40
|
return new Date(`${value.substring(0, 10)}T00:00:00`);
|
|
44
41
|
};
|
|
42
|
+
export const today = () => {
|
|
43
|
+
return isoToDate(dateToIso(new Date()));
|
|
44
|
+
};
|
|
45
45
|
export const getDayNames = locale => {
|
|
46
46
|
const formatter = new Intl.DateTimeFormat(locale, {
|
|
47
47
|
weekday: 'narrow',
|
|
@@ -85,7 +85,7 @@ export const assertDate = (value, attrName, attrValue) => {
|
|
|
85
85
|
throw new Error(`sinch-date-picker: invalid "${attrName}" attribute: ${attrValue}`);
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
|
-
const compareDates = (a, b) => {
|
|
88
|
+
export const compareDates = (a, b) => {
|
|
89
89
|
return a.getTime() - b.getTime();
|
|
90
90
|
};
|
|
91
91
|
export const clampMinDate = (date, min) => {
|
|
@@ -131,8 +131,23 @@ export const canGoPrevYear = (date, min) => {
|
|
|
131
131
|
return compareDates(prevYear, min) >= 0;
|
|
132
132
|
};
|
|
133
133
|
export const isDateBetween = (date, min, max) => {
|
|
134
|
+
if (min === null || max === null) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
134
137
|
return compareDates(date, min) >= 0 && compareDates(max, date) >= 0;
|
|
135
138
|
};
|
|
136
139
|
export const areDatesEqual = (a, b) => {
|
|
140
|
+
if (b === null) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
137
143
|
return compareDates(a, b) === 0;
|
|
144
|
+
};
|
|
145
|
+
export const cloneDate = date => {
|
|
146
|
+
return new Date(date.getTime());
|
|
147
|
+
};
|
|
148
|
+
export const sortDates = dateTuple => {
|
|
149
|
+
if (compareDates(dateTuple[0], dateTuple[1]) > 0) {
|
|
150
|
+
return [dateTuple[1], dateTuple[0]];
|
|
151
|
+
}
|
|
152
|
+
return dateTuple;
|
|
138
153
|
};
|
package/file-drop/types.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare type TSinchFileDropReact = TSinchElementReact<TSinchFileDropEleme
|
|
|
44
44
|
/** Placeholder */
|
|
45
45
|
placeholder: string;
|
|
46
46
|
/** Change value handler */
|
|
47
|
-
'on-change'
|
|
47
|
+
'on-change': (e: CustomEvent<File[]>) => void;
|
|
48
48
|
/** Invalid handler */
|
|
49
|
-
'on-invalid'
|
|
49
|
+
'on-invalid': (e: CustomEvent<TSinchFileDropInvalidType>) => void;
|
|
50
50
|
};
|
package/file-picker/types.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export declare type TSinchFilePickerReact = TSinchElementReact<TSinchFilePickerE
|
|
|
28
28
|
/** Change value handler */
|
|
29
29
|
'on-change': (e: CustomEvent<File[]>) => void;
|
|
30
30
|
/** Invalid handler */
|
|
31
|
-
'on-invalid'
|
|
31
|
+
'on-invalid': (e: CustomEvent<TSinchFilePickerInvalidType>) => void;
|
|
32
32
|
};
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineCustomElement, getAttribute, getBooleanAttribute,
|
|
1
|
+
import { defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getReactEventHandler, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv } from '../utils';
|
|
2
2
|
const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:row}</style><div id="wrapper"><slot></slot></div>';
|
|
3
3
|
const template = document.createElement('template');
|
|
4
4
|
template.innerHTML = templateHTML;
|
|
@@ -69,9 +69,9 @@ defineCustomElement('sinch-segmented-icon-control', class extends NectaryElement
|
|
|
69
69
|
};
|
|
70
70
|
#onValueChange(csv) {
|
|
71
71
|
if (this.multiple) {
|
|
72
|
-
const values =
|
|
72
|
+
const values = unpackCsv(csv);
|
|
73
73
|
for (const $option of this.#$slot.assignedElements()) {
|
|
74
|
-
const isChecked = !getBooleanAttribute($option, 'disabled') && values.
|
|
74
|
+
const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
|
|
75
75
|
updateBooleanAttribute($option, 'data-checked', isChecked);
|
|
76
76
|
}
|
|
77
77
|
} else {
|
package/select-menu/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute,
|
|
1
|
+
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
2
2
|
const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}</style><div id="listbox" role="presentation"><slot></slot></div>';
|
|
3
3
|
const ITEM_HEIGHT = 40;
|
|
4
4
|
const template = document.createElement('template');
|
|
@@ -159,9 +159,9 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
|
|
|
159
159
|
};
|
|
160
160
|
#onValueChange(csv) {
|
|
161
161
|
if (this.multiple) {
|
|
162
|
-
const values =
|
|
162
|
+
const values = unpackCsv(csv);
|
|
163
163
|
for (const $option of this.#getOptionElements()) {
|
|
164
|
-
const isChecked = !getBooleanAttribute($option, 'disabled') && values.
|
|
164
|
+
const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
|
|
165
165
|
updateBooleanAttribute($option, 'data-checked', isChecked);
|
|
166
166
|
}
|
|
167
167
|
} else {
|
package/stop-events/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { defineCustomElement,
|
|
1
|
+
import { defineCustomElement, unpackCsv } from '../utils';
|
|
2
2
|
defineCustomElement('sinch-stop-events', class extends HTMLElement {
|
|
3
3
|
constructor() {
|
|
4
4
|
super();
|
|
5
5
|
this.style.display = 'contents';
|
|
6
6
|
}
|
|
7
7
|
connectedCallback() {
|
|
8
|
-
const events =
|
|
8
|
+
const events = unpackCsv(this.getAttribute('events'));
|
|
9
9
|
for (const event of events) {
|
|
10
10
|
this.addEventListener(event, this.#stopEvent);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
disconnectedCallback() {
|
|
14
|
-
const events =
|
|
14
|
+
const events = unpackCsv(this.getAttribute('events'));
|
|
15
15
|
for (const event of events) {
|
|
16
16
|
this.removeEventListener(event, this.#stopEvent);
|
|
17
17
|
}
|
package/tile-control/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineCustomElement, getAttribute, getBooleanAttribute,
|
|
1
|
+
import { defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateIntegerAttribute } from '../utils';
|
|
2
2
|
const templateHTML = '<style>:host{display:block;outline:0;--sinch-grid-num-columns:1}#wrapper{display:grid;grid-template-columns:repeat(var(--sinch-grid-num-columns),auto);gap:16px;width:fit-content}:host([small]:not([small=false])) #wrapper{gap:8px}:host([cols="2"]){--sinch-grid-num-columns:2}:host([cols="3"]){--sinch-grid-num-columns:3}:host([cols="4"]){--sinch-grid-num-columns:4}:host([cols="5"]){--sinch-grid-num-columns:5}:host([cols="6"]){--sinch-grid-num-columns:6}:host([cols="7"]){--sinch-grid-num-columns:7}:host([cols="8"]){--sinch-grid-num-columns:8}</style><div id="wrapper"><slot></slot></div>';
|
|
3
3
|
const template = document.createElement('template');
|
|
4
4
|
template.innerHTML = templateHTML;
|
|
@@ -91,9 +91,9 @@ defineCustomElement('sinch-tile-control', class extends NectaryElement {
|
|
|
91
91
|
};
|
|
92
92
|
#onValueChange(csv) {
|
|
93
93
|
if (this.multiple) {
|
|
94
|
-
const values =
|
|
94
|
+
const values = unpackCsv(csv);
|
|
95
95
|
for (const $option of this.#$slot.assignedElements()) {
|
|
96
|
-
const isChecked = !getBooleanAttribute($option, 'disabled') && values.
|
|
96
|
+
const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
|
|
97
97
|
updateBooleanAttribute($option, 'data-checked', isChecked);
|
|
98
98
|
}
|
|
99
99
|
} else {
|
package/utils/csv.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
1
|
+
export declare const CSV_DELIMITER = ",";
|
|
2
|
+
export declare const packCsv: (values: string[]) => string;
|
|
3
|
+
export declare const unpackCsv: (csv: string) => string[];
|
|
4
|
+
export declare const updateCsv: (csv: string, value: string, setActive: boolean) => string;
|
|
3
5
|
export declare const getFirstCsvValue: (acc: string) => string | null;
|
package/utils/csv.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
export const CSV_DELIMITER = ',';
|
|
2
|
+
export const packCsv = values => {
|
|
3
|
+
return values.join(CSV_DELIMITER);
|
|
3
4
|
};
|
|
4
|
-
const
|
|
5
|
-
return
|
|
5
|
+
export const unpackCsv = csv => {
|
|
6
|
+
return csv.length === 0 ? [] : csv.split(CSV_DELIMITER);
|
|
6
7
|
};
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export const updateCsv = (acc, value, setActive) => {
|
|
11
|
-
const values = getCsvSet(acc);
|
|
8
|
+
export const updateCsv = (csv, value, setActive) => {
|
|
9
|
+
const values = unpackCsv(csv);
|
|
10
|
+
const index = values.indexOf(value);
|
|
12
11
|
if (setActive) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
if (index < 0) {
|
|
13
|
+
values.push(value);
|
|
14
|
+
}
|
|
15
|
+
} else if (index >= 0) {
|
|
16
|
+
values.splice(index, 1);
|
|
16
17
|
}
|
|
17
18
|
return packCsv(values);
|
|
18
19
|
};
|
|
19
20
|
export const getFirstCsvValue = acc => {
|
|
20
|
-
return acc ===
|
|
21
|
+
return acc.length === 0 ? null : unpackCsv(acc)[0];
|
|
21
22
|
};
|