@thednp/color-picker 0.0.2-alpha4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- import { addListener, removeListener } from 'event-listener.js';
1
+ import { addListener, removeListener } from '@thednp/event-listener/src/event-listener';
2
2
 
3
3
  import ariaDescription from 'shorter-js/src/strings/ariaDescription';
4
4
  import ariaSelected from 'shorter-js/src/strings/ariaSelected';
@@ -16,12 +16,12 @@ import focusinEvent from 'shorter-js/src/strings/focusinEvent';
16
16
  import mouseclickEvent from 'shorter-js/src/strings/mouseclickEvent';
17
17
  import keydownEvent from 'shorter-js/src/strings/keydownEvent';
18
18
  import changeEvent from 'shorter-js/src/strings/changeEvent';
19
- import touchstartEvent from 'shorter-js/src/strings/touchstartEvent';
19
+
20
20
  import touchmoveEvent from 'shorter-js/src/strings/touchmoveEvent';
21
- import touchendEvent from 'shorter-js/src/strings/touchendEvent';
22
- import mousedownEvent from 'shorter-js/src/strings/mousedownEvent';
23
- import mousemoveEvent from 'shorter-js/src/strings/mousemoveEvent';
24
- import mouseupEvent from 'shorter-js/src/strings/mouseupEvent';
21
+ import pointerdownEvent from 'shorter-js/src/strings/pointerdownEvent';
22
+ import pointermoveEvent from 'shorter-js/src/strings/pointermoveEvent';
23
+ import pointerupEvent from 'shorter-js/src/strings/pointerupEvent';
24
+
25
25
  import scrollEvent from 'shorter-js/src/strings/scrollEvent';
26
26
  import keyupEvent from 'shorter-js/src/strings/keyupEvent';
27
27
  import resizeEvent from 'shorter-js/src/strings/resizeEvent';
@@ -29,7 +29,6 @@ import focusoutEvent from 'shorter-js/src/strings/focusoutEvent';
29
29
 
30
30
  import getDocument from 'shorter-js/src/get/getDocument';
31
31
  import getDocumentElement from 'shorter-js/src/get/getDocumentElement';
32
- import getWindow from 'shorter-js/src/get/getWindow';
33
32
  import getElementStyle from 'shorter-js/src/get/getElementStyle';
34
33
  import getUID from 'shorter-js/src/get/getUID';
35
34
  import getBoundingClientRect from 'shorter-js/src/get/getBoundingClientRect';
