@thednp/color-picker 0.0.2 → 1.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.
@@ -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;