@remotion/studio 4.0.463 → 4.0.465

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.
Files changed (94) hide show
  1. package/dist/components/CanvasOrLoading.js +1 -1
  2. package/dist/components/ColorPicker/AlphaSlider.d.ts +9 -0
  3. package/dist/components/ColorPicker/AlphaSlider.js +88 -0
  4. package/dist/components/ColorPicker/ColorPicker.d.ts +18 -0
  5. package/dist/components/ColorPicker/ColorPicker.js +176 -0
  6. package/dist/components/ColorPicker/ColorPickerPopup.d.ts +8 -0
  7. package/dist/components/ColorPicker/ColorPickerPopup.js +276 -0
  8. package/dist/components/ColorPicker/HueSlider.d.ts +6 -0
  9. package/dist/components/ColorPicker/HueSlider.js +68 -0
  10. package/dist/components/ColorPicker/SaturationValueArea.d.ts +14 -0
  11. package/dist/components/ColorPicker/SaturationValueArea.js +93 -0
  12. package/dist/components/ColorPicker/checker.d.ts +4 -0
  13. package/dist/components/ColorPicker/checker.js +9 -0
  14. package/dist/components/ExpandedTracksProvider.d.ts +2 -0
  15. package/dist/components/ExpandedTracksProvider.js +18 -1
  16. package/dist/components/NewComposition/CodemodFooter.d.ts +1 -0
  17. package/dist/components/NewComposition/CodemodFooter.js +35 -23
  18. package/dist/components/NewComposition/DeleteComposition.js +3 -1
  19. package/dist/components/NewComposition/DiffPreview.js +1 -1
  20. package/dist/components/NewComposition/DuplicateComposition.js +3 -1
  21. package/dist/components/NewComposition/RenameComposition.js +4 -2
  22. package/dist/components/RenderModal/SchemaEditor/ZodColorEditor.js +8 -47
  23. package/dist/components/RenderQueue/actions.d.ts +2 -1
  24. package/dist/components/RenderQueue/actions.js +2 -1
  25. package/dist/components/Timeline/Padder.js +2 -2
  26. package/dist/components/Timeline/SubscribeToNodePaths.d.ts +1 -1
  27. package/dist/components/Timeline/SubscribeToNodePaths.js +9 -4
  28. package/dist/components/Timeline/Timeline.js +4 -7
  29. package/dist/components/Timeline/TimelineColorField.js +18 -156
  30. package/dist/components/Timeline/TimelineEffectFieldRow.d.ts +1 -2
  31. package/dist/components/Timeline/TimelineEffectFieldRow.js +22 -31
  32. package/dist/components/Timeline/TimelineEffectGroupRow.d.ts +1 -2
  33. package/dist/components/Timeline/TimelineEffectGroupRow.js +67 -12
  34. package/dist/components/Timeline/TimelineExpandedRow.js +15 -30
  35. package/dist/components/Timeline/TimelineFieldRow.d.ts +1 -2
  36. package/dist/components/Timeline/TimelineFieldRow.js +19 -16
  37. package/dist/components/Timeline/TimelineImageInfo.js +5 -17
  38. package/dist/components/Timeline/TimelineListItem.d.ts +0 -1
  39. package/dist/components/Timeline/TimelineListItem.js +12 -14
  40. package/dist/components/Timeline/TimelineRowChrome.d.ts +8 -0
  41. package/dist/components/Timeline/TimelineRowChrome.js +21 -0
  42. package/dist/components/Timeline/TimelineTimeIndicators.js +4 -11
  43. package/dist/components/Timeline/TimelineTracks.js +0 -4
  44. package/dist/components/Timeline/TimelineVideoInfo.js +25 -8
  45. package/dist/components/Timeline/get-timeline-video-info-widths.d.ts +9 -0
  46. package/dist/components/Timeline/get-timeline-video-info-widths.js +11 -0
  47. package/dist/components/Timeline/save-effect-prop.d.ts +2 -1
  48. package/dist/components/Timeline/save-effect-prop.js +2 -13
  49. package/dist/components/Timeline/save-prop-queue.d.ts +1 -2
  50. package/dist/components/Timeline/save-prop-queue.js +3 -12
  51. package/dist/components/Timeline/save-sequence-prop.d.ts +2 -1
  52. package/dist/components/Timeline/save-sequence-prop.js +2 -11
  53. package/dist/components/Timeline/should-show-track-in-timeline.d.ts +2 -0
  54. package/dist/components/Timeline/should-show-track-in-timeline.js +23 -0
  55. package/dist/components/Timeline/timeline-field-row-layout.d.ts +2 -0
  56. package/dist/components/Timeline/timeline-field-row-layout.js +14 -0
  57. package/dist/components/Timeline/timeline-indent.d.ts +1 -0
  58. package/dist/components/Timeline/timeline-indent.js +4 -0
  59. package/dist/components/Timeline/timeline-row-layout.d.ts +11 -0
  60. package/dist/components/Timeline/timeline-row-layout.js +27 -0
  61. package/dist/components/Timeline/use-resolved-stack-react-to-change.d.ts +2 -0
  62. package/dist/components/Timeline/use-resolved-stack-react-to-change.js +59 -0
  63. package/dist/components/Timeline/use-resolved-stack.d.ts +1 -0
  64. package/dist/components/Timeline/use-resolved-stack.js +10 -1
  65. package/dist/components/Timeline/use-sequence-props-subscription.js +27 -1
  66. package/dist/error-overlay/remotion-overlay/ShortcutHint.js +5 -3
  67. package/dist/esm/chunk-6jf1natv.js +25 -0
  68. package/dist/esm/{chunk-b0m62frw.js → chunk-pqk2qd0d.js} +4971 -4013
  69. package/dist/esm/index.mjs +16 -0
  70. package/dist/esm/internals.mjs +4984 -4011
  71. package/dist/esm/previewEntry.mjs +4995 -4023
  72. package/dist/esm/renderEntry.mjs +4 -3
  73. package/dist/helpers/client-id.d.ts +2 -9
  74. package/dist/helpers/client-id.js +15 -40
  75. package/dist/helpers/color-conversion.d.ts +36 -0
  76. package/dist/helpers/color-conversion.js +121 -0
  77. package/dist/helpers/inject-css.js +4 -7
  78. package/dist/helpers/migrate-expanded-tracks-for-subscription-key.d.ts +3 -0
  79. package/dist/helpers/migrate-expanded-tracks-for-subscription-key.js +26 -0
  80. package/dist/helpers/preview-server-events.d.ts +15 -0
  81. package/dist/helpers/preview-server-events.js +81 -0
  82. package/dist/helpers/resolved-stack-to-symbolicated.d.ts +3 -0
  83. package/dist/helpers/resolved-stack-to-symbolicated.js +16 -0
  84. package/dist/helpers/timeline-layout.d.ts +0 -1
  85. package/dist/helpers/timeline-layout.js +29 -25
  86. package/dist/hot-middleware-client/client.js +10 -16
  87. package/package.json +10 -10
  88. package/dist/components/NewComposition/RemInputTypeColor.d.ts +0 -8
  89. package/dist/components/NewComposition/RemInputTypeColor.js +0 -53
  90. package/dist/components/Timeline/is-collapsed.d.ts +0 -2
  91. package/dist/components/Timeline/is-collapsed.js +0 -10
  92. package/dist/esm/chunk-5gtx3pza.js +0 -9
  93. package/dist/helpers/color-math.d.ts +0 -1
  94. package/dist/helpers/color-math.js +0 -13
@@ -81,7 +81,7 @@ const loaderContainer = {
81
81
  overflowY: 'auto',
82
82
  };
83
83
  const ErrorLoading = ({ error, calculateMetadataContext }) => {
84
- return (jsx_runtime_1.jsx("div", { style: loaderContainer, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: jsx_runtime_1.jsx(ErrorLoader_1.ErrorLoader, { canHaveDismissButton: false, keyboardShortcuts: false, error: error, onRetry: () => {
84
+ return (jsx_runtime_1.jsx("div", { style: loaderContainer, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: jsx_runtime_1.jsx(ErrorLoader_1.ErrorLoader, { canHaveDismissButton: false, keyboardShortcuts: true, error: error, onRetry: () => {
85
85
  var _a;
86
86
  return (_a = remotion_1.Internals.resolveCompositionsRef.current) === null || _a === void 0 ? void 0 : _a.reloadCurrentlySelectedComposition();
87
87
  }, calculateMetadata: calculateMetadataContext }, error.stack) }));
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export declare const AlphaSlider: React.FC<{
3
+ readonly hue: number;
4
+ readonly saturation: number;
5
+ readonly value: number;
6
+ readonly alpha: number;
7
+ readonly onChange: (next: number) => void;
8
+ readonly onChangeComplete: (next: number) => void;
9
+ }>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AlphaSlider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const color_conversion_1 = require("../../helpers/color-conversion");
7
+ const checker_1 = require("./checker");
8
+ const SLIDER_HEIGHT = 12;
9
+ const HANDLE_WIDTH = 8;
10
+ const wrapperStyle = {
11
+ position: 'relative',
12
+ height: SLIDER_HEIGHT,
13
+ width: '100%',
14
+ borderRadius: 3,
15
+ overflow: 'hidden',
16
+ cursor: 'ew-resize',
17
+ backgroundColor: checker_1.CHECKER_BACKGROUND_COLOR,
18
+ backgroundImage: checker_1.CHECKER_BACKGROUND_IMAGE,
19
+ backgroundSize: checker_1.CHECKER_BACKGROUND_SIZE,
20
+ backgroundPosition: checker_1.CHECKER_BACKGROUND_POSITION,
21
+ touchAction: 'none',
22
+ };
23
+ const innerStyle = {
24
+ position: 'absolute',
25
+ inset: 0,
26
+ };
27
+ const AlphaSlider = ({ hue, saturation, value, alpha, onChange, onChangeComplete }) => {
28
+ const ref = (0, react_1.useRef)(null);
29
+ const opaqueColor = (0, react_1.useMemo)(() => {
30
+ const { r, g, b } = (0, color_conversion_1.hsvToRgb)({ h: hue, s: saturation, v: value });
31
+ return `rgb(${r}, ${g}, ${b})`;
32
+ }, [hue, saturation, value]);
33
+ const gradientStyle = (0, react_1.useMemo)(() => {
34
+ return {
35
+ ...innerStyle,
36
+ background: `linear-gradient(to right, rgba(0, 0, 0, 0), ${opaqueColor})`,
37
+ };
38
+ }, [opaqueColor]);
39
+ const updateFromEvent = (0, react_1.useCallback)((clientX, isFinal) => {
40
+ const { current } = ref;
41
+ if (!current) {
42
+ return;
43
+ }
44
+ const rect = current.getBoundingClientRect();
45
+ const next = (0, color_conversion_1.clamp)((clientX - rect.left) / rect.width, 0, 1);
46
+ if (isFinal) {
47
+ onChangeComplete(next);
48
+ }
49
+ else {
50
+ onChange(next);
51
+ }
52
+ }, [onChange, onChangeComplete]);
53
+ const onPointerDown = (0, react_1.useCallback)((e) => {
54
+ if (e.button !== 0) {
55
+ return;
56
+ }
57
+ e.preventDefault();
58
+ updateFromEvent(e.clientX, false);
59
+ const onMove = (ev) => {
60
+ updateFromEvent(ev.clientX, false);
61
+ };
62
+ const onUp = (ev) => {
63
+ window.removeEventListener('pointermove', onMove);
64
+ window.removeEventListener('pointerup', onUp);
65
+ updateFromEvent(ev.clientX, true);
66
+ };
67
+ window.addEventListener('pointermove', onMove);
68
+ window.addEventListener('pointerup', onUp);
69
+ }, [updateFromEvent]);
70
+ const handleStyle = (0, react_1.useMemo)(() => {
71
+ return {
72
+ position: 'absolute',
73
+ top: -2,
74
+ left: `${alpha * 100}%`,
75
+ marginLeft: -HANDLE_WIDTH / 2,
76
+ width: HANDLE_WIDTH,
77
+ height: SLIDER_HEIGHT + 4,
78
+ borderRadius: 2,
79
+ border: '2px solid #fff',
80
+ boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.6)',
81
+ pointerEvents: 'none',
82
+ };
83
+ }, [alpha]);
84
+ return (jsx_runtime_1.jsxs("div", { ref: ref, style: wrapperStyle, onPointerDown: onPointerDown, children: [
85
+ jsx_runtime_1.jsx("div", { style: gradientStyle }), jsx_runtime_1.jsx("div", { style: handleStyle })
86
+ ] }));
87
+ };
88
+ exports.AlphaSlider = AlphaSlider;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { RemInputStatus } from '../NewComposition/RemInput';
3
+ type Props = {
4
+ readonly value: string;
5
+ readonly onChange: (next: string) => void;
6
+ readonly onChangeComplete: (next: string) => void;
7
+ readonly status: RemInputStatus;
8
+ readonly disabled?: boolean;
9
+ readonly width?: number;
10
+ readonly height?: number;
11
+ readonly borderRadius?: number;
12
+ readonly title?: string;
13
+ readonly name?: string;
14
+ readonly className?: string;
15
+ readonly style?: React.CSSProperties;
16
+ };
17
+ export declare const ColorPicker: React.FC<Props>;
18
+ export {};
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ColorPicker = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const player_1 = require("@remotion/player");
9
+ const react_1 = require("react");
10
+ const react_dom_1 = __importDefault(require("react-dom"));
11
+ const colors_1 = require("../../helpers/colors");
12
+ const z_index_1 = require("../../state/z-index");
13
+ const is_menu_item_1 = require("../Menu/is-menu-item");
14
+ const portals_1 = require("../Menu/portals");
15
+ const styles_1 = require("../Menu/styles");
16
+ const checker_1 = require("./checker");
17
+ const ColorPickerPopup_1 = require("./ColorPickerPopup");
18
+ // Class name used to opt the swatch button out of the global
19
+ // `button:focus` inset box-shadow defined in inject-css.ts.
20
+ const SWATCH_CLASSNAME = '__remotion_color_swatch';
21
+ const getSwatchBorderColor = ({ status, isFocused, isHovered, }) => {
22
+ if (status === 'warning') {
23
+ return colors_1.WARNING_COLOR;
24
+ }
25
+ if (status === 'error') {
26
+ return colors_1.FAIL_COLOR;
27
+ }
28
+ return isFocused || isHovered
29
+ ? colors_1.INPUT_BORDER_COLOR_HOVERED
30
+ : colors_1.INPUT_BORDER_COLOR_UNHOVERED;
31
+ };
32
+ const swatchBaseStyle = {
33
+ position: 'relative',
34
+ display: 'inline-block',
35
+ overflow: 'hidden',
36
+ padding: 0,
37
+ margin: 0,
38
+ cursor: 'pointer',
39
+ backgroundColor: checker_1.CHECKER_BACKGROUND_COLOR,
40
+ backgroundImage: checker_1.CHECKER_BACKGROUND_IMAGE,
41
+ backgroundSize: checker_1.CHECKER_BACKGROUND_SIZE,
42
+ backgroundPosition: checker_1.CHECKER_BACKGROUND_POSITION,
43
+ boxSizing: 'border-box',
44
+ };
45
+ const fillStyle = {
46
+ position: 'absolute',
47
+ inset: 0,
48
+ display: 'block',
49
+ };
50
+ const ColorPicker = ({ value, onChange, onChangeComplete, status, disabled, width = 45, height = 25, borderRadius = 3, title, name, className, style: customStyle, }) => {
51
+ const [opened, setOpened] = (0, react_1.useState)(false);
52
+ const [isHovered, setIsHovered] = (0, react_1.useState)(false);
53
+ const [isFocused, setIsFocused] = (0, react_1.useState)(false);
54
+ const triggerRef = (0, react_1.useRef)(null);
55
+ const { tabIndex, currentZIndex } = (0, z_index_1.useZIndex)();
56
+ const size = player_1.PlayerInternals.useElementSize(triggerRef, {
57
+ triggerOnWindowResize: true,
58
+ shouldApplyCssTransforms: true,
59
+ });
60
+ const refresh = size === null || size === void 0 ? void 0 : size.refresh;
61
+ const onHide = (0, react_1.useCallback)(() => {
62
+ setOpened(false);
63
+ }, []);
64
+ const swatchFill = (0, react_1.useMemo)(() => {
65
+ return {
66
+ ...fillStyle,
67
+ backgroundColor: value,
68
+ };
69
+ }, [value]);
70
+ const swatchStyle = (0, react_1.useMemo)(() => {
71
+ return {
72
+ ...swatchBaseStyle,
73
+ width,
74
+ height,
75
+ borderRadius,
76
+ borderColor: getSwatchBorderColor({ status, isFocused, isHovered }),
77
+ cursor: disabled ? 'not-allowed' : 'pointer',
78
+ opacity: disabled ? 0.5 : 1,
79
+ ...(customStyle !== null && customStyle !== void 0 ? customStyle : {}),
80
+ };
81
+ }, [
82
+ borderRadius,
83
+ customStyle,
84
+ disabled,
85
+ height,
86
+ isFocused,
87
+ isHovered,
88
+ status,
89
+ width,
90
+ ]);
91
+ const onMouseEnter = (0, react_1.useCallback)(() => setIsHovered(true), []);
92
+ const onMouseLeave = (0, react_1.useCallback)(() => setIsHovered(false), []);
93
+ const onFocus = (0, react_1.useCallback)(() => setIsFocused(true), []);
94
+ const onBlur = (0, react_1.useCallback)(() => setIsFocused(false), []);
95
+ // Toggle on pointerdown (matches Combobox) so the state flips before the
96
+ // HigherZIndex outside-click detection runs on pointerup. If we toggled in
97
+ // onClick, the popup would close in pointerup and immediately re-open in
98
+ // the click that follows.
99
+ const onTriggerPointerDown = (0, react_1.useCallback)(() => {
100
+ if (disabled) {
101
+ return;
102
+ }
103
+ setOpened((prev) => {
104
+ if (!prev) {
105
+ refresh === null || refresh === void 0 ? void 0 : refresh();
106
+ }
107
+ return !prev;
108
+ });
109
+ }, [disabled, refresh]);
110
+ const onTriggerClick = (0, react_1.useCallback)((e) => {
111
+ if (disabled) {
112
+ return;
113
+ }
114
+ // Keyboard-initiated activations (Enter/Space) don't fire pointerdown,
115
+ // so handle them here. Mouse clicks are already handled by pointerdown
116
+ // above; ignore them.
117
+ const isKeyboardInitiated = e.detail === 0;
118
+ if (!isKeyboardInitiated) {
119
+ return;
120
+ }
121
+ setOpened((prev) => {
122
+ if (!prev) {
123
+ refresh === null || refresh === void 0 ? void 0 : refresh();
124
+ }
125
+ return !prev;
126
+ });
127
+ }, [disabled, refresh]);
128
+ const portalStyle = (0, react_1.useMemo)(() => {
129
+ if (!opened || !size) {
130
+ return null;
131
+ }
132
+ const margin = 6;
133
+ const popupHeight = 250;
134
+ const popupWidth = ColorPickerPopup_1.POPUP_WIDTH;
135
+ const spaceBelow = size.windowSize.height - (size.top + size.height);
136
+ const spaceAbove = size.top;
137
+ const openBelow = spaceBelow >= popupHeight + margin || spaceBelow >= spaceAbove;
138
+ const spaceRight = size.windowSize.width - size.left;
139
+ // Try aligning the popup to the trigger's left edge first; fall back
140
+ // to right-aligning if it would clip the viewport.
141
+ const left = spaceRight >= popupWidth + margin
142
+ ? size.left
143
+ : Math.max(margin, size.windowSize.width - popupWidth - margin);
144
+ if (openBelow) {
145
+ return {
146
+ ...styles_1.menuContainerTowardsBottom,
147
+ top: size.top + size.height + margin,
148
+ left,
149
+ background: 'transparent',
150
+ boxShadow: 'none',
151
+ };
152
+ }
153
+ return {
154
+ ...styles_1.menuContainerTowardsTop,
155
+ bottom: size.windowSize.height - size.top + margin,
156
+ left,
157
+ background: 'transparent',
158
+ boxShadow: 'none',
159
+ };
160
+ }, [opened, size]);
161
+ (0, react_1.useEffect)(() => {
162
+ if (!opened) {
163
+ return;
164
+ }
165
+ const onWindowBlur = () => setOpened(false);
166
+ window.addEventListener('blur', onWindowBlur);
167
+ return () => window.removeEventListener('blur', onWindowBlur);
168
+ }, [opened]);
169
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
170
+ jsx_runtime_1.jsx("button", { ref: triggerRef, type: "button", className: [is_menu_item_1.MENU_INITIATOR_CLASSNAME, SWATCH_CLASSNAME, className]
171
+ .filter(Boolean)
172
+ .join(' '), disabled: disabled, name: name, title: title !== null && title !== void 0 ? title : value, tabIndex: tabIndex, style: swatchStyle, onPointerDown: onTriggerPointerDown, onClick: onTriggerClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, children: jsx_runtime_1.jsx("span", { style: swatchFill }) }), portalStyle
173
+ ? react_dom_1.default.createPortal(jsx_runtime_1.jsx("div", { style: styles_1.fullScreenOverlay, children: jsx_runtime_1.jsx("div", { style: styles_1.outerPortal, className: "css-reset", children: jsx_runtime_1.jsx(z_index_1.HigherZIndex, { onOutsideClick: onHide, onEscape: onHide, children: jsx_runtime_1.jsx("div", { style: portalStyle, children: jsx_runtime_1.jsx(ColorPickerPopup_1.ColorPickerPopup, { value: value, onChange: onChange, onChangeComplete: onChangeComplete }) }) }) }) }), (0, portals_1.getPortal)(currentZIndex))
174
+ : null] }));
175
+ };
176
+ exports.ColorPicker = ColorPicker;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export declare const POPUP_WIDTH = 240;
3
+ export type ChannelKey = 'r' | 'g' | 'b' | 'a-percent';
4
+ export declare const ColorPickerPopup: React.FC<{
5
+ readonly value: string;
6
+ readonly onChange: (next: string) => void;
7
+ readonly onChangeComplete: (next: string) => void;
8
+ }>;
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ColorPickerPopup = exports.POPUP_WIDTH = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const color_conversion_1 = require("../../helpers/color-conversion");
7
+ const colors_1 = require("../../helpers/colors");
8
+ const eyedropper_1 = require("../../icons/eyedropper");
9
+ const z_index_1 = require("../../state/z-index");
10
+ const AlphaSlider_1 = require("./AlphaSlider");
11
+ const checker_1 = require("./checker");
12
+ const HueSlider_1 = require("./HueSlider");
13
+ const SaturationValueArea_1 = require("./SaturationValueArea");
14
+ exports.POPUP_WIDTH = 240;
15
+ const POPUP_PADDING = 12;
16
+ const popupShellStyle = {
17
+ width: exports.POPUP_WIDTH,
18
+ padding: POPUP_PADDING,
19
+ background: '#1f2428',
20
+ border: '1px solid rgba(0, 0, 0, 0.6)',
21
+ borderRadius: 4,
22
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5)',
23
+ display: 'flex',
24
+ flexDirection: 'column',
25
+ gap: 10,
26
+ color: 'white',
27
+ fontSize: 12,
28
+ userSelect: 'none',
29
+ WebkitUserSelect: 'none',
30
+ };
31
+ const sliderRowStyle = {
32
+ display: 'flex',
33
+ alignItems: 'center',
34
+ gap: 8,
35
+ };
36
+ const previewSwatchStyle = {
37
+ position: 'relative',
38
+ width: 28,
39
+ height: 28,
40
+ flex: '0 0 auto',
41
+ borderRadius: 14,
42
+ overflow: 'hidden',
43
+ border: '1px solid rgba(0, 0, 0, 0.6)',
44
+ backgroundColor: checker_1.CHECKER_BACKGROUND_COLOR,
45
+ backgroundImage: checker_1.CHECKER_BACKGROUND_IMAGE,
46
+ backgroundSize: checker_1.CHECKER_BACKGROUND_SIZE,
47
+ backgroundPosition: checker_1.CHECKER_BACKGROUND_POSITION,
48
+ };
49
+ const previewSwatchFillBase = {
50
+ position: 'absolute',
51
+ inset: 0,
52
+ };
53
+ const slidersColumnStyle = {
54
+ display: 'flex',
55
+ flexDirection: 'column',
56
+ gap: 6,
57
+ flex: 1,
58
+ };
59
+ const inputsRowStyle = {
60
+ display: 'flex',
61
+ gap: 6,
62
+ alignItems: 'flex-end',
63
+ };
64
+ const inputColumnStyle = {
65
+ display: 'flex',
66
+ flexDirection: 'column',
67
+ flex: 1,
68
+ minWidth: 0,
69
+ };
70
+ const labelStyle = {
71
+ color: colors_1.LIGHT_TEXT,
72
+ fontSize: 10,
73
+ textTransform: 'uppercase',
74
+ letterSpacing: 0.5,
75
+ marginBottom: 2,
76
+ };
77
+ const baseInputStyle = {
78
+ width: '100%',
79
+ background: colors_1.INPUT_BACKGROUND,
80
+ border: '1px solid rgba(0, 0, 0, 0.6)',
81
+ borderRadius: 3,
82
+ color: 'white',
83
+ padding: '4px 6px',
84
+ fontSize: 12,
85
+ textAlign: 'center',
86
+ boxSizing: 'border-box',
87
+ };
88
+ const eyedropperButtonStyle = {
89
+ background: 'transparent',
90
+ border: '1px solid rgba(0, 0, 0, 0.6)',
91
+ borderRadius: 3,
92
+ padding: 0,
93
+ cursor: 'pointer',
94
+ color: colors_1.LIGHT_TEXT,
95
+ width: 28,
96
+ height: 28,
97
+ display: 'inline-flex',
98
+ alignItems: 'center',
99
+ justifyContent: 'center',
100
+ flex: '0 0 auto',
101
+ };
102
+ const hasEyeDropper = () => typeof window !== 'undefined' && 'EyeDropper' in window;
103
+ const ChannelInput = ({ label, channel, value, min, max, onCommit, }) => {
104
+ const [draft, setDraft] = (0, react_1.useState)(String(Math.round(value)));
105
+ const { tabIndex } = (0, z_index_1.useZIndex)();
106
+ (0, react_1.useEffect)(() => {
107
+ setDraft(String(Math.round(value)));
108
+ }, [value]);
109
+ const onChange = (0, react_1.useCallback)((e) => {
110
+ setDraft(e.target.value);
111
+ }, []);
112
+ const onBlur = (0, react_1.useCallback)(() => {
113
+ const parsed = Number(draft);
114
+ if (Number.isNaN(parsed)) {
115
+ setDraft(String(Math.round(value)));
116
+ return;
117
+ }
118
+ const clamped = Math.max(min, Math.min(max, parsed));
119
+ setDraft(String(Math.round(clamped)));
120
+ onCommit(channel, clamped);
121
+ }, [channel, draft, max, min, onCommit, value]);
122
+ const onKeyDown = (0, react_1.useCallback)((e) => {
123
+ if (e.key === 'Enter') {
124
+ e.target.blur();
125
+ }
126
+ }, []);
127
+ return (jsx_runtime_1.jsxs("label", { style: inputColumnStyle, children: [
128
+ jsx_runtime_1.jsx("span", { style: labelStyle, children: label }), jsx_runtime_1.jsx("input", { type: "text", inputMode: "numeric", value: draft, onChange: onChange, onBlur: onBlur, onKeyDown: onKeyDown, style: baseInputStyle, tabIndex: tabIndex })
129
+ ] }));
130
+ };
131
+ const HexInput = ({ value, onCommit }) => {
132
+ const [draft, setDraft] = (0, react_1.useState)(value);
133
+ const { tabIndex } = (0, z_index_1.useZIndex)();
134
+ (0, react_1.useEffect)(() => {
135
+ setDraft(value);
136
+ }, [value]);
137
+ const onChange = (0, react_1.useCallback)((e) => {
138
+ setDraft(e.target.value);
139
+ }, []);
140
+ const onBlur = (0, react_1.useCallback)(() => {
141
+ try {
142
+ const parsed = (0, color_conversion_1.parseAnyColor)(draft);
143
+ onCommit((0, color_conversion_1.formatRgba)(parsed));
144
+ }
145
+ catch (_a) {
146
+ setDraft(value);
147
+ }
148
+ }, [draft, onCommit, value]);
149
+ const onKeyDown = (0, react_1.useCallback)((e) => {
150
+ if (e.key === 'Enter') {
151
+ e.target.blur();
152
+ }
153
+ }, []);
154
+ const style = (0, react_1.useMemo)(() => {
155
+ return {
156
+ ...baseInputStyle,
157
+ textAlign: 'left',
158
+ };
159
+ }, []);
160
+ return (jsx_runtime_1.jsxs("label", { style: inputColumnStyle, children: [
161
+ jsx_runtime_1.jsx("span", { style: labelStyle, children: "Hex" }), jsx_runtime_1.jsx("input", { type: "text", value: draft, onChange: onChange, onBlur: onBlur, onKeyDown: onKeyDown, style: style, autoComplete: "off", spellCheck: false, tabIndex: tabIndex })
162
+ ] }));
163
+ };
164
+ const ColorPickerPopup = ({ value, onChange, onChangeComplete }) => {
165
+ // useZIndex is intentionally read inside the popup (which is wrapped by
166
+ // HigherZIndex) so child inputs receive the popup's tabIndex rather than
167
+ // the trigger's, which would resolve to -1 once the popup is open.
168
+ const { tabIndex } = (0, z_index_1.useZIndex)();
169
+ // HSV is the source of truth while the picker is open. We seed it from
170
+ // the incoming value but keep our own state so that hue and saturation
171
+ // don't snap back to 0 when the user drags into pure black/white.
172
+ const [hsva, setHsva] = (0, react_1.useState)(() => (0, color_conversion_1.rgbaToHsva)((0, color_conversion_1.parseAnyColor)(value)));
173
+ const lastEmittedRef = (0, react_1.useRef)((0, color_conversion_1.formatRgba)((0, color_conversion_1.parseAnyColor)(value)));
174
+ (0, react_1.useEffect)(() => {
175
+ // Sync from external value changes that didn't originate from us.
176
+ const incoming = (0, color_conversion_1.formatRgba)((0, color_conversion_1.parseAnyColor)(value));
177
+ if (incoming === lastEmittedRef.current) {
178
+ return;
179
+ }
180
+ lastEmittedRef.current = incoming;
181
+ setHsva((0, color_conversion_1.rgbaToHsva)((0, color_conversion_1.parseAnyColor)(value)));
182
+ }, [value]);
183
+ const rgba = (0, react_1.useMemo)(() => (0, color_conversion_1.hsvaToRgba)(hsva), [hsva]);
184
+ const formatted = (0, react_1.useMemo)(() => (0, color_conversion_1.formatRgba)(rgba), [rgba]);
185
+ const emit = (0, react_1.useCallback)((next, mode) => {
186
+ setHsva(next);
187
+ const nextRgba = (0, color_conversion_1.hsvaToRgba)(next);
188
+ const nextFormatted = (0, color_conversion_1.formatRgba)(nextRgba);
189
+ lastEmittedRef.current = nextFormatted;
190
+ if (mode === 'complete') {
191
+ onChangeComplete(nextFormatted);
192
+ }
193
+ else {
194
+ onChange(nextFormatted);
195
+ }
196
+ }, [onChange, onChangeComplete]);
197
+ const onSvChange = (0, react_1.useCallback)((next) => {
198
+ emit({ ...hsva, s: next.s, v: next.v }, 'change');
199
+ }, [emit, hsva]);
200
+ const onSvComplete = (0, react_1.useCallback)((next) => {
201
+ emit({ ...hsva, s: next.s, v: next.v }, 'complete');
202
+ }, [emit, hsva]);
203
+ const onHueChange = (0, react_1.useCallback)((next) => {
204
+ emit({ ...hsva, h: next }, 'change');
205
+ }, [emit, hsva]);
206
+ const onHueComplete = (0, react_1.useCallback)((next) => {
207
+ emit({ ...hsva, h: next }, 'complete');
208
+ }, [emit, hsva]);
209
+ const onAlphaChange = (0, react_1.useCallback)((next) => {
210
+ emit({ ...hsva, a: next }, 'change');
211
+ }, [emit, hsva]);
212
+ const onAlphaComplete = (0, react_1.useCallback)((next) => {
213
+ emit({ ...hsva, a: next }, 'complete');
214
+ }, [emit, hsva]);
215
+ const onChannelCommit = (0, react_1.useCallback)((channel, next) => {
216
+ if (channel === 'a-percent') {
217
+ const clamped = Math.max(0, Math.min(100, next));
218
+ emit({ ...hsva, a: clamped / 100 }, 'complete');
219
+ return;
220
+ }
221
+ const updatedRgba = { ...rgba, [channel]: next };
222
+ const newHsva = (0, color_conversion_1.rgbaToHsva)(updatedRgba);
223
+ // Preserve hue when transitioning through achromatic colors.
224
+ if (newHsva.s === 0) {
225
+ newHsva.h = hsva.h;
226
+ }
227
+ emit(newHsva, 'complete');
228
+ }, [emit, hsva, rgba]);
229
+ const onHexCommit = (0, react_1.useCallback)((next) => {
230
+ const parsed = (0, color_conversion_1.parseAnyColor)(next);
231
+ const newHsva = (0, color_conversion_1.rgbaToHsva)(parsed);
232
+ if (newHsva.s === 0) {
233
+ newHsva.h = hsva.h;
234
+ }
235
+ emit(newHsva, 'complete');
236
+ }, [emit, hsva.h]);
237
+ const onPickWithEyeDropper = (0, react_1.useCallback)(() => {
238
+ const Ctor = window.EyeDropper;
239
+ if (!Ctor) {
240
+ return;
241
+ }
242
+ const dropper = new Ctor();
243
+ dropper
244
+ .open()
245
+ .then((result) => {
246
+ const parsed = (0, color_conversion_1.parseAnyColor)(result.sRGBHex);
247
+ // `EyeDropper` always returns full opacity; preserve the user's
248
+ // previously chosen alpha so opening it doesn't drop transparency.
249
+ parsed.a = rgba.a;
250
+ const newHsva = (0, color_conversion_1.rgbaToHsva)(parsed);
251
+ if (newHsva.s === 0) {
252
+ newHsva.h = hsva.h;
253
+ }
254
+ emit(newHsva, 'complete');
255
+ })
256
+ .catch(() => {
257
+ // Aborted; ignore.
258
+ });
259
+ }, [emit, hsva.h, rgba.a]);
260
+ const previewFill = (0, react_1.useMemo)(() => {
261
+ return {
262
+ ...previewSwatchFillBase,
263
+ backgroundColor: formatted,
264
+ };
265
+ }, [formatted]);
266
+ const showEyeDropper = hasEyeDropper();
267
+ return (jsx_runtime_1.jsxs("div", { style: popupShellStyle, children: [
268
+ jsx_runtime_1.jsx(SaturationValueArea_1.SaturationValueArea, { hue: hsva.h, saturation: hsva.s, value: hsva.v, onChange: onSvChange, onChangeComplete: onSvComplete }), jsx_runtime_1.jsxs("div", { style: sliderRowStyle, children: [showEyeDropper ? (jsx_runtime_1.jsx("button", { type: "button", style: eyedropperButtonStyle, onClick: onPickWithEyeDropper, tabIndex: tabIndex, title: "Pick color from screen", "aria-label": "Pick color from screen", children: jsx_runtime_1.jsx(eyedropper_1.EyedropperIcon, { style: { width: 14, height: 14 }, color: "currentColor" }) })) : null, jsx_runtime_1.jsx("div", { style: previewSwatchStyle, title: formatted, children: jsx_runtime_1.jsx("div", { style: previewFill }) }), jsx_runtime_1.jsxs("div", { style: slidersColumnStyle, children: [
269
+ jsx_runtime_1.jsx(HueSlider_1.HueSlider, { hue: hsva.h, onChange: onHueChange, onChangeComplete: onHueComplete }), jsx_runtime_1.jsx(AlphaSlider_1.AlphaSlider, { hue: hsva.h, saturation: hsva.s, value: hsva.v, alpha: hsva.a, onChange: onAlphaChange, onChangeComplete: onAlphaComplete })
270
+ ] })
271
+ ] }), jsx_runtime_1.jsxs("div", { style: inputsRowStyle, children: [
272
+ jsx_runtime_1.jsx(HexInput, { value: formatted, onCommit: onHexCommit }), jsx_runtime_1.jsx(ChannelInput, { label: "R", channel: "r", value: rgba.r, min: 0, max: 255, onCommit: onChannelCommit }), jsx_runtime_1.jsx(ChannelInput, { label: "G", channel: "g", value: rgba.g, min: 0, max: 255, onCommit: onChannelCommit }), jsx_runtime_1.jsx(ChannelInput, { label: "B", channel: "b", value: rgba.b, min: 0, max: 255, onCommit: onChannelCommit }), jsx_runtime_1.jsx(ChannelInput, { label: "A%", channel: "a-percent", value: Math.round(hsva.a * 100), min: 0, max: 100, onCommit: onChannelCommit })
273
+ ] })
274
+ ] }));
275
+ };
276
+ exports.ColorPickerPopup = ColorPickerPopup;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export declare const HueSlider: React.FC<{
3
+ readonly hue: number;
4
+ readonly onChange: (next: number) => void;
5
+ readonly onChangeComplete: (next: number) => void;
6
+ }>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HueSlider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const color_conversion_1 = require("../../helpers/color-conversion");
7
+ const SLIDER_HEIGHT = 12;
8
+ const HANDLE_WIDTH = 8;
9
+ const containerStyle = {
10
+ position: 'relative',
11
+ height: SLIDER_HEIGHT,
12
+ width: '100%',
13
+ borderRadius: 3,
14
+ cursor: 'ew-resize',
15
+ background: 'linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)',
16
+ touchAction: 'none',
17
+ };
18
+ const HueSlider = ({ hue, onChange, onChangeComplete }) => {
19
+ const ref = (0, react_1.useRef)(null);
20
+ const updateFromEvent = (0, react_1.useCallback)((clientX, isFinal) => {
21
+ const { current } = ref;
22
+ if (!current) {
23
+ return;
24
+ }
25
+ const rect = current.getBoundingClientRect();
26
+ const x = (0, color_conversion_1.clamp)((clientX - rect.left) / rect.width, 0, 1);
27
+ const next = x * 360;
28
+ if (isFinal) {
29
+ onChangeComplete(next);
30
+ }
31
+ else {
32
+ onChange(next);
33
+ }
34
+ }, [onChange, onChangeComplete]);
35
+ const onPointerDown = (0, react_1.useCallback)((e) => {
36
+ if (e.button !== 0) {
37
+ return;
38
+ }
39
+ e.preventDefault();
40
+ updateFromEvent(e.clientX, false);
41
+ const onMove = (ev) => {
42
+ updateFromEvent(ev.clientX, false);
43
+ };
44
+ const onUp = (ev) => {
45
+ window.removeEventListener('pointermove', onMove);
46
+ window.removeEventListener('pointerup', onUp);
47
+ updateFromEvent(ev.clientX, true);
48
+ };
49
+ window.addEventListener('pointermove', onMove);
50
+ window.addEventListener('pointerup', onUp);
51
+ }, [updateFromEvent]);
52
+ const handleStyle = (0, react_1.useMemo)(() => {
53
+ return {
54
+ position: 'absolute',
55
+ top: -2,
56
+ left: `${(hue / 360) * 100}%`,
57
+ marginLeft: -HANDLE_WIDTH / 2,
58
+ width: HANDLE_WIDTH,
59
+ height: SLIDER_HEIGHT + 4,
60
+ borderRadius: 2,
61
+ border: '2px solid #fff',
62
+ boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.6)',
63
+ pointerEvents: 'none',
64
+ };
65
+ }, [hue]);
66
+ return (jsx_runtime_1.jsx("div", { ref: ref, style: containerStyle, onPointerDown: onPointerDown, children: jsx_runtime_1.jsx("div", { style: handleStyle }) }));
67
+ };
68
+ exports.HueSlider = HueSlider;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export declare const SaturationValueArea: React.FC<{
3
+ readonly hue: number;
4
+ readonly saturation: number;
5
+ readonly value: number;
6
+ readonly onChange: (next: {
7
+ s: number;
8
+ v: number;
9
+ }) => void;
10
+ readonly onChangeComplete: (next: {
11
+ s: number;
12
+ v: number;
13
+ }) => void;
14
+ }>;