@@ -80,7 +79,7 @@ const colorPickerDefaults = {
80
79
  // ColorPicker Static Methods
81
80
  // ==========================
82
81
 
83
- /** @type {CP.GetInstance<ColorPicker>} */
82
+ /** @type {CP.GetInstance<ColorPicker, HTMLInputElement>} */
84
83
  const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
85
84
 
86
85
  /** @type {CP.InitCallback<ColorPicker>} */
@@ -115,12 +114,10 @@ function toggleEventsOnShown(self, action) {
115
114
  const fn = action ? addListener : removeListener;
116
115
  const { input, colorMenu, parent } = self;
117
116
  const doc = getDocument(input);
118
- const win = getWindow(input);
119
- const pointerEvents = `on${touchstartEvent}` in doc
120
- ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
121
- : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
117
+ // const win = getWindow(input);
118
+ const win = doc.defaultView;
122
119
 
123
- fn(self.controls, pointerEvents.down, self.pointerDown);
120
+ fn(self.controls, pointerdownEvent, self.pointerDown);
124
121
  self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
125
122
 
126
123
  // @ts-ignore -- this is `Window`
@@ -135,8 +132,8 @@ function toggleEventsOnShown(self, action) {
135
132
  fn(colorMenu, keydownEvent, self.menuKeyHandler);
136
133
  }
137
134
 
138
- fn(doc, pointerEvents.move, self.pointerMove);
139
- fn(doc, pointerEvents.up, self.pointerUp);
135
+ fn(doc, pointermoveEvent, self.pointerMove);
136
+ fn(doc, pointerupEvent, self.pointerUp);
140
137
  fn(parent, focusoutEvent, self.handleFocusOut);
141
138
  fn(doc, keyupEvent, self.handleDismiss);
142
139
  }
@@ -155,6 +152,7 @@ function firePickerChange(self) {
155
152
  * @returns {void}
156
153
  */
157
154
  function removePosition(element) {
155
+ /* istanbul ignore else */
158
156
  if (element) {
159
157
  ['bottom', 'top'].forEach((x) => removeClass(element, x));
160
158
  }
@@ -257,6 +255,7 @@ export default class ColorPicker {
257
255
  } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
258
256
 
259
257
  let translatedColorLabels = colorNames;
258
+ /* istanbul ignore else */
260
259
  if (colorLabels instanceof Array && colorLabels.length === 17) {
261
260
  translatedColorLabels = colorLabels;
262
261
  } else if (colorLabels && colorLabels.split(',').length === 17) {
@@ -273,7 +272,7 @@ export default class ColorPicker {
273
272
  ? JSON.parse(componentLabels) : componentLabels;
274
273
 
275
274
  /** @type {Record<string, string>} */
276
- self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
275
+ self.componentLabels = ObjectAssign({ ...colorPickerLabels }, tempComponentLabels);
277
276
 
278
277
  /** @type {Color} */
279
278
  self.color = new Color(input.value || '#fff', format);
@@ -282,14 +281,14 @@ export default class ColorPicker {
282
281
  self.format = format;
283
282
 
284
283
  // set colour defaults
285
- if (colorKeywords instanceof Array) {
284
+ if (colorKeywords instanceof Array && colorKeywords.length) {
286
285
  self.colorKeywords = colorKeywords;
287
286
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
288
287
  self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
289
288
  }
290
289
 
291
290
  // set colour presets
292
- if (colorPresets instanceof Array) {
291
+ if (colorPresets instanceof Array && colorPresets.length) {
293
292
  self.colorPresets = colorPresets;
294
293
  } else if (typeof colorPresets === 'string' && colorPresets.length) {
295
294
  if (isValidJSON(colorPresets)) {
@@ -420,6 +419,7 @@ export default class ColorPicker {
420
419
  let colorName;
421
420
 
422
421
  // determine color appearance
422
+ /* istanbul ignore else */
423
423
  if (lightness === 100 && saturation === 0) {
424
424
  colorName = colorLabels.white;
425
425
  } else if (lightness === 0) {
@@ -520,13 +520,14 @@ export default class ColorPicker {
520
520
  const self = this;
521
521
  const { activeElement } = getDocument(self.input);
522
522
 
523
- if ((e.type === touchmoveEvent && self.dragElement)
523
+ self.updateDropdownPosition();
524
+
525
+ /* istanbul ignore next */
526
+ if (([pointermoveEvent, touchmoveEvent].includes(e.type) && self.dragElement)
524
527
  || (activeElement && self.controlKnobs.includes(activeElement))) {
525
528
  e.stopPropagation();
526
529
  e.preventDefault();
527
530
  }
528
-
529
- self.updateDropdownPosition();
530
531
  }
531
532
 
532
533
  /**
@@ -600,7 +601,9 @@ export default class ColorPicker {
600
601
 
601
602
  self.update();
602
603
 
604
+ /* istanbul ignore else */
603
605
  if (currentActive !== target) {
606
+ /* istanbul ignore else */
604
607
  if (currentActive) {
605
608
  removeClass(currentActive, 'active');
606
609
  removeAttribute(currentActive, ariaSelected);
@@ -618,15 +621,13 @@ export default class ColorPicker {
618
621
 
619
622
  /**
620
623
  * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
621
- * @param {TouchEvent} e
624
+ * @param {PointerEvent} e
622
625
  * @this {ColorPicker}
623
626
  */
624
627
  pointerDown(e) {
625
628
  const self = this;
626
629
  /** @type {*} */
627
- const {
628
- type, target, touches, pageX, pageY,
629
- } = e;
630
+ const { target, pageX, pageY } = e;
630
631
  const { colorMenu, visuals, controlKnobs } = self;
631
632
  const [v1, v2, v3] = visuals;
632
633
  const [c1, c2, c3] = controlKnobs;
@@ -634,11 +635,10 @@ export default class ColorPicker {
634
635
  const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
635
636
  const visualRect = getBoundingClientRect(visual);
636
637
  const html = getDocumentElement(v1);
637
- const X = type === 'touchstart' ? touches[0].pageX : pageX;
638
- const Y = type === 'touchstart' ? touches[0].pageY : pageY;
639
- const offsetX = X - html.scrollLeft - visualRect.left;
640
- const offsetY = Y - html.scrollTop - visualRect.top;
638
+ const offsetX = pageX - html.scrollLeft - visualRect.left;
639
+ const offsetY = pageY - html.scrollTop - visualRect.top;
641
640
 
641
+ /* istanbul ignore else */
642
642
  if (target === v1 || target === c1) {
643
643
  self.dragElement = visual;
644
644
  self.changeControl1(offsetX, offsetY);
@@ -662,7 +662,7 @@ export default class ColorPicker {
662
662
 
663
663
  /**
664
664
  * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
665
- * @param {TouchEvent} e
665
+ * @param {PointerEvent} e
666
666
  * @this {ColorPicker}
667
667
  */
668
668
  pointerUp({ target }) {
@@ -671,9 +671,8 @@ export default class ColorPicker {
671
671
  const doc = getDocument(parent);
672
672
  const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
673
673
  const selection = doc.getSelection();
674
- // @ts-ignore
674
+
675
675
  if (!self.dragElement && !selection.toString().length
676
- // @ts-ignore
677
676
  && !parent.contains(target)) {
678
677
  self.hide(currentOpen);
679
678
  }
@@ -683,25 +682,20 @@ export default class ColorPicker {
683
682
 
684
683
  /**
685
684
  * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
686
- * @param {TouchEvent} e
685
+ * @param {PointerEvent} e
687
686
  */
688
687
  pointerMove(e) {
689
688
  const self = this;
690
689
  const { dragElement, visuals } = self;
691
690
  const [v1, v2, v3] = visuals;
692
- const {
693
- // @ts-ignore
694
- type, touches, pageX, pageY,
695
- } = e;
691
+ const { pageX, pageY } = e;
696
692
 
697
693
  if (!dragElement) return;
698
694
 
699
695
  const controlRect = getBoundingClientRect(dragElement);
700
696
  const win = getDocumentElement(v1);
701
- const X = type === touchmoveEvent ? touches[0].pageX : pageX;
702
- const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
703
- const offsetX = X - win.scrollLeft - controlRect.left;
704
- const offsetY = Y - win.scrollTop - controlRect.top;
697
+ const offsetX = pageX - win.scrollLeft - controlRect.left;
698
+ const offsetY = pageY - win.scrollTop - controlRect.top;
705
699
 
706
700
  if (dragElement === v1) {
707
701
  self.changeControl1(offsetX, offsetY);
@@ -735,13 +729,16 @@ export default class ColorPicker {
735
729
  const currentKnob = controlKnobs.find((x) => x === activeElement);
736
730
  const yRatio = offsetHeight / 360;
737
731
 
732
+ /* istanbul ignore else */
738
733
  if (currentKnob) {
739
734
  let offsetX = 0;
740
735
  let offsetY = 0;
741
736
 
737
+ /* istanbul ignore else */
742
738
  if (target === c1) {
743
739
  const xRatio = offsetWidth / 100;
744
740
 
741
+ /* istanbul ignore else */
745
742
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
746
743
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
747
744
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
@@ -787,6 +784,7 @@ export default class ColorPicker {
787
784
  const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
788
785
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
789
786
 
787
+ /* istanbul ignore else */
790
788
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
791
789
  if (activeElement === input) {
792
790
  if (isNonColorValue) {
@@ -1101,6 +1099,7 @@ export default class ColorPicker {
1101
1099
  const hue = roundPart(hsl.h * 360);
1102
1100
  let newColor;
1103
1101
 
1102
+ /* istanbul ignore else */
1104
1103
  if (format === 'hex') {
1105
1104
  newColor = self.color.toHexString(true);
1106
1105
  i1.value = self.hex;
@@ -1201,15 +1200,15 @@ export default class ColorPicker {
1201
1200
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
1202
1201
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
1203
1202
 
1204
- // if (!self.isValid) {
1205
1203
  self.value = self.color.toString(true);
1206
- // }
1207
1204
 
1205
+ /* istanbul ignore else */
1208
1206
  if (openDropdown) {
1209
1207
  removeClass(openDropdown, 'show');
1210
1208
  setAttribute(relatedBtn, ariaExpanded, 'false');
1211
1209
  setTimeout(() => {
1212
1210
  removePosition(openDropdown);
1211
+ /* istanbul ignore else */
1213
1212
  if (!querySelector('.show', parent)) {
1214
1213
  removeClass(parent, 'open');
1215
1214
  toggleEventsOnShown(self);
@@ -1222,7 +1221,7 @@ export default class ColorPicker {
1222
1221
  focus(pickerToggle);
1223
1222
  }
1224
1223
  setAttribute(input, tabIndex, '-1');
1225
- if (menuToggle) {
1224
+ if (relatedBtn === menuToggle) {
1226
1225
  setAttribute(menuToggle, tabIndex, '-1');
1227
1226
  }
1228
1227
  }
@@ -23,17 +23,16 @@ export default function getColorMenu(self, colorsSource, menuClass) {
23
23
  const isOptionsMenu = menuClass === 'color-options';
24
24
  const isPalette = colorsSource instanceof ColorPalette;
25
25
  const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
26
- let colorsArray = isPalette ? colorsSource.colors : colorsSource;
27
- colorsArray = colorsArray instanceof Array ? colorsArray : [];
26
+ const colorsArray = isPalette ? colorsSource.colors : colorsSource;
28
27
  const colorsCount = colorsArray.length;
29
28
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
30
- const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
29
+ const fit = lightSteps || [9, 10].find((x) => colorsCount >= x * 2 && !(colorsCount % x)) || 5;
31
30
  const isMultiLine = isOptionsMenu && colorsCount > fit;
32
31
  let rowCountHover = 2;
33
- rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
34
- rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
35
- rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
36
- const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
32
+ rowCountHover = isMultiLine && colorsCount > fit * 2 ? 3 : rowCountHover;
33
+ rowCountHover = isMultiLine && colorsCount > fit * 3 ? 4 : rowCountHover;
34
+ rowCountHover = isMultiLine && colorsCount > fit * 4 ? 5 : rowCountHover;
35
+ const rowCount = rowCountHover - (colorsCount <= fit * 3 ? 1 : 2);
37
36
  const isScrollable = isMultiLine && colorsCount > rowCount * fit;
38
37
  let finalClass = menuClass;
39
38
  finalClass += isScrollable ? ' scrollable' : '';
@@ -41,7 +40,7 @@ export default function getColorMenu(self, colorsSource, menuClass) {
41
40
  const gap = isMultiLine ? '1px' : '0.25rem';
42
41
  let optionSize = isMultiLine ? 1.75 : 2;
43
42
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
44
- const menuHeight = `${(rowCount || 1) * optionSize}rem`;
43
+ const menuHeight = `${rowCount * optionSize}rem`;
45
44
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
46
45
  /** @type {HTMLUListElement} */
47
46
  // @ts-ignore -- <UL> is an `HTMLElement`
@@ -17,7 +17,6 @@ import vHidden from './vHidden';
17
17
  import tabIndex from './tabindex';
18
18
 
19
19
  import Color from '../color';
20
- import ColorPalette from '../color-palette';
21
20
 
22
21
  /**
23
22
  * Generate HTML markup and update instance properties.
@@ -75,16 +74,14 @@ export default function setMarkup(self) {
75
74
  });
76
75
 
77
76
  // color presets
78
- if ((colorPresets instanceof Array && colorPresets.length)
79
- || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
80
- const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
81
- presetsDropdown.append(presetsMenu);
77
+ if (colorPresets) {
78
+ presetsDropdown.append(getColorMenu(self, colorPresets, 'color-options'));
82
79
  }
83
80
 
84
81
  // explicit defaults [reset, initial, inherit, transparent, currentColor]
82
+ // also custom defaults [default: #069, complementary: #930]
85
83
  if (colorKeywords && colorKeywords.length) {
86
- const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
87
- presetsDropdown.append(keywordsMenu);
84
+ presetsDropdown.append(getColorMenu(self, colorKeywords, 'color-defaults'));
88
85
  }
89
86
 
90
87
  const presetsBtn = createElement({
@@ -0,0 +1,70 @@
1
+ import setAttribute from 'shorter-js/src/attr/setAttribute';
2
+ import removeAttribute from 'shorter-js/src/attr/removeAttribute';
3
+ import ObjectKeys from 'shorter-js/src/misc/ObjectKeys';
4
+
5
+ import ColorPalette from '../color-palette';
6
+ import colorNames from './colorNames';
7
+ import colorPickerLabels from './colorPickerLabels';
8
+
9
+ /**
10
+ * A small utility to toggle `ColorPickerElement` attributes
11
+ * when `connectedCallback` or `disconnectedCallback` methods
12
+ * are called and helps the instance keep its value and settings instact.
13
+ *
14
+ * @param {CP.ColorPickerElement} self ColorPickerElement instance
15
+ * @param {Function=} callback when `true`, attributes are added
16
+ *
17
+ * @example
18
+ * const attributes = [
19
+ * // essentials
20
+ * 'value', 'format',
21
+ * // presets menus
22
+ * 'color-presets', 'color-keywords',
23
+ * // labels
24
+ * 'color-labels', 'component-labels',
25
+ * ];
26
+ */
27
+ export default function toggleCEAttr(self, callback) {
28
+ if (callback) {
29
+ const { input, colorPicker } = self;
30
+
31
+ const {
32
+ value, format, colorPresets, colorKeywords, componentLabels, colorLabels,
33
+ } = colorPicker;
34
+
35
+ const { id, placeholder } = input;
36
+
37
+ setAttribute(self, 'data-id', id);
38
+ setAttribute(self, 'data-value', value);
39
+ setAttribute(self, 'data-format', format);
40
+ setAttribute(self, 'data-placeholder', placeholder);
41
+
42
+ if (ObjectKeys(colorPickerLabels).some((l) => colorPickerLabels[l] !== componentLabels[l])) {
43
+ setAttribute(self, 'data-component-labels', JSON.stringify(componentLabels));
44
+ }
45
+ if (!colorNames.every((c) => c === colorLabels[c])) {
46
+ setAttribute(self, 'data-color-labels', colorNames.map((n) => colorLabels[n]).join(','));
47
+ }
48
+ if (colorPresets instanceof ColorPalette) {
49
+ const { hue, hueSteps, lightSteps } = colorPresets;
50
+ setAttribute(self, 'data-color-presets', JSON.stringify({ hue, hueSteps, lightSteps }));
51
+ }
52
+ if (Array.isArray(colorPresets) && colorPresets.length) {
53
+ setAttribute(self, 'data-color-presets', colorPresets.join(','));
54
+ }
55
+ if (colorKeywords) {
56
+ setAttribute(self, 'data-color-keywords', colorKeywords.join(','));
57
+ }
58
+ setTimeout(callback, 0);
59
+ } else {
60
+ // keep id
61
+ // removeAttribute(self, 'data-id');
62
+ removeAttribute(self, 'data-value');
63
+ removeAttribute(self, 'data-format');
64
+ removeAttribute(self, 'data-placeholder');
65
+ removeAttribute(self, 'data-component-labels');
66
+ removeAttribute(self, 'data-color-labels');
67
+ removeAttribute(self, 'data-color-presets');
68
+ removeAttribute(self, 'data-color-keywords');
69
+ }
70
+ }
@@ -0,0 +1,7 @@
1
+ $transparency-levels: (
2
+ 15, 25, 33, 50, 75, 90
3
+ );
4
+
5
+ $dropdown-transition: transform .33s ease, opacity .33s ease;
6
+ $btn-transition: box-shadow .33s ease, border .33s ease;
7
+ $options-transition: height .33s ease;