@kenlo/sds-design-system 0.9.9
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/README.md +129 -0
- package/components/Accordion/Accordion.css +186 -0
- package/components/Accordion/accordion-variants.js +17 -0
- package/components/Accordion/accordion.marko +264 -0
- package/components/AccordionGroup/AccordionGroup.css +35 -0
- package/components/AccordionGroup/accordion-group.marko +17 -0
- package/components/Alert/Alert.css +104 -0
- package/components/Alert/alert-variants.js +35 -0
- package/components/Alert/alert.marko +94 -0
- package/components/AttachmentList/AttachmentList.css +92 -0
- package/components/AttachmentList/attachment-list.marko +51 -0
- package/components/AttachmentList/attachment-row.marko +167 -0
- package/components/Avatar/Avatar.css +110 -0
- package/components/Avatar/avatar-variants.js +12 -0
- package/components/Avatar/avatar.marko +71 -0
- package/components/Badge/Badge.css +381 -0
- package/components/Badge/badge-dismissible.marko +143 -0
- package/components/Badge/badge-variants.js +35 -0
- package/components/Badge/badge.marko +134 -0
- package/components/BottomSheet/BottomSheet.css +300 -0
- package/components/BottomSheet/bottom-sheet.marko +340 -0
- package/components/BulkActionBar/BulkActionBar.css +88 -0
- package/components/BulkActionBar/bulk-action-bar.marko +186 -0
- package/components/Button/Button.css +340 -0
- package/components/Button/button-variants.js +25 -0
- package/components/Button/button.marko +139 -0
- package/components/Card/Card.css +391 -0
- package/components/Card/card-body.marko +5 -0
- package/components/Card/card-collapsible.marko +94 -0
- package/components/Card/card-footer.marko +12 -0
- package/components/Card/card-header.marko +30 -0
- package/components/Card/card-info-pair.marko +17 -0
- package/components/Card/card-info-row.marko +12 -0
- package/components/Card/card-section.marko +14 -0
- package/components/Card/card-variants.js +24 -0
- package/components/Card/card.marko +124 -0
- package/components/Checkbox/Checkbox.css +249 -0
- package/components/Checkbox/checkbox-variants.js +21 -0
- package/components/Checkbox/checkbox.marko +128 -0
- package/components/ColorPicker/ColorPicker.css +253 -0
- package/components/ColorPicker/color-maths.js +104 -0
- package/components/ColorPicker/color-picker.marko +474 -0
- package/components/DatePicker/DatePicker.css +429 -0
- package/components/DatePicker/date-logic.js +225 -0
- package/components/DatePicker/date-picker.marko +492 -0
- package/components/Drawer/Drawer.css +174 -0
- package/components/Drawer/drawer.marko +262 -0
- package/components/Floating/Floating.css +28 -0
- package/components/Floating/floating-position.js +251 -0
- package/components/Floating/floating.marko +291 -0
- package/components/Form/Form.css +15 -0
- package/components/Form/form-context.js +69 -0
- package/components/Form/form.marko +73 -0
- package/components/GradientPicker/GradientPicker.css +179 -0
- package/components/GradientPicker/gradient-picker.marko +398 -0
- package/components/Grid/Grid.css +161 -0
- package/components/Grid/col.marko +36 -0
- package/components/Grid/grid-variants.js +43 -0
- package/components/Grid/grid.marko +25 -0
- package/components/Header/Header.css +457 -0
- package/components/Header/header.marko +523 -0
- package/components/Icon/Icon.css +44 -0
- package/components/Icon/icon-colors.js +31 -0
- package/components/Icon/icon.marko +56 -0
- package/components/IconButton/IconButton.css +85 -0
- package/components/IconButton/icon-button-variants.js +18 -0
- package/components/IconButton/icon-button.marko +103 -0
- package/components/Incrementer/Incrementer.css +94 -0
- package/components/Incrementer/incrementer.marko +70 -0
- package/components/Input/Input.css +547 -0
- package/components/Input/input-countries.js +109 -0
- package/components/Input/input-masks.js +218 -0
- package/components/Input/input-types.js +194 -0
- package/components/Input/input.marko +530 -0
- package/components/Menu/Menu.css +651 -0
- package/components/Menu/menu-variants.js +89 -0
- package/components/Menu/menu.marko +560 -0
- package/components/Modal/Modal.css +160 -0
- package/components/Modal/modal.marko +296 -0
- package/components/Nav/Nav.css +298 -0
- package/components/Nav/nav-variants.js +23 -0
- package/components/Nav/nav.marko +164 -0
- package/components/Notification/Notification.css +78 -0
- package/components/Notification/notification-variants.js +30 -0
- package/components/Notification/notification.marko +55 -0
- package/components/Pagination/Pagination.css +78 -0
- package/components/Pagination/pagination-pages.js +66 -0
- package/components/Pagination/pagination.marko +141 -0
- package/components/Pill/Pill.css +193 -0
- package/components/Pill/pill-variants.js +16 -0
- package/components/Pill/pill.marko +165 -0
- package/components/Popover/Popover.css +121 -0
- package/components/Popover/popover.marko +96 -0
- package/components/Radio/Radio.css +230 -0
- package/components/Radio/radio-variants.js +16 -0
- package/components/Radio/radio.marko +93 -0
- package/components/RadioGroup/RadioGroup.css +32 -0
- package/components/RadioGroup/radio-group.marko +93 -0
- package/components/SearchBar/SearchBar.css +181 -0
- package/components/SearchBar/search-bar-variants.js +24 -0
- package/components/SearchBar/search-bar.marko +157 -0
- package/components/Select/Select.css +427 -0
- package/components/Select/select-options.js +163 -0
- package/components/Select/select.marko +449 -0
- package/components/Separator/Separator.css +22 -0
- package/components/Separator/separator.marko +14 -0
- package/components/Skeleton/Skeleton.css +100 -0
- package/components/Skeleton/skeleton-avatar.marko +28 -0
- package/components/Skeleton/skeleton-button.marko +22 -0
- package/components/Skeleton/skeleton-image.marko +20 -0
- package/components/Skeleton/skeleton-input.marko +22 -0
- package/components/Skeleton/skeleton-variants.js +16 -0
- package/components/Skeleton/skeleton.marko +53 -0
- package/components/Spinner/Spinner.css +173 -0
- package/components/Spinner/spinner-geometry.js +39 -0
- package/components/Spinner/spinner-overlay.marko +97 -0
- package/components/Spinner/spinner.marko +88 -0
- package/components/StatCard/StatCard.css +90 -0
- package/components/StatCard/stat-card-group.marko +5 -0
- package/components/StatCard/stat-card.marko +57 -0
- package/components/Switch/Switch.css +209 -0
- package/components/Switch/switch.marko +99 -0
- package/components/Table/Table.css +270 -0
- package/components/Table/table-cell.marko +28 -0
- package/components/Table/table-header.marko +27 -0
- package/components/Table/table-logic.js +129 -0
- package/components/Table/table.marko +372 -0
- package/components/Text/Text.css +79 -0
- package/components/Text/text-variants.js +25 -0
- package/components/Text/text.marko +48 -0
- package/components/TextEditor/TextEditor.css +78 -0
- package/components/TextEditor/safe-url.js +50 -0
- package/components/TextEditor/text-editor-commands.js +22 -0
- package/components/TextEditor/text-editor.marko +253 -0
- package/components/Toast/Toast.css +88 -0
- package/components/Toast/toast-api.js +19 -0
- package/components/Toast/toast-store.js +118 -0
- package/components/Toast/toast-variants.js +16 -0
- package/components/Toast/toast.marko +93 -0
- package/components/Toast/toaster.marko +70 -0
- package/components/Toggle/Toggle.css +169 -0
- package/components/Toggle/toggle.marko +93 -0
- package/components/Tooltip/Tooltip.css +115 -0
- package/components/Tooltip/tooltip.marko +77 -0
- package/components/Uploader/Uploader.css +180 -0
- package/components/Uploader/crop-geometry.js +161 -0
- package/components/Uploader/uploader.marko +269 -0
- package/foundations/Scrollbar/Scrollbar.css +26 -0
- package/foundations/attrs.js +27 -0
- package/foundations/breakpoints.js +27 -0
- package/foundations/contrast.js +74 -0
- package/foundations/custom-scheme.js +222 -0
- package/foundations/overlay.js +108 -0
- package/foundations/styles.css +284 -0
- package/foundations/text.js +22 -0
- package/foundations/theme-server.js +81 -0
- package/foundations/theme.js +42 -0
- package/foundations/tokens.css +1656 -0
- package/icons/custom/custom-video-horizontal-filled.marko +6 -0
- package/icons/custom/custom-video-horizontal-regular.marko +6 -0
- package/icons/custom/custom-video-tour-3d-filled.marko +7 -0
- package/icons/custom/custom-video-tour-3d-regular.marko +9 -0
- package/icons/custom/custom-video-vertical-filled.marko +6 -0
- package/icons/custom/custom-video-vertical-regular.marko +6 -0
- package/icons/generated/fluent-add-regular.marko +3 -0
- package/icons/generated/fluent-alert-regular.marko +3 -0
- package/icons/generated/fluent-apps-regular.marko +3 -0
- package/icons/generated/fluent-arrow-download-regular.marko +3 -0
- package/icons/generated/fluent-arrow-left-regular.marko +3 -0
- package/icons/generated/fluent-arrow-right-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-clockwise-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-counterclockwise-regular.marko +3 -0
- package/icons/generated/fluent-border-all-regular.marko +3 -0
- package/icons/generated/fluent-calendar-regular.marko +3 -0
- package/icons/generated/fluent-checkmark-circle-filled.marko +3 -0
- package/icons/generated/fluent-checkmark-regular.marko +3 -0
- package/icons/generated/fluent-chevron-down-regular.marko +3 -0
- package/icons/generated/fluent-chevron-left-regular.marko +3 -0
- package/icons/generated/fluent-chevron-right-regular.marko +3 -0
- package/icons/generated/fluent-chevron-up-regular.marko +3 -0
- package/icons/generated/fluent-circle-half-fill-regular.marko +3 -0
- package/icons/generated/fluent-clock-regular.marko +3 -0
- package/icons/generated/fluent-color-regular.marko +3 -0
- package/icons/generated/fluent-crop-regular.marko +3 -0
- package/icons/generated/fluent-dark-theme-regular.marko +3 -0
- package/icons/generated/fluent-delete-regular.marko +3 -0
- package/icons/generated/fluent-desktop-regular.marko +3 -0
- package/icons/generated/fluent-dismiss-circle-filled.marko +3 -0
- package/icons/generated/fluent-dismiss-regular.marko +3 -0
- package/icons/generated/fluent-document-pdf-regular.marko +3 -0
- package/icons/generated/fluent-document-regular.marko +3 -0
- package/icons/generated/fluent-eye-off-regular.marko +3 -0
- package/icons/generated/fluent-eye-regular.marko +3 -0
- package/icons/generated/fluent-eyedropper-regular.marko +3 -0
- package/icons/generated/fluent-grid-regular.marko +3 -0
- package/icons/generated/fluent-history-regular.marko +3 -0
- package/icons/generated/fluent-home-regular.marko +3 -0
- package/icons/generated/fluent-image-regular.marko +3 -0
- package/icons/generated/fluent-info-regular.marko +3 -0
- package/icons/generated/fluent-link-regular.marko +3 -0
- package/icons/generated/fluent-more-horizontal-regular.marko +3 -0
- package/icons/generated/fluent-navigation-regular.marko +3 -0
- package/icons/generated/fluent-paint-brush-regular.marko +3 -0
- package/icons/generated/fluent-question-circle-regular.marko +3 -0
- package/icons/generated/fluent-search-regular.marko +3 -0
- package/icons/generated/fluent-settings-regular.marko +3 -0
- package/icons/generated/fluent-square-hint-regular.marko +3 -0
- package/icons/generated/fluent-subtract-regular.marko +3 -0
- package/icons/generated/fluent-text-align-center-regular.marko +3 -0
- package/icons/generated/fluent-text-align-left-regular.marko +3 -0
- package/icons/generated/fluent-text-align-right-regular.marko +3 -0
- package/icons/generated/fluent-text-bold-regular.marko +3 -0
- package/icons/generated/fluent-text-bullet-list-regular.marko +3 -0
- package/icons/generated/fluent-text-font-regular.marko +3 -0
- package/icons/generated/fluent-text-font-size-regular.marko +3 -0
- package/icons/generated/fluent-text-italic-regular.marko +3 -0
- package/icons/generated/fluent-text-number-list-ltr-regular.marko +3 -0
- package/icons/generated/fluent-video-regular.marko +3 -0
- package/icons/generated/fluent-warning-filled.marko +3 -0
- package/icons/generated/fluent-weather-moon-regular.marko +3 -0
- package/icons/generated/fluent-weather-sunny-regular.marko +3 -0
- package/icons/generated/fluent-zoom-in-regular.marko +3 -0
- package/icons/generated/fluent-zoom-out-regular.marko +3 -0
- package/icons/manifest.json +61 -0
- package/icons/social/social-facebook-color.marko +7 -0
- package/icons/social/social-facebook.marko +6 -0
- package/icons/social/social-google-color.marko +20 -0
- package/icons/social/social-google.marko +6 -0
- package/icons/social/social-instagram-color.marko +16 -0
- package/icons/social/social-instagram.marko +6 -0
- package/icons/social/social-linkedin-color.marko +8 -0
- package/icons/social/social-linkedin.marko +8 -0
- package/icons/social/social-messenger-color.marko +15 -0
- package/icons/social/social-messenger.marko +6 -0
- package/icons/social/social-pinterest-color.marko +6 -0
- package/icons/social/social-pinterest.marko +6 -0
- package/icons/social/social-telegram-color.marko +7 -0
- package/icons/social/social-telegram.marko +6 -0
- package/icons/social/social-whatsapp-color.marko +8 -0
- package/icons/social/social-whatsapp.marko +7 -0
- package/icons/social/social-x-color.marko +7 -0
- package/icons/social/social-x.marko +6 -0
- package/icons/social/social-youtube-color.marko +7 -0
- package/icons/social/social-youtube.marko +6 -0
- package/marko.json +203 -0
- package/package.json +55 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
<!-- sds-color-picker — ported from kdls-design-system v2.5.1 (0f3edf8)
|
|
2
|
+
components/ColorPicker/ColorPicker.tsx.
|
|
3
|
+
|
|
4
|
+
A swatch that opens a picker: a saturation canvas, a hue slider, an opacity slider, the
|
|
5
|
+
eyedropper where the browser has one, and R/G/B/A and HEX fields. The conversions are
|
|
6
|
+
in color-maths.js and tested there — a round trip through HSV and back is exactly the
|
|
7
|
+
arithmetic that drifts by one, and a picker whose colour creeps as you drag is a picker
|
|
8
|
+
nobody trusts.
|
|
9
|
+
|
|
10
|
+
Divergences:
|
|
11
|
+
|
|
12
|
+
KDLS falls back to [41, 142, 230] when it cannot read the value — #298EE6, Kenlo blue,
|
|
13
|
+
in a component that is supposed to carry no brand. It was in the stylesheet too. Both
|
|
14
|
+
now start from the middle of the neutral ramp: a colour with no opinion, which is what
|
|
15
|
+
a picker with no value should show.
|
|
16
|
+
|
|
17
|
+
The trigger is a <button>. KDLS uses a div with role="button" and a hand-written
|
|
18
|
+
tabindex, which gets focus but not Enter or Space — so the picker cannot be opened from
|
|
19
|
+
the keyboard at all.
|
|
20
|
+
|
|
21
|
+
hexToRgb checks the shape before the value. KDLS relies on parseInt returning NaN, and
|
|
22
|
+
parseInt reads a valid prefix and stops: "12zzzz" parses as 0x12 and is accepted as a
|
|
23
|
+
colour.
|
|
24
|
+
|
|
25
|
+
The canvas and the sliders take arrow keys. KDLS binds mousedown only, so every control
|
|
26
|
+
inside the panel is mouse-only — on a picker whose whole job is choosing a precise
|
|
27
|
+
value.
|
|
28
|
+
|
|
29
|
+
`overlay` takes the panel out of the flow entirely: a backdrop over the page with the
|
|
30
|
+
panel centred in it, instead of a panel anchored to the trigger. It is for one situation —
|
|
31
|
+
this control living inside another panel, where anchoring would put a panel on top of the
|
|
32
|
+
panel it came from — and for a phone, where there is no room beside a trigger to anchor
|
|
33
|
+
anything. Whatever opened this is told through `onOpenChange`, so it can get out of the
|
|
34
|
+
way.
|
|
35
|
+
-->
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
hsvToRgb, rgbToHex, hexToRgb, hexToAlpha, alphaToHex, rgbToHsv, startingRgb, startingAlpha,
|
|
39
|
+
} from "./color-maths.js";
|
|
40
|
+
import eyedropperIcon from "../../icons/generated/fluent-eyedropper-regular.marko";
|
|
41
|
+
|
|
42
|
+
class {
|
|
43
|
+
onCreate(input) {
|
|
44
|
+
const [h, s, v] = rgbToHsv(...startingRgb(input.value));
|
|
45
|
+
this.state = {
|
|
46
|
+
open: false,
|
|
47
|
+
hue: h,
|
|
48
|
+
sat: s,
|
|
49
|
+
bri: v,
|
|
50
|
+
opacity: startingAlpha(input.value),
|
|
51
|
+
hexInput: "",
|
|
52
|
+
};
|
|
53
|
+
this.lastValue = input.value;
|
|
54
|
+
// `this.input` is not set during onCreate — Marko passes it in. See
|
|
55
|
+
// .claude/component-standard.md §3.
|
|
56
|
+
this.syncHexInput(input);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onInput(input) {
|
|
60
|
+
// Follows the caller when the caller changes it, and not otherwise.
|
|
61
|
+
if (input.value === this.lastValue) return;
|
|
62
|
+
this.lastValue = input.value;
|
|
63
|
+
const [h, s, v] = rgbToHsv(...startingRgb(input.value));
|
|
64
|
+
this.state.hue = h;
|
|
65
|
+
this.state.sat = s;
|
|
66
|
+
this.state.bri = v;
|
|
67
|
+
this.state.opacity = startingAlpha(input.value);
|
|
68
|
+
this.syncHexInput();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get currentHex() {
|
|
72
|
+
return rgbToHex(...hsvToRgb(this.state.hue, this.state.sat, this.state.bri));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get rgb() {
|
|
76
|
+
return hsvToRgb(this.state.hue, this.state.sat, this.state.bri);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
syncHexInput(input = this.input) {
|
|
80
|
+
const base = this.currentHex.replace("#", "");
|
|
81
|
+
this.state.hexInput = input.disableAlpha ? base : base + alphaToHex(this.state.opacity);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setHsv(h, s, v) {
|
|
85
|
+
this.state.hue = h;
|
|
86
|
+
this.state.sat = s;
|
|
87
|
+
this.state.bri = v;
|
|
88
|
+
this.syncHexInput();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setOpacity(percent) {
|
|
92
|
+
this.state.opacity = percent;
|
|
93
|
+
this.syncHexInput();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
toggle() {
|
|
97
|
+
if (this.input.disabled) return;
|
|
98
|
+
this.state.open = !this.state.open;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The backdrop dismisses, the panel does not: a drag that starts on a slider and ends
|
|
102
|
+
// outside it is still a drag. Escape is listened for on the document because the
|
|
103
|
+
// backdrop only receives key events while something inside it holds focus.
|
|
104
|
+
onMount() {
|
|
105
|
+
this.escape = (event) => this.closeOnEscape(event);
|
|
106
|
+
document.addEventListener("keydown", this.escape);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onDestroy() {
|
|
110
|
+
document.removeEventListener("keydown", this.escape);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
closeFromBackdrop(event, element) {
|
|
114
|
+
if (event.target !== element) return;
|
|
115
|
+
this.setOpen(false);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
closeOnEscape(event) {
|
|
119
|
+
if (event.key !== "Escape") return;
|
|
120
|
+
if (!this.input.overlay || !(this.input.open ?? this.state.open)) return;
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
this.setOpen(false);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setOpen(next) {
|
|
126
|
+
if (this.input.disabled) return;
|
|
127
|
+
if (this.input.open === undefined) this.state.open = next;
|
|
128
|
+
this.input.onOpenChange?.(next);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A drag that keeps following the pointer after it leaves the control. */
|
|
132
|
+
track(element, read, event) {
|
|
133
|
+
if (!element) return;
|
|
134
|
+
const apply = (moveEvent) => {
|
|
135
|
+
const rect = element.getBoundingClientRect();
|
|
136
|
+
const x = Math.max(0, Math.min(1, (moveEvent.clientX - rect.left) / rect.width));
|
|
137
|
+
const y = Math.max(0, Math.min(1, (moveEvent.clientY - rect.top) / rect.height));
|
|
138
|
+
read(x, y);
|
|
139
|
+
};
|
|
140
|
+
apply(event);
|
|
141
|
+
const move = (moveEvent) => apply(moveEvent);
|
|
142
|
+
const up = () => {
|
|
143
|
+
window.removeEventListener("mousemove", move);
|
|
144
|
+
window.removeEventListener("mouseup", up);
|
|
145
|
+
};
|
|
146
|
+
window.addEventListener("mousemove", move);
|
|
147
|
+
window.addEventListener("mouseup", up);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dragCanvas(event) {
|
|
151
|
+
this.track(this.getEl("canvas"), (x, y) => {
|
|
152
|
+
this.setHsv(this.state.hue, Math.round(x * 100), Math.round((1 - y) * 100));
|
|
153
|
+
}, event);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
dragHue(event) {
|
|
157
|
+
this.track(this.getEl("hue"), (x) => {
|
|
158
|
+
this.setHsv(Math.round(x * 360), this.state.sat, this.state.bri);
|
|
159
|
+
}, event);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
dragOpacity(event) {
|
|
163
|
+
this.track(this.getEl("opacity"), (x) => this.setOpacity(Math.round(x * 100)), event);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Arrow keys on the canvas and the sliders.
|
|
168
|
+
*
|
|
169
|
+
* KDLS binds mousedown and nothing else, so a picker whose entire purpose is choosing a
|
|
170
|
+
* precise value cannot be operated without a mouse.
|
|
171
|
+
*/
|
|
172
|
+
nudge(which, event) {
|
|
173
|
+
const step = event.shiftKey ? 10 : 1;
|
|
174
|
+
const keys = { ArrowLeft: -1, ArrowRight: 1, ArrowDown: -1, ArrowUp: 1 };
|
|
175
|
+
const direction = keys[event.key];
|
|
176
|
+
if (direction === undefined) return;
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
|
|
179
|
+
const horizontal = event.key === "ArrowLeft" || event.key === "ArrowRight";
|
|
180
|
+
const clamp = (n, max) => Math.max(0, Math.min(max, n));
|
|
181
|
+
|
|
182
|
+
if (which === "hue") {
|
|
183
|
+
this.setHsv(clamp(this.state.hue + direction * step, 360), this.state.sat, this.state.bri);
|
|
184
|
+
} else if (which === "opacity") {
|
|
185
|
+
this.setOpacity(clamp(this.state.opacity + direction * step, 100));
|
|
186
|
+
} else if (horizontal) {
|
|
187
|
+
this.setHsv(this.state.hue, clamp(this.state.sat + direction * step, 100), this.state.bri);
|
|
188
|
+
} else {
|
|
189
|
+
this.setHsv(this.state.hue, this.state.sat, clamp(this.state.bri + direction * step, 100));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
setChannel(channel, raw) {
|
|
194
|
+
const value = Math.max(0, Math.min(255, Number(raw) || 0));
|
|
195
|
+
const [r, g, b] = this.rgb;
|
|
196
|
+
const next = { r: [value, g, b], g: [r, value, b], b: [r, g, value] }[channel];
|
|
197
|
+
this.setHsv(...rgbToHsv(...next));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setAlphaChannel(raw) {
|
|
201
|
+
const value = Math.max(0, Math.min(255, Number(raw) || 0));
|
|
202
|
+
this.setOpacity(Math.round((value / 255) * 100));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
setHex(raw) {
|
|
206
|
+
this.state.hexInput = String(raw).toUpperCase();
|
|
207
|
+
const rgb = hexToRgb(raw);
|
|
208
|
+
if (rgb) this.setHsv(...rgbToHsv(...rgb));
|
|
209
|
+
if (this.input.disableAlpha) return;
|
|
210
|
+
const alpha = hexToAlpha(raw);
|
|
211
|
+
if (alpha !== null) this.state.opacity = alpha;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async pickFromScreen() {
|
|
215
|
+
if (typeof window === "undefined" || !("EyeDropper" in window)) return;
|
|
216
|
+
try {
|
|
217
|
+
const result = await new window.EyeDropper().open();
|
|
218
|
+
const rgb = hexToRgb(result.sRGBHex);
|
|
219
|
+
if (rgb) this.setHsv(...rgbToHsv(...rgb));
|
|
220
|
+
} catch {
|
|
221
|
+
// The visitor pressed Escape. Nothing to do, and nothing to report.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
apply() {
|
|
226
|
+
const hex = this.input.disableAlpha
|
|
227
|
+
? this.currentHex
|
|
228
|
+
: this.currentHex + alphaToHex(this.state.opacity);
|
|
229
|
+
this.input.onChange?.(hex);
|
|
230
|
+
this.state.open = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
$ {
|
|
235
|
+
const {
|
|
236
|
+
label,
|
|
237
|
+
width = "hug",
|
|
238
|
+
zIndex,
|
|
239
|
+
disableAlpha = false,
|
|
240
|
+
disabled = false,
|
|
241
|
+
applyLabel = "Aplicar",
|
|
242
|
+
overlay = false,
|
|
243
|
+
open: openInput,
|
|
244
|
+
showTrigger = true,
|
|
245
|
+
class: extraClass = "",
|
|
246
|
+
} = input;
|
|
247
|
+
|
|
248
|
+
if (!label) throw new Error("sds-color-picker needs a `label`.");
|
|
249
|
+
if (width !== "hug" && width !== "fill") {
|
|
250
|
+
throw new Error(`Unknown color picker width: "${width}". Allowed: hug, fill`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const [r, g, b] = component.rgb;
|
|
254
|
+
const hex = component.currentHex;
|
|
255
|
+
// Controlled when the caller passes `open`. An overlay picker usually has to be: it is
|
|
256
|
+
// opened from somewhere that then gets out of the way, and a picker that owns its own
|
|
257
|
+
// open state dies with whatever it was rendered inside.
|
|
258
|
+
const open = openInput !== undefined ? openInput : state.open;
|
|
259
|
+
|
|
260
|
+
const triggerClass = [
|
|
261
|
+
"sds-colorpicker__trigger",
|
|
262
|
+
open ? "sds-colorpicker__trigger--open" : "",
|
|
263
|
+
disabled ? "sds-colorpicker__trigger--disabled" : "",
|
|
264
|
+
].filter(Boolean).join(" ");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
<div class=[
|
|
268
|
+
"sds-colorpicker",
|
|
269
|
+
showTrigger ? "" : "sds-colorpicker--headless",
|
|
270
|
+
width === "fill" ? "sds-colorpicker--fill" : "",
|
|
271
|
+
extraClass,
|
|
272
|
+
].filter(Boolean).join(" ")>
|
|
273
|
+
<if(showTrigger)>
|
|
274
|
+
<sds-floating
|
|
275
|
+
open=open
|
|
276
|
+
onOpenChange=(next) => component.setOpen(next)
|
|
277
|
+
placement="bottomLeft"
|
|
278
|
+
zIndex=zIndex
|
|
279
|
+
>
|
|
280
|
+
<@content>
|
|
281
|
+
<if(!overlay)>
|
|
282
|
+
<div class="sds-colorpicker__panel">
|
|
283
|
+
<div
|
|
284
|
+
key="canvas"
|
|
285
|
+
class="sds-colorpicker__canvas"
|
|
286
|
+
style=`--cp-hue:${state.hue}`
|
|
287
|
+
role="application"
|
|
288
|
+
aria-label=`Saturação e brilho de ${label}`
|
|
289
|
+
tabindex=(disabled ? -1 : 0)
|
|
290
|
+
on-mousedown("dragCanvas")
|
|
291
|
+
on-keydown("nudge", "canvas")
|
|
292
|
+
>
|
|
293
|
+
<div class="sds-colorpicker__handle" style=`left:${state.sat}%;top:${100 - state.bri}%`/>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div class="sds-colorpicker__slider-row">
|
|
297
|
+
<sds-icon-button size="md" aria-label="Selecionar cor da tela"
|
|
298
|
+
onClick=() => component.pickFromScreen()>
|
|
299
|
+
<@icon><${eyedropperIcon}/></@icon>
|
|
300
|
+
</sds-icon-button>
|
|
301
|
+
|
|
302
|
+
<div class="sds-colorpicker__sliders">
|
|
303
|
+
<div
|
|
304
|
+
key="hue"
|
|
305
|
+
class="sds-colorpicker__slider sds-colorpicker__slider--hue"
|
|
306
|
+
role="slider"
|
|
307
|
+
aria-label="Matiz"
|
|
308
|
+
aria-valuemin="0"
|
|
309
|
+
aria-valuemax="360"
|
|
310
|
+
aria-valuenow=String(state.hue)
|
|
311
|
+
tabindex="0"
|
|
312
|
+
on-mousedown("dragHue")
|
|
313
|
+
on-keydown("nudge", "hue")
|
|
314
|
+
>
|
|
315
|
+
<div class="sds-colorpicker__slider-thumb" style=`left:${(state.hue / 360) * 100}%`/>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<if(!disableAlpha)>
|
|
319
|
+
<div
|
|
320
|
+
key="opacity"
|
|
321
|
+
class="sds-colorpicker__slider sds-colorpicker__slider--opacity"
|
|
322
|
+
style=`--cp-hex:${hex}`
|
|
323
|
+
role="slider"
|
|
324
|
+
aria-label="Opacidade"
|
|
325
|
+
aria-valuemin="0"
|
|
326
|
+
aria-valuemax="100"
|
|
327
|
+
aria-valuenow=String(state.opacity)
|
|
328
|
+
aria-valuetext=`${state.opacity}%`
|
|
329
|
+
tabindex="0"
|
|
330
|
+
on-mousedown("dragOpacity")
|
|
331
|
+
on-keydown("nudge", "opacity")
|
|
332
|
+
>
|
|
333
|
+
<div class="sds-colorpicker__slider-thumb" style=`left:${state.opacity}%`/>
|
|
334
|
+
</div>
|
|
335
|
+
</if>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div class=[
|
|
340
|
+
"sds-colorpicker__input-group",
|
|
341
|
+
!disableAlpha ? "sds-colorpicker__input-group--rgba" : "",
|
|
342
|
+
].filter(Boolean).join(" ")>
|
|
343
|
+
<sds-input label="R" value=String(r) onChange=(event, value) => component.setChannel("r", value)/>
|
|
344
|
+
<sds-input label="G" value=String(g) onChange=(event, value) => component.setChannel("g", value)/>
|
|
345
|
+
<sds-input label="B" value=String(b) onChange=(event, value) => component.setChannel("b", value)/>
|
|
346
|
+
<if(!disableAlpha)>
|
|
347
|
+
<sds-input
|
|
348
|
+
label="A"
|
|
349
|
+
value=String(Math.round((state.opacity / 100) * 255))
|
|
350
|
+
onChange=(event, value) => component.setAlphaChannel(value)
|
|
351
|
+
/>
|
|
352
|
+
</if>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<sds-input label="HEX" value=state.hexInput
|
|
356
|
+
onChange=(event, value) => component.setHex(value)/>
|
|
357
|
+
|
|
358
|
+
<sds-button variant="primary" size="md" label=applyLabel
|
|
359
|
+
class="sds-colorpicker__apply" onClick=() => component.apply()/>
|
|
360
|
+
</div>
|
|
361
|
+
</if>
|
|
362
|
+
</@content>
|
|
363
|
+
|
|
364
|
+
<!-- A real button. KDLS uses a div with role="button" and a tabindex, which takes focus
|
|
365
|
+
but answers neither Enter nor Space — so the picker cannot be opened by keyboard. -->
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
class=triggerClass
|
|
369
|
+
disabled=(disabled || null)
|
|
370
|
+
aria-haspopup="dialog"
|
|
371
|
+
aria-expanded=(open ? "true" : "false")
|
|
372
|
+
on-click("toggle")
|
|
373
|
+
>
|
|
374
|
+
<span class="sds-colorpicker__swatch" style=`background:${hex}` aria-hidden="true"/>
|
|
375
|
+
<span class="sds-colorpicker__field">
|
|
376
|
+
<sds-text as="span" variant="labelRegular">${label}</sds-text>
|
|
377
|
+
<span class="sds-colorpicker__hex">#${state.hexInput}</span>
|
|
378
|
+
</span>
|
|
379
|
+
</button>
|
|
380
|
+
</sds-floating>
|
|
381
|
+
</if>
|
|
382
|
+
|
|
383
|
+
<!-- Centred, over everything, when `overlay` is on. Escape and the backdrop both close
|
|
384
|
+
it; the panel itself does not, so a drag that ends outside the panel is still a
|
|
385
|
+
drag rather than a dismissal. -->
|
|
386
|
+
<if(overlay && open)>
|
|
387
|
+
<div
|
|
388
|
+
class="sds-colorpicker__backdrop"
|
|
389
|
+
style=(zIndex !== undefined ? `z-index:${zIndex}` : null)
|
|
390
|
+
on-click("closeFromBackdrop")
|
|
391
|
+
on-keydown("closeOnEscape")
|
|
392
|
+
>
|
|
393
|
+
<div class="sds-colorpicker__panel">
|
|
394
|
+
<div
|
|
395
|
+
key="canvas"
|
|
396
|
+
class="sds-colorpicker__canvas"
|
|
397
|
+
style=`--cp-hue:${state.hue}`
|
|
398
|
+
role="application"
|
|
399
|
+
aria-label=`Saturação e brilho de ${label}`
|
|
400
|
+
tabindex=(disabled ? -1 : 0)
|
|
401
|
+
on-mousedown("dragCanvas")
|
|
402
|
+
on-keydown("nudge", "canvas")
|
|
403
|
+
>
|
|
404
|
+
<div class="sds-colorpicker__handle" style=`left:${state.sat}%;top:${100 - state.bri}%`/>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<div class="sds-colorpicker__slider-row">
|
|
408
|
+
<sds-icon-button size="md" aria-label="Selecionar cor da tela"
|
|
409
|
+
onClick=() => component.pickFromScreen()>
|
|
410
|
+
<@icon><${eyedropperIcon}/></@icon>
|
|
411
|
+
</sds-icon-button>
|
|
412
|
+
|
|
413
|
+
<div class="sds-colorpicker__sliders">
|
|
414
|
+
<div
|
|
415
|
+
key="hue"
|
|
416
|
+
class="sds-colorpicker__slider sds-colorpicker__slider--hue"
|
|
417
|
+
role="slider"
|
|
418
|
+
aria-label="Matiz"
|
|
419
|
+
aria-valuemin="0"
|
|
420
|
+
aria-valuemax="360"
|
|
421
|
+
aria-valuenow=String(state.hue)
|
|
422
|
+
tabindex="0"
|
|
423
|
+
on-mousedown("dragHue")
|
|
424
|
+
on-keydown("nudge", "hue")
|
|
425
|
+
>
|
|
426
|
+
<div class="sds-colorpicker__slider-thumb" style=`left:${(state.hue / 360) * 100}%`/>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<if(!disableAlpha)>
|
|
430
|
+
<div
|
|
431
|
+
key="opacity"
|
|
432
|
+
class="sds-colorpicker__slider sds-colorpicker__slider--opacity"
|
|
433
|
+
style=`--cp-hex:${hex}`
|
|
434
|
+
role="slider"
|
|
435
|
+
aria-label="Opacidade"
|
|
436
|
+
aria-valuemin="0"
|
|
437
|
+
aria-valuemax="100"
|
|
438
|
+
aria-valuenow=String(state.opacity)
|
|
439
|
+
aria-valuetext=`${state.opacity}%`
|
|
440
|
+
tabindex="0"
|
|
441
|
+
on-mousedown("dragOpacity")
|
|
442
|
+
on-keydown("nudge", "opacity")
|
|
443
|
+
>
|
|
444
|
+
<div class="sds-colorpicker__slider-thumb" style=`left:${state.opacity}%`/>
|
|
445
|
+
</div>
|
|
446
|
+
</if>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<div class=[
|
|
451
|
+
"sds-colorpicker__input-group",
|
|
452
|
+
!disableAlpha ? "sds-colorpicker__input-group--rgba" : "",
|
|
453
|
+
].filter(Boolean).join(" ")>
|
|
454
|
+
<sds-input label="R" value=String(r) onChange=(event, value) => component.setChannel("r", value)/>
|
|
455
|
+
<sds-input label="G" value=String(g) onChange=(event, value) => component.setChannel("g", value)/>
|
|
456
|
+
<sds-input label="B" value=String(b) onChange=(event, value) => component.setChannel("b", value)/>
|
|
457
|
+
<if(!disableAlpha)>
|
|
458
|
+
<sds-input
|
|
459
|
+
label="A"
|
|
460
|
+
value=String(Math.round((state.opacity / 100) * 255))
|
|
461
|
+
onChange=(event, value) => component.setAlphaChannel(value)
|
|
462
|
+
/>
|
|
463
|
+
</if>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<sds-input label="HEX" value=state.hexInput
|
|
467
|
+
onChange=(event, value) => component.setHex(value)/>
|
|
468
|
+
|
|
469
|
+
<sds-button variant="primary" size="md" label=applyLabel
|
|
470
|
+
class="sds-colorpicker__apply" onClick=() => component.apply()/>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</if>
|
|
474
|
+
</div>
|