Skip to content

Commit 894628f

Browse files
feat(ui5-color-palette-popover): initial implementation (#3746)
1 parent 23ce10f commit 894628f

18 files changed

+919
-17
lines changed

packages/main/bundle.common.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Carousel from "./dist/Carousel.js";
4242
import CheckBox from "./dist/CheckBox.js";
4343
import ColorPalette from "./dist/ColorPalette.js";
4444
import ColorPaletteItem from "./dist/ColorPaletteItem.js";
45+
import ColorPalettePopover from "./dist/ColorPalettePopover.js";
4546
import ColorPicker from "./dist/ColorPicker.js";
4647
import ComboBox from "./dist/ComboBox.js";
4748
import DatePicker from "./dist/DatePicker.js";

packages/main/src/ColorPalette.hbs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
<div
2-
class="ui5-cp-root"
2+
class="{{classes.colorPaletteRoot}}"
33
@click={{_onclick}}
44
@keyup={{_onkeyup}}
55
@keydown={{_onkeydown}}
66
>
7+
{{#if this.showDefaultColor}}
8+
<div class="ui5-cp-default-color-button-wrapper">
9+
<ui5-button
10+
class="ui5-cp-default-color-button"
11+
design="Transparent"
12+
@click={{_onDefaultColorClick}}
13+
@keydown={{_onDefaultColorKeyDown}}>Default color</ui5-button>
14+
<div class="ui5-cp-separator"></div>
15+
</div>
16+
{{/if}}
717
<div class="ui5-cp-item-container"
818
role="region"
919
aria-label="{{colorContainerLabel}}"
20+
@keydown="{{_onColorContainerKeyDown}}"
1021
>
1122
{{#each this.displayedColors}}
1223
<slot
@@ -23,16 +34,19 @@
2334
design="Transparent"
2435
class="ui5-cp-more-colors"
2536
@click="{{_openMoreColorsDialog}}"
37+
@keydown={{_onMoreColorsKeyDown}}
2638
>{{colorPaleteMoreColorsText}}</ui5-button>
2739
</div>
2840
{{/if}}
2941

3042
{{#if showRecentColors}}
31-
<div class="ui5-cp-separator"></div>
3243
<div class="ui5-cp-recent-colors-wrapper">
33-
{{#each recentColors}}
34-
<ui5-color-palette-item value="{{this}}"></ui5-color-palette-item>
35-
{{/each}}
44+
<div class="ui5-cp-separator"></div>
45+
<div class="ui5-cp-recent-colors-container" @keydown="{{_onRecentColorsContainerKeyDown}}">
46+
{{#each recentColors}}
47+
<ui5-color-palette-item value="{{this}}"></ui5-color-palette-item>
48+
{{/each}}
49+
</div>
3650
</div>
3751
{{/if}}
3852
</div>

packages/main/src/ColorPalette.js

Lines changed: 208 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18
44
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
55
import CSSColor from "@ui5/webcomponents-base/dist/types/CSSColor.js";
66
import ItemNavigationBehavior from "@ui5/webcomponents-base/dist/types/ItemNavigationBehavior.js";
7+
import { isPhone } from "@ui5/webcomponents-base/dist/Device.js";
78
import {
89
isSpace,
910
isEnter,
11+
isDown,
12+
isUp,
13+
isTabNext,
1014
} from "@ui5/webcomponents-base/dist/Keys.js";
1115
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
1216
import ColorPaletteTemplate from "./generated/templates/ColorPaletteTemplate.lit.js";
@@ -50,6 +54,28 @@ const metadata = {
5054
type: Boolean,
5155
},
5256

57+
/**
58+
* Defines whether the user can choose the default color from a button.
59+
* @type {boolean}
60+
* @defaultvalue false
61+
* @private
62+
* @since 1.0.0-rc.16
63+
*/
64+
showDefaultColor: {
65+
type: Boolean,
66+
},
67+
68+
/**
69+
* Defines the default color of the color palette
70+
* <b>Note:</b> The default color should be a part of the ColorPalette colors</code>
71+
* @type {CSSColor}
72+
* @private
73+
* @since 1.0.0-rc.16
74+
*/
75+
defaultColor: {
76+
type: CSSColor,
77+
},
78+
5379
/**
5480
* Defines the selected color.
5581
* @type {CSSColor}
@@ -58,6 +84,15 @@ const metadata = {
5884
_selectedColor: {
5985
type: CSSColor,
6086
},
87+
88+
/**
89+
* Defines if the palette is in Popup or Embeded mode.
90+
* @type {CSSColor}
91+
* @private
92+
*/
93+
popupMode: {
94+
type: Boolean,
95+
},
6196
},
6297
slots: /** @lends sap.ui.webcomponents.main.ColorPalette.prototype */ {
6398
/**
@@ -155,10 +190,16 @@ class ColorPalette extends UI5Element {
155190
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
156191
this._itemNavigation = new ItemNavigation(this, {
157192
getItemsCallback: () => this.displayedColors,
158-
rowSize: 5,
193+
rowSize: this.rowSize,
159194
behavior: ItemNavigationBehavior.Cyclic,
160195
});
161196

197+
this._itemNavigationRecentColors = new ItemNavigation(this, {
198+
getItemsCallback: () => this.recentColorsElements,
199+
rowSize: this.rowSize,
200+
behavior: ItemNavigationBehavior.Static,
201+
});
202+
162203
this._recentColors = [];
163204
}
164205

@@ -178,6 +219,10 @@ class ColorPalette extends UI5Element {
178219
}
179220

180221
selectColor(item) {
222+
if (!item.value) {
223+
return;
224+
}
225+
181226
item.focus();
182227

183228
if (this.displayedColors.includes(item)) {
@@ -209,18 +254,122 @@ class ColorPalette extends UI5Element {
209254
}
210255

211256
_onkeyup(event) {
212-
if (isSpace(event)) {
257+
if (isSpace(event) && event.target.localName === "ui5-color-palette-item") {
213258
event.preventDefault();
214259
this.selectColor(event.target);
215260
}
216261
}
217262

218263
_onkeydown(event) {
219-
if (isEnter(event)) {
264+
if (isEnter(event) && event.target.localName === "ui5-color-palette-item") {
220265
this.selectColor(event.target);
221266
}
222267
}
223268

269+
_onDefaultColorKeyDown(event) {
270+
if (isTabNext(event) && this.popupMode) {
271+
event.preventDefault();
272+
this._onDefaultColorClick();
273+
}
274+
275+
if (isDown(event)) {
276+
event.stopPropagation();
277+
278+
this.focusColorElement(this.colorPaletteNavigationElements[1], this._itemNavigation);
279+
} else if (isUp(event)) {
280+
event.stopPropagation();
281+
const lastElementInNavigation = this.colorPaletteNavigationElements[this.colorPaletteNavigationElements.length - 1];
282+
283+
if (this.hasRecentColors) {
284+
this.focusColorElement(lastElementInNavigation, this._itemNavigationRecentColors);
285+
} else if (this.showMoreColors) {
286+
lastElementInNavigation.focus();
287+
} else {
288+
const colorPaletteFocusIndex = (this.displayedColors.length % this.rowSize) * this.rowSize;
289+
290+
this.focusColorElement(this.displayedColors[colorPaletteFocusIndex], this._itemNavigation);
291+
}
292+
}
293+
}
294+
295+
_onMoreColorsKeyDown(event) {
296+
const index = this.colorPaletteNavigationElements.indexOf(event.target);
297+
const colorPaletteFocusIndex = (this.displayedColors.length % this.rowSize) * this.rowSize;
298+
299+
if (isUp(event)) {
300+
event.stopPropagation();
301+
302+
this.focusColorElement(this.displayedColors[colorPaletteFocusIndex], this._itemNavigation);
303+
} else if (isDown(event)) {
304+
event.stopPropagation();
305+
306+
if (this.hasRecentColors) {
307+
this.focusColorElement(this.colorPaletteNavigationElements[index + 1], this._itemNavigationRecentColors);
308+
} else if (this.showDefaultColor) {
309+
this.colorPaletteNavigationElements[0].focus();
310+
} else {
311+
this.focusColorElement(this.displayedColors[0], this._itemNavigation);
312+
}
313+
}
314+
}
315+
316+
_onColorContainerKeyDown(event) {
317+
const lastElementInNavigation = this.colorPaletteNavigationElements[this.colorPaletteNavigationElements.length - 1];
318+
if (isTabNext(event) && this.popupMode) {
319+
event.preventDefault();
320+
this.selectColor(event.target);
321+
}
322+
323+
if (isUp(event) && event.target === this.displayedColors[0] && this.colorPaletteNavigationElements.length > 1) {
324+
event.stopPropagation();
325+
if (this.showDefaultColor) {
326+
this.colorPaletteNavigationElements[0].focus();
327+
} else if (!this.showDefaultColor && this.hasRecentColors) {
328+
this.focusColorElement(lastElementInNavigation, this._itemNavigationRecentColors);
329+
} else if (!this.showDefaultColor && this.showMoreColors) {
330+
lastElementInNavigation.focus();
331+
}
332+
} else if (isDown(event) && event.target === this.displayedColors[this.displayedColors.length - 1] && this.colorPaletteNavigationElements.length > 1) {
333+
event.stopPropagation();
334+
const isRecentColorsNextElement = (this.showDefaultColor && !this.showMoreColors && this.hasRecentColors) || (!this.showDefaultColor && !this.showMoreColors && this.hasRecentColors);
335+
336+
if (this.showDefaultColor && this.showMoreColors) {
337+
this.colorPaletteNavigationElements[2].focus();
338+
} else if (this.showDefaultColor && !this.showMoreColors && (!this.showRecentColors || !this.recentColors[0])) {
339+
this.colorPaletteNavigationElements[0].focus();
340+
} else if (isRecentColorsNextElement) {
341+
this.focusColorElement(lastElementInNavigation, this._itemNavigationRecentColors);
342+
} else if (!this.showDefaultColor && this.showMoreColors) {
343+
this.colorPaletteNavigationElements[1].focus();
344+
}
345+
}
346+
}
347+
348+
_onRecentColorsContainerKeyDown(event) {
349+
if (isUp(event)) {
350+
if (this.showMoreColors) {
351+
this.colorPaletteNavigationElements[1 + this.showDefaultColor].focus();
352+
} else if (!this.showMoreColors && this.colorPaletteNavigationElements.length > 1) {
353+
const colorPaletteFocusIndex = (this.displayedColors.length % this.rowSize) * this.rowSize;
354+
event.stopPropagation();
355+
356+
this.focusColorElement(this.displayedColors[colorPaletteFocusIndex], this._itemNavigation);
357+
}
358+
} else if (isDown(event)) {
359+
if (this.showDefaultColor) {
360+
this.colorPaletteNavigationElements[0].focus();
361+
} else {
362+
event.stopPropagation();
363+
this.focusColorElement(this.displayedColors[0], this._itemNavigation);
364+
}
365+
}
366+
}
367+
368+
focusColorElement(element, itemNavigation) {
369+
itemNavigation.setCurrentItem(element);
370+
itemNavigation._focusCurrentItem();
371+
}
372+
224373
async _chooseCustomColor() {
225374
const colorPicker = await this.getColorPicker();
226375
this._setColor(colorPicker.color);
@@ -237,6 +386,12 @@ class ColorPalette extends UI5Element {
237386
dialog.show();
238387
}
239388

389+
_onDefaultColorClick() {
390+
if (this.defaultColor) {
391+
this._setColor(this.defaultColor);
392+
}
393+
}
394+
240395
/**
241396
* Returns the selected color.
242397
*/
@@ -245,7 +400,7 @@ class ColorPalette extends UI5Element {
245400
}
246401

247402
get displayedColors() {
248-
return this.colors.filter(item => item.value).slice(0, 15);
403+
return this.getSlottedNodes("colors").filter(item => item.value).slice(0, 15);
249404
}
250405

251406
get colorContainerLabel() {
@@ -260,18 +415,64 @@ class ColorPalette extends UI5Element {
260415
return this.showMoreColors && this.moreColorsFeature;
261416
}
262417

418+
get rowSize() {
419+
return 5;
420+
}
421+
422+
get hasRecentColors() {
423+
return this.showRecentColors && this.recentColors[0];
424+
}
425+
263426
get recentColors() {
264-
if (this._recentColors.length > 5) {
265-
this._recentColors = this._recentColors.slice(0, 5);
427+
if (this._recentColors.length > this.rowSize) {
428+
this._recentColors = this._recentColors.slice(0, this.rowSize);
266429
}
267430

268-
while (this._recentColors.length < 5) {
431+
while (this._recentColors.length < this.rowSize) {
269432
this._recentColors.push("");
270433
}
271434

272435
return this._recentColors;
273436
}
274437

438+
get recentColorsElements() {
439+
if (this.getDomRef()) {
440+
return Array.from(this.getDomRef().querySelectorAll(".ui5-cp-recent-colors-wrapper [ui5-color-palette-item]")).filter(x => x.value !== "");
441+
}
442+
443+
return [];
444+
}
445+
446+
get colorPaletteNavigationElements() {
447+
const navigationElements = [];
448+
const rootElement = this.shadowRoot.querySelector(".ui5-cp-root");
449+
450+
if (this.showDefaultColor) {
451+
navigationElements.push(rootElement.querySelector(".ui5-cp-default-color-button"));
452+
}
453+
454+
navigationElements.push(this.displayedColors[0]);
455+
456+
if (this.showMoreColors) {
457+
navigationElements.push(rootElement.querySelector(".ui5-cp-more-colors"));
458+
}
459+
460+
if (this.showRecentColors && !!this.recentColorsElements.length) {
461+
navigationElements.push(this.recentColorsElements[0]);
462+
}
463+
464+
return navigationElements;
465+
}
466+
467+
get classes() {
468+
return {
469+
colorPaletteRoot: {
470+
"ui5-cp-root": true,
471+
"ui5-cp-root-phone": isPhone(),
472+
},
473+
};
474+
}
475+
275476
async _getDialog() {
276477
const staticAreaItem = await this.getStaticAreaItemDomRef();
277478
return staticAreaItem.querySelector("[ui5-dialog]");

packages/main/src/ColorPaletteItem.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
33
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
44
import CSSColor from "@ui5/webcomponents-base/dist/types/CSSColor.js";
5+
import { isPhone } from "@ui5/webcomponents-base/dist/Device.js";
56
import ColorPaletteItemTemplate from "./generated/templates/ColorPaletteItemTemplate.lit.js";
67
import {
78
COLORPALETTE_COLOR_LABEL,
@@ -48,6 +49,15 @@ const metadata = {
4849
type: String,
4950
},
5051

52+
/**
53+
* Defines if the ColorPalette is on phone mode.
54+
* @private
55+
* @type {Boolean}
56+
*/
57+
phone: {
58+
type: Boolean,
59+
},
60+
5161
/**
5262
* @private
5363
* @type {boolean}
@@ -107,6 +117,7 @@ class ColorPaletteItem extends UI5Element {
107117

108118
onBeforeRendering() {
109119
this._disabled = !this.value;
120+
this.phone = isPhone();
110121
}
111122

112123
get colorLabel() {

0 commit comments

Comments
 (0)