pxt-core 7.5.8 → 7.5.11

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 (47) hide show
  1. package/built/pxt.js +1004 -4
  2. package/built/pxtblockly.js +439 -49
  3. package/built/pxtblocks.d.ts +34 -0
  4. package/built/pxtblocks.js +439 -49
  5. package/built/pxtlib.d.ts +20 -2
  6. package/built/pxtlib.js +127 -3
  7. package/built/pxtsim.d.ts +222 -0
  8. package/built/pxtsim.js +877 -1
  9. package/built/target.js +1 -1
  10. package/built/web/icons.css +49 -10
  11. package/built/web/main.js +1 -1
  12. package/built/web/pxtapp.js +1 -1
  13. package/built/web/pxtasseteditor.js +1 -1
  14. package/built/web/pxtblockly.js +1 -1
  15. package/built/web/pxtblocks.js +1 -1
  16. package/built/web/pxtembed.js +2 -2
  17. package/built/web/pxtlib.js +1 -1
  18. package/built/web/pxtsim.js +1 -1
  19. package/built/web/pxtworker.js +1 -1
  20. package/built/web/react-common-authcode.css +130 -1
  21. package/built/web/react-common-skillmap.css +1 -1
  22. package/built/web/rtlreact-common-skillmap.css +1 -1
  23. package/built/web/rtlsemantic.css +2 -2
  24. package/built/web/semantic.css +2 -2
  25. package/built/web/skillmap/js/main.e30f6be4.chunk.js +1 -0
  26. package/docfiles/footer.html +1 -1
  27. package/docfiles/script.html +1 -1
  28. package/docfiles/thin-footer.html +1 -1
  29. package/localtypings/pxtarget.d.ts +1 -0
  30. package/package.json +1 -1
  31. package/react-common/components/controls/Button.tsx +10 -4
  32. package/react-common/components/controls/DraggableGraph.tsx +242 -0
  33. package/react-common/components/controls/Dropdown.tsx +121 -0
  34. package/react-common/components/controls/FocusList.tsx +17 -8
  35. package/react-common/components/controls/Input.tsx +13 -3
  36. package/react-common/components/controls/RadioButtonGroup.tsx +66 -0
  37. package/react-common/components/util.tsx +23 -0
  38. package/react-common/styles/controls/Button.less +21 -0
  39. package/react-common/styles/controls/DraggableGraph.less +13 -0
  40. package/react-common/styles/controls/Dropdown.less +68 -0
  41. package/react-common/styles/controls/RadioButtonGroup.less +36 -0
  42. package/react-common/styles/react-common-variables.less +38 -0
  43. package/react-common/styles/react-common.less +3 -0
  44. package/theme/pxt.less +1 -0
  45. package/theme/soundeffecteditor.less +239 -0
  46. package/webapp/public/skillmap.html +1 -1
  47. package/built/web/skillmap/js/main.2485091f.chunk.js +0 -1
@@ -0,0 +1,242 @@
1
+ import * as React from "react";
2
+ import { ControlProps, screenToSVGCoord, clientCoord, classList } from "../util";
3
+
4
+ export interface DraggableGraphProps extends ControlProps {
5
+ interpolation: pxt.assets.SoundInterpolation;
6
+ min: number;
7
+ max: number;
8
+ squiggly?: boolean;
9
+
10
+ aspectRatio: number; // width / height
11
+ onPointChange: (index: number, newValue: number) => void;
12
+
13
+ // Points are equally spaced y-values along the width of the graph
14
+ points: number[];
15
+ handleStartAnimationRef?: (startAnimation: (duration: number) => void) => void;
16
+ }
17
+
18
+ export const DraggableGraph = (props: DraggableGraphProps) => {
19
+ const {
20
+ interpolation,
21
+ min,
22
+ max,
23
+ points,
24
+ handleStartAnimationRef,
25
+ onPointChange,
26
+ id,
27
+ className,
28
+ ariaLabel,
29
+ ariaHidden,
30
+ ariaDescribedBy,
31
+ role,
32
+ aspectRatio,
33
+ squiggly
34
+ } = props;
35
+
36
+ const width = 1000;
37
+ const height = (1 / aspectRatio) * width;
38
+
39
+ const unit = width / 40;
40
+ const halfUnit = unit / 2;
41
+
42
+ const yOffset = unit;
43
+ const availableHeight = height - unit * 5 / 2;
44
+ const availableWidth = width - halfUnit * 3;
45
+
46
+ const xSlice = availableWidth / (points.length - 1);
47
+ const yScale = availableHeight / (max - min);
48
+
49
+ const [dragIndex, setDragIndex] = React.useState(-1);
50
+
51
+ const svgCoordToValue = (point: DOMPoint) =>
52
+ (1 - ((point.y - yOffset) / availableHeight)) * (max - min) + min;
53
+
54
+ let animationRef: number;
55
+
56
+ const throttledSetDragValue = (index: number, value: number) => {
57
+ if (animationRef) cancelAnimationFrame(animationRef);
58
+ animationRef = requestAnimationFrame(() => {
59
+ handlePointChange(index, value);
60
+ });
61
+ }
62
+
63
+ const handlePointChange = (index: number, newValue: number) => {
64
+ onPointChange(index, Math.max(Math.min(newValue, max), min));
65
+ }
66
+
67
+ const refs: SVGRectElement[] = [];
68
+
69
+ const getPointRefHandler = (index: number) =>
70
+ (ref: SVGRectElement) => {
71
+ if (!ref) return;
72
+
73
+ refs[index] = ref;
74
+ }
75
+
76
+ React.useEffect(() => {
77
+ refs.forEach((ref, index) => {
78
+ ref.onpointerdown = ev => {
79
+ if (dragIndex !== -1) return;
80
+ const coord = clientCoord(ev);
81
+ const svg = screenToSVGCoord(ref.ownerSVGElement, coord);
82
+ setDragIndex(index);
83
+ throttledSetDragValue(index, svgCoordToValue(svg));
84
+ };
85
+
86
+ ref.onpointermove = ev => {
87
+ if (dragIndex !== index) return;
88
+ const coord = clientCoord(ev);
89
+ const svg = screenToSVGCoord(ref.ownerSVGElement, coord);
90
+ throttledSetDragValue(index, svgCoordToValue(svg));
91
+ };
92
+
93
+ ref.onpointerleave = ev => {
94
+ if (dragIndex !== index) return;
95
+ setDragIndex(-1);
96
+ const coord = clientCoord(ev);
97
+ const svg = screenToSVGCoord(ref.ownerSVGElement, coord);
98
+ throttledSetDragValue(index, svgCoordToValue(svg));
99
+ };
100
+
101
+ ref.onpointerup = ev => {
102
+ if (dragIndex !== index) return;
103
+ setDragIndex(-1);
104
+ const coord = clientCoord(ev);
105
+ const svg = screenToSVGCoord(ref.ownerSVGElement, coord);
106
+ throttledSetDragValue(index, svgCoordToValue(svg));
107
+ };
108
+ });
109
+ }, [dragIndex, onPointChange])
110
+
111
+ const getValue = (index: number) => {
112
+ return points[index];
113
+ }
114
+
115
+ const handleRectAnimateRef = (ref: SVGAnimateElement) => {
116
+ if (ref && handleStartAnimationRef) {
117
+ handleStartAnimationRef((duration: number) => {
118
+ if (duration <= 0) duration = 1;
119
+ ref.setAttribute("dur", duration + "ms");
120
+ (ref as any).beginElement();
121
+ })
122
+ }
123
+ }
124
+
125
+ return <div
126
+ id={id}
127
+ className={classList("common-draggable-graph", className)}
128
+ aria-label={ariaLabel}
129
+ aria-hidden={ariaHidden}
130
+ aria-describedby={ariaDescribedBy}
131
+ role={role}>
132
+ <svg viewBox={`0 0 ${width} ${height}`} xmlns="http://www.w3.org/2000/svg">
133
+ {points.map((val, index) => {
134
+ const isNotLast = index < points.length - 1;
135
+ const x = Math.max(xSlice * index - halfUnit, unit);
136
+ const y = yOffset + Math.max(yScale * (max - getValue(index)) - halfUnit, halfUnit);
137
+
138
+ // The logarithmic interpolation is perpendicular to the x-axis at the beginning, so
139
+ // flip the label to the other side if it would overlap path
140
+ const shouldFlipLabel = isNotLast && interpolation === "logarithmic" && getValue(index + 1) > getValue(index);
141
+
142
+ return <g key={index}>
143
+ <rect
144
+ className="draggable-graph-point"
145
+ x={x}
146
+ y={y}
147
+ width={unit}
148
+ height={unit}
149
+ />
150
+ {isNotLast &&
151
+ <path
152
+ className="draggable-graph-path"
153
+ stroke="black"
154
+ fill="none"
155
+ strokeWidth="2px"
156
+ d={getInterpolationPath(
157
+ x + halfUnit,
158
+ y + halfUnit,
159
+ Math.max(xSlice * (index + 1), 0),
160
+ yOffset + Math.max(yScale * (max - getValue(index + 1)) - halfUnit, halfUnit) + halfUnit,
161
+ interpolation,
162
+ squiggly
163
+ )}
164
+ />
165
+ }
166
+ <text x={x + halfUnit} y={shouldFlipLabel ? y + unit * 2 : y - halfUnit} fontSize={unit} className="common-draggable-graph-text">
167
+ {Math.round(getValue(index))}
168
+ </text>
169
+ <rect
170
+ className="draggable-graph-surface"
171
+ ref={getPointRefHandler(index)}
172
+ x={x - xSlice / 6}
173
+ y={0}
174
+ width={xSlice / 3}
175
+ height={height}
176
+ fill="white"
177
+ opacity={0}
178
+ />
179
+ </g>
180
+ })}
181
+ <rect x="-2" y="0" width="1" height="100%" fill="grey">
182
+ <animate ref={handleRectAnimateRef} attributeName="x" from="2%" to="98%" dur="1000ms" begin="indefinite" />
183
+ </rect>
184
+ </svg>
185
+ </div>
186
+ }
187
+
188
+ function getInterpolationPath(x0: number, y0: number, x1: number, y1: number, curve: pxt.assets.SoundInterpolation, squiggly: boolean) {
189
+ let pathFunction: (x: number) => number;
190
+
191
+ switch (curve) {
192
+ case "linear":
193
+ pathFunction = x => y0 + (x - x0) * (y1 - y0) / (x1 - x0);
194
+ break;
195
+ case "curve":
196
+ pathFunction = x => y0 + (y1 - y0) * Math.sin((x - x0) / (x1 - x0) * (Math.PI / 2));
197
+ break;
198
+ case "logarithmic":
199
+ pathFunction = x => y0 + Math.log10(1 + 9 * ((x - x0) / (x1 - x0))) * (y1 - y0)
200
+ break;
201
+ }
202
+
203
+ const slices = 20;
204
+ const slice = (x1 - x0) / slices;
205
+
206
+ const parts: string[] = [`M ${x0} ${y0}`];
207
+
208
+ let prevX = x0;
209
+ let prevY = y0;
210
+
211
+ let currX = x0;
212
+ let currY = y0;
213
+
214
+ const squiggleAmplitude = 40;
215
+
216
+ for (let i = 1; i < slices + 1; i++) {
217
+ currX = x0 + i * slice;
218
+ currY = pathFunction(currX);
219
+ if (!squiggly) {
220
+ parts.push(`L ${currX} ${currY}`);
221
+ continue;
222
+ }
223
+
224
+ const angle = Math.atan2(currY - prevY, currX - prevX);
225
+ const distance = Math.sqrt((currY - prevY) ** 2 + (currX - prevX) ** 2);
226
+
227
+ const cx1 = prevX + Math.cos(angle) * (distance / 4) + squiggleAmplitude * Math.cos(angle + Math.PI / 2);
228
+ const cy1 = prevY + Math.sin(angle) * (distance / 4) + squiggleAmplitude * Math.sin(angle + Math.PI / 2);
229
+
230
+ parts.push(`Q ${cx1} ${cy1} ${prevX + Math.cos(angle) * (distance / 2)} ${prevY + Math.sin(angle) * (distance / 2)}`)
231
+
232
+ const cx2 = prevX + Math.cos(angle) * (3 * distance / 4) - squiggleAmplitude * Math.cos(angle + Math.PI / 2);
233
+ const cy2 = prevY + Math.sin(angle) * (3 * distance / 4) - squiggleAmplitude * Math.sin(angle + Math.PI / 2);
234
+
235
+ parts.push(`Q ${cx2} ${cy2} ${currX} ${currY}`)
236
+
237
+ prevX = currX;
238
+ prevY = currY;
239
+ }
240
+
241
+ return parts.join(" ");
242
+ }
@@ -0,0 +1,121 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+ import { Button, ButtonViewProps } from "./Button";
4
+ import { FocusList } from "./FocusList";
5
+
6
+ export interface DropdownItem extends ButtonViewProps {
7
+ id: string;
8
+ role?: "option" | undefined;
9
+ }
10
+
11
+ export interface DropdownProps extends ControlProps {
12
+ id: string;
13
+ selectedId: string;
14
+ items: DropdownItem[];
15
+ onItemSelected: (id: string) => void;
16
+ tabIndex?: number;
17
+ }
18
+
19
+ export const Dropdown = (props: DropdownProps) => {
20
+ const {
21
+ id,
22
+ className,
23
+ ariaHidden,
24
+ ariaLabel,
25
+ role,
26
+ items,
27
+ tabIndex,
28
+ selectedId,
29
+ onItemSelected
30
+ } = props;
31
+
32
+ const [ expanded, setExpanded ] = React.useState(false);
33
+
34
+ let container: HTMLDivElement;
35
+
36
+ const handleContainerRef = (ref: HTMLDivElement) => {
37
+ if (!ref) return;
38
+ container = ref;
39
+ }
40
+
41
+ const onMenuButtonClick = () => {
42
+ setExpanded(!expanded);
43
+ }
44
+
45
+ const onBlur = (e: React.FocusEvent) => {
46
+ if (!container) return;
47
+ if (expanded && !container.contains(e.relatedTarget as HTMLElement)) setExpanded(false);
48
+ }
49
+
50
+ const classes = classList("common-dropdown", className);
51
+
52
+ const selected = items.find(item => item.id === selectedId) || items[0];
53
+ const onItemFocused = (element: HTMLElement) => {
54
+ if (element.id && items.some(item => item.id === element.id)) onItemSelected(element.id);
55
+ }
56
+
57
+ const onKeyDown = (e: React.KeyboardEvent) => {
58
+ const selectedIndex = items.indexOf(selected)
59
+
60
+ if (e.key === "ArrowDown") {
61
+ if (selectedIndex < items.length - 1) {
62
+ onItemSelected(items[selectedIndex + 1].id);
63
+ e.preventDefault();
64
+ e.stopPropagation();
65
+ }
66
+ }
67
+ else if (e.key === "ArrowUp") {
68
+ if (selectedIndex > 0) {
69
+ onItemSelected(items[selectedIndex - 1].id);
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ }
73
+ }
74
+ else if (e.key === "Enter") {
75
+ setExpanded(true);
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ }
79
+ }
80
+
81
+ return <div className={classes} ref={handleContainerRef} onBlur={onBlur}>
82
+ <Button
83
+ {...selected}
84
+ id={id}
85
+ tabIndex={tabIndex}
86
+ rightIcon={expanded ? "fas fa-chevron-up" : "fas fa-chevron-down"}
87
+ role={role}
88
+ className={classList("common-dropdown-button", expanded && "expanded", selected.className)}
89
+ onClick={onMenuButtonClick}
90
+ onKeydown={onKeyDown}
91
+ ariaHasPopup="listbox"
92
+ ariaExpanded={expanded}
93
+ ariaLabel={ariaLabel}
94
+ ariaHidden={ariaHidden}
95
+ />
96
+ {expanded &&
97
+ <FocusList role="listbox"
98
+ className="common-menu-dropdown-pane common-dropdown-shadow"
99
+ childTabStopId={selectedId}
100
+ aria-labelledby={id}
101
+ useUpAndDownArrowKeys={true}
102
+ onItemReceivedFocus={onItemFocused}>
103
+ <ul role="presentation">
104
+ { items.map(item =>
105
+ <li key={item.id} role="presentation">
106
+ <Button
107
+ {...item}
108
+ className={classList("common-dropdown-item", item.className)}
109
+ onClick={() => {
110
+ setExpanded(false);
111
+ onItemSelected(item.id);
112
+ }}
113
+ ariaSelected={item.id === selectedId}
114
+ role="option"/>
115
+ </li>
116
+ )}
117
+ </ul>
118
+ </FocusList>
119
+ }
120
+ </div>
121
+ }
@@ -4,6 +4,8 @@ import { ContainerProps } from "../util";
4
4
  export interface FocusListProps extends ContainerProps {
5
5
  role: string;
6
6
  childTabStopId?: string;
7
+ useUpAndDownArrowKeys?: boolean;
8
+ onItemReceivedFocus?: (item: HTMLElement) => void;
7
9
  }
8
10
 
9
11
  /**
@@ -23,6 +25,8 @@ export const FocusList = (props: FocusListProps) => {
23
25
  ariaLabel,
24
26
  childTabStopId,
25
27
  children,
28
+ onItemReceivedFocus,
29
+ useUpAndDownArrowKeys
26
30
  } = props;
27
31
 
28
32
  let focusableElements: HTMLElement[];
@@ -59,6 +63,11 @@ export const FocusList = (props: FocusListProps) => {
59
63
  const target = document.activeElement as HTMLElement;
60
64
  const index = focusableElements.indexOf(target);
61
65
 
66
+ const focus = (element: HTMLElement) => {
67
+ element.focus();
68
+ if (onItemReceivedFocus) onItemReceivedFocus(element);
69
+ }
70
+
62
71
  if (index === -1 && target !== focusList) return;
63
72
 
64
73
  if (e.key === "Enter" || e.key === " ") {
@@ -73,33 +82,33 @@ export const FocusList = (props: FocusListProps) => {
73
82
  target.dispatchEvent(new Event("click"));
74
83
  }
75
84
  }
76
- else if (e.key === "ArrowRight") {
85
+ else if (e.key === (useUpAndDownArrowKeys ? "ArrowDown" : "ArrowRight")) {
77
86
  if (index === focusableElements.length - 1 || target === focusList) {
78
- focusableElements[0].focus();
87
+ focus(focusableElements[0]);
79
88
  }
80
89
  else {
81
- focusableElements[index + 1].focus();
90
+ focus(focusableElements[index + 1]);
82
91
  }
83
92
  e.preventDefault();
84
93
  e.stopPropagation();
85
94
  }
86
- else if (e.key === "ArrowLeft") {
95
+ else if (e.key === (useUpAndDownArrowKeys ? "ArrowUp" : "ArrowLeft")) {
87
96
  if (index === 0 || target === focusList) {
88
- focusableElements[focusableElements.length - 1].focus();
97
+ focus(focusableElements[focusableElements.length - 1]);
89
98
  }
90
99
  else {
91
- focusableElements[Math.max(index - 1, 0)].focus();
100
+ focus(focusableElements[Math.max(index - 1, 0)]);
92
101
  }
93
102
  e.preventDefault();
94
103
  e.stopPropagation();
95
104
  }
96
105
  else if (e.key === "Home") {
97
- focusableElements[0].focus();
106
+ focus(focusableElements[0]);
98
107
  e.preventDefault();
99
108
  e.stopPropagation();
100
109
  }
101
110
  else if (e.key === "End") {
102
- focusableElements[focusableElements.length - 1].focus();
111
+ focus(focusableElements[focusableElements.length - 1]);
103
112
  e.preventDefault();
104
113
  e.stopPropagation();
105
114
  }
@@ -19,6 +19,7 @@ export interface InputProps extends ControlProps {
19
19
  onChange?: (newValue: string) => void;
20
20
  onEnterKey?: (value: string) => void;
21
21
  onIconClick?: (value: string) => void;
22
+ onBlur?: (value: string) => void;
22
23
  }
23
24
 
24
25
  export const Input = (props: InputProps) => {
@@ -41,10 +42,11 @@ export const Input = (props: InputProps) => {
41
42
  selectOnClick,
42
43
  onChange,
43
44
  onEnterKey,
44
- onIconClick
45
+ onIconClick,
46
+ onBlur
45
47
  } = props;
46
48
 
47
- const [value, setValue] = React.useState(initialValue || "");
49
+ const [value, setValue] = React.useState(undefined);
48
50
 
49
51
  const clickHandler = (evt: React.MouseEvent<any>) => {
50
52
  if (selectOnClick) {
@@ -76,6 +78,13 @@ export const Input = (props: InputProps) => {
76
78
  if (onIconClick) onIconClick(value);
77
79
  }
78
80
 
81
+ const blurHandler = () => {
82
+ if (onBlur) {
83
+ onBlur(value);
84
+ }
85
+ setValue(undefined);
86
+ }
87
+
79
88
  return (
80
89
  <div className={classList("common-input-wrapper", disabled && "disabled", className)}>
81
90
  {label && <label className="common-input-label" htmlFor={id}>
@@ -92,11 +101,12 @@ export const Input = (props: InputProps) => {
92
101
  aria-hidden={ariaHidden}
93
102
  type={type || "text"}
94
103
  placeholder={placeholder}
95
- value={value || ''}
104
+ value={value || initialValue || ""}
96
105
  readOnly={!!readOnly}
97
106
  onClick={clickHandler}
98
107
  onChange={changeHandler}
99
108
  onKeyDown={enterKeyHandler}
109
+ onBlur={blurHandler}
100
110
  autoComplete={autoComplete ? "" : "off"}
101
111
  autoCorrect={autoComplete ? "" : "off"}
102
112
  autoCapitalize={autoComplete ? "" : "off"}
@@ -0,0 +1,66 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+ import { FocusList } from "./FocusList";
4
+
5
+ export interface RadioButtonGroupProps extends ControlProps {
6
+ id: string;
7
+ choices: RadioGroupChoice[];
8
+ selectedId: string;
9
+ onChoiceSelected: (id: string) => void;
10
+ }
11
+
12
+ export interface RadioGroupChoice {
13
+ title: string;
14
+ id: string;
15
+ className?: string;
16
+ icon?: string;
17
+ label?: string | JSX.Element;
18
+ }
19
+
20
+ export const RadioButtonGroup = (props: RadioButtonGroupProps) => {
21
+ const {
22
+ id,
23
+ className,
24
+ ariaHidden,
25
+ ariaLabel,
26
+ role,
27
+ choices,
28
+ selectedId,
29
+ onChoiceSelected
30
+ } = props;
31
+
32
+ const onChoiceClick = (id: string) => {
33
+ onChoiceSelected(id);
34
+ }
35
+
36
+ return (
37
+ <FocusList id={id}
38
+ className={classList("common-radio-group", className)}
39
+ ariaHidden={ariaHidden}
40
+ ariaLabel={ariaLabel}
41
+ role={role || "radiogroup"}
42
+ childTabStopId={selectedId}>
43
+ {choices.map(choice =>
44
+ <div key={choice.id}
45
+ className={classList("common-radio-choice", choice.className, selectedId === choice.id && "selected" )}
46
+ onClick={() => onChoiceClick(choice.id)}>
47
+ <input
48
+ type="radio"
49
+ id={choice.id}
50
+ value={choice.id}
51
+ name={id + "-input"}
52
+ checked={selectedId === choice.id}
53
+ tabIndex={0}
54
+ aria-label={choice.label ? undefined : choice.title}
55
+ aria-labelledby={choice.label ? choice.id + "-label" : undefined} />
56
+ {choice.label &&
57
+ <span id={choice.id + "-label"}>
58
+ {choice.label}
59
+ </span>
60
+ }
61
+ {choice.icon && <i className={choice.icon} aria-hidden={true}/>}
62
+ </div>
63
+ )}
64
+ </FocusList>
65
+ )
66
+ }
@@ -68,4 +68,27 @@ export enum CheckboxStatus {
68
68
  Selected,
69
69
  Unselected,
70
70
  Waiting
71
+ }
72
+
73
+ export interface ClientCoordinates {
74
+ clientX: number;
75
+ clientY: number;
76
+ }
77
+
78
+ export function clientCoord(ev: PointerEvent | MouseEvent | TouchEvent): ClientCoordinates {
79
+ if ((ev as TouchEvent).touches) {
80
+ const te = ev as TouchEvent;
81
+ if (te.touches.length) {
82
+ return te.touches[0];
83
+ }
84
+ return te.changedTouches[0];
85
+ }
86
+ return (ev as PointerEvent | MouseEvent);
87
+ }
88
+
89
+ export function screenToSVGCoord(ref: SVGSVGElement, coord: ClientCoordinates) {
90
+ const screenCoord = ref.createSVGPoint();
91
+ screenCoord.x = coord.clientX;
92
+ screenCoord.y = coord.clientY;
93
+ return screenCoord.matrixTransform(ref.getScreenCTM().inverse());
71
94
  }
@@ -192,6 +192,27 @@
192
192
  color: @buttonMenuTextColorInverted;
193
193
  }
194
194
 
195
+ /****************************************************
196
+ * Link Buttons *
197
+ ****************************************************/
198
+
199
+ .common-button.link-button {
200
+ background: none;
201
+ border: none;
202
+ padding: 0;
203
+ color: @buttonLinkColor;
204
+ }
205
+
206
+ .common-button.link-button:hover {
207
+ text-decoration: underline;
208
+ }
209
+
210
+ .common-button.link-button:focus::after {
211
+ outline: none;
212
+ border: none;
213
+ text-decoration: underline;
214
+ }
215
+
195
216
  /****************************************************
196
217
  * High Contrast *
197
218
  ****************************************************/
@@ -0,0 +1,13 @@
1
+ .common-draggable-graph-text {
2
+ user-select: none;
3
+ color: @commonTextColor;
4
+ text-anchor: middle;
5
+ }
6
+
7
+ .draggable-graph-point {
8
+ fill: @draggableGraphPointColor;
9
+ }
10
+
11
+ .draggable-graph-path {
12
+ stroke: @draggableGraphPointColor;
13
+ }
@@ -0,0 +1,68 @@
1
+ .common-dropdown {
2
+ position: relative;
3
+ width: fit-content;
4
+ }
5
+
6
+ .common-dropdown > .common-button {
7
+ display: block;
8
+ box-sizing: border-box;
9
+
10
+ color: @inputTextColor;
11
+ background-color: @inputBackgroundColor;
12
+ border: 1px solid @inputBorderColor;
13
+
14
+ min-width: 10rem;
15
+ border-radius: 2px;
16
+ padding: 0px 28px 0px 8px;
17
+ margin: 0px;
18
+ height: 32px;
19
+ line-height: 30px;
20
+ position: relative;
21
+ overflow: hidden;
22
+ white-space: nowrap;
23
+ text-overflow: ellipsis;
24
+ text-align: left;
25
+
26
+ & > .common-button-flex > i.right {
27
+ position: absolute;
28
+ right: 0
29
+ }
30
+
31
+ &:focus::after {
32
+ outline: none;
33
+ }
34
+
35
+ &:focus {
36
+ border: 1px solid @inputBorderColorFocus;
37
+ }
38
+ }
39
+
40
+ .common-dropdown .common-button > .common-button-flex > i:first-child {
41
+ margin-right: 0.5rem;
42
+ }
43
+
44
+ .common-dropdown > .common-menu-dropdown-pane {
45
+ width: unset;
46
+ right: unset;
47
+ min-width: 100%;
48
+ left: 0;
49
+ z-index: 1;
50
+
51
+ li .common-button {
52
+ text-align: left;
53
+ width: 100%;
54
+ padding-left: 0.5rem;
55
+ }
56
+ }
57
+
58
+ .common-dropdown-shadow {
59
+ box-shadow: 0 3.2px 7.2px 0 rgb(0 0 0 ~"/ 13%"), 0 0.6px 1.8px 0 rgb(0 0 0 ~"/ 11%");
60
+ }
61
+
62
+ .common-dropdown.icon-preview > .common-button {
63
+ min-width: unset;
64
+
65
+ .common-button-label {
66
+ display: none;
67
+ }
68
+ }