@khanacademy/perseus-editor 4.1.5 → 4.1.6
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/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +9 -6
- package/.babelrc.js +0 -8
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -1155
- package/src/README.md +0 -7
- package/src/__stories__/editor-page.stories.tsx +0 -65
- package/src/__stories__/editor.stories.tsx +0 -134
- package/src/__stories__/item-editor.stories.tsx +0 -41
- package/src/__stories__/tex-error-view.stories.tsx +0 -26
- package/src/__testdata__/input-number.testdata.ts +0 -27
- package/src/__tests__/editor.test.tsx +0 -142
- package/src/__tests__/hint-editor.test.tsx +0 -83
- package/src/__tests__/i18n.test.ts +0 -501
- package/src/__tests__/integration.test.ts +0 -5
- package/src/__tests__/item-extras-editor.test.tsx +0 -85
- package/src/__tests__/traversal.test.ts +0 -377
- package/src/all-editors.test.ts +0 -16
- package/src/all-editors.ts +0 -83
- package/src/all-widgets.ts +0 -7
- package/src/article-editor.tsx +0 -438
- package/src/components/__stories__/blur-input.stories.tsx +0 -22
- package/src/components/__stories__/section-control-button.stories.tsx +0 -26
- package/src/components/__tests__/blur-input.test.tsx +0 -67
- package/src/components/blur-input.tsx +0 -69
- package/src/components/checkbox.tsx +0 -194
- package/src/components/device-framer.tsx +0 -90
- package/src/components/drag-target.tsx +0 -87
- package/src/components/dropdown-option.tsx +0 -311
- package/src/components/form-wrapped-text-field.tsx +0 -245
- package/src/components/graph-points-count-selector.tsx +0 -31
- package/src/components/graph-settings.tsx +0 -627
- package/src/components/graph-type-selector.tsx +0 -31
- package/src/components/hover-behavior.ts +0 -177
- package/src/components/json-editor.tsx +0 -121
- package/src/components/link.ts +0 -173
- package/src/components/section-control-button.tsx +0 -53
- package/src/components/segment-count-selector.tsx +0 -27
- package/src/components/simple-button.tsx +0 -85
- package/src/components/sortable.tsx +0 -187
- package/src/components/util.ts +0 -38
- package/src/components/viewport-resizer.tsx +0 -72
- package/src/components/widget-editor.tsx +0 -222
- package/src/components/widget-select.tsx +0 -48
- package/src/diffs/__stories__/perseus-diff-wrapper.tsx +0 -11
- package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -855
- package/src/diffs/__stories__/tags-diff.stories.tsx +0 -36
- package/src/diffs/__stories__/text-diff.stories.tsx +0 -35
- package/src/diffs/__tests__/split-diff.test.ts +0 -33
- package/src/diffs/__tests__/string-array-diff.test.ts +0 -73
- package/src/diffs/__tests__/structured-item-diff.test.ts +0 -433
- package/src/diffs/__tests__/widget-diff-performer.test.ts +0 -43
- package/src/diffs/article-diff.tsx +0 -69
- package/src/diffs/item-diff.tsx +0 -76
- package/src/diffs/renderer-diff.tsx +0 -137
- package/src/diffs/split-diff.ts +0 -31
- package/src/diffs/string-array-diff.ts +0 -74
- package/src/diffs/structured-item-diff.tsx +0 -275
- package/src/diffs/tags-diff.tsx +0 -49
- package/src/diffs/text-diff.tsx +0 -193
- package/src/diffs/widget-diff-performer.ts +0 -69
- package/src/diffs/widget-diff.tsx +0 -290
- package/src/editor-page.tsx +0 -325
- package/src/editor.tsx +0 -1099
- package/src/hint-editor.tsx +0 -469
- package/src/i18n.ts +0 -140
- package/src/iframe-content-renderer.tsx +0 -188
- package/src/index.ts +0 -30
- package/src/item-editor.tsx +0 -158
- package/src/item-extras-editor.tsx +0 -230
- package/src/katex-mhchem/index.js +0 -73
- package/src/katex-mhchem/parser.js +0 -2659
- package/src/multirenderer-editor.tsx +0 -1061
- package/src/stateful-article-editor.tsx +0 -173
- package/src/stateful-editor-page.tsx +0 -88
- package/src/styles/perseus-editor.less +0 -1321
- package/src/testing-widgets.ts +0 -11
- package/src/tex-error-view.tsx +0 -90
- package/src/util/points.test.ts +0 -24
- package/src/util/points.ts +0 -13
- package/src/util/register-all-widgets-and-editors-for-testing.test.ts +0 -17
- package/src/util/register-all-widgets-and-editors-for-testing.ts +0 -17
- package/src/version.ts +0 -10
- package/src/widgets/__stories__/categorizer-editor.stories.tsx +0 -24
- package/src/widgets/__stories__/definition-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/dropdown-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/explanation-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/expression-editor.stories.tsx +0 -122
- package/src/widgets/__stories__/image-editor.stories.tsx +0 -72
- package/src/widgets/__stories__/input-number-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/interaction-editor.stories.tsx +0 -37
- package/src/widgets/__stories__/label-image-editor.stories.tsx +0 -99
- package/src/widgets/__stories__/matcher-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/number-line-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/numeric-input-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/radio-editor.stories.tsx +0 -116
- package/src/widgets/__stories__/sorter-editor.stories.tsx +0 -18
- package/src/widgets/__tests__/categorizer-editor.test.tsx +0 -51
- package/src/widgets/__tests__/definition-editor.test.tsx +0 -41
- package/src/widgets/__tests__/dropdown-editor.test.tsx +0 -57
- package/src/widgets/__tests__/explanation-editor.test.tsx +0 -59
- package/src/widgets/__tests__/expression-editor.test.tsx +0 -403
- package/src/widgets/__tests__/input-number-editor.test.tsx +0 -128
- package/src/widgets/__tests__/matcher-editor.test.tsx +0 -47
- package/src/widgets/__tests__/number-line-editor.test.tsx +0 -144
- package/src/widgets/__tests__/numeric-input-editor.test.tsx +0 -177
- package/src/widgets/__tests__/radio-editor.test.tsx +0 -118
- package/src/widgets/__tests__/sorter-editor.test.tsx +0 -55
- package/src/widgets/categorizer-editor.tsx +0 -104
- package/src/widgets/cs-program-editor.tsx +0 -256
- package/src/widgets/definition-editor.tsx +0 -77
- package/src/widgets/deprecated-standin-editor.tsx +0 -29
- package/src/widgets/dropdown-editor.tsx +0 -211
- package/src/widgets/example-graphie-widget-editor.tsx +0 -70
- package/src/widgets/example-graphie-widget.tsx +0 -172
- package/src/widgets/example-widget-editor.tsx +0 -62
- package/src/widgets/example-widget.tsx +0 -154
- package/src/widgets/explanation-editor.tsx +0 -92
- package/src/widgets/expression-editor.tsx +0 -557
- package/src/widgets/graded-group-editor.tsx +0 -161
- package/src/widgets/graded-group-set-editor.tsx +0 -104
- package/src/widgets/grapher-editor.tsx +0 -155
- package/src/widgets/group-editor.tsx +0 -83
- package/src/widgets/iframe-editor.tsx +0 -202
- package/src/widgets/image-editor.tsx +0 -367
- package/src/widgets/input-number-editor.tsx +0 -288
- package/src/widgets/interaction-editor/constraint-editor.tsx +0 -134
- package/src/widgets/interaction-editor/function-editor.tsx +0 -108
- package/src/widgets/interaction-editor/index.ts +0 -3
- package/src/widgets/interaction-editor/interaction-editor.tsx +0 -749
- package/src/widgets/interaction-editor/label-editor.tsx +0 -87
- package/src/widgets/interaction-editor/line-editor.tsx +0 -125
- package/src/widgets/interaction-editor/mathquill-input.tsx +0 -81
- package/src/widgets/interaction-editor/movable-line-editor.tsx +0 -121
- package/src/widgets/interaction-editor/movable-point-editor.tsx +0 -89
- package/src/widgets/interaction-editor/parametric-editor.tsx +0 -117
- package/src/widgets/interaction-editor/point-editor.tsx +0 -72
- package/src/widgets/interaction-editor/rectangle-editor.tsx +0 -96
- package/src/widgets/interactive-graph-editor.tsx +0 -571
- package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -82
- package/src/widgets/label-image/__stories__/behavior.stories.tsx +0 -31
- package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -75
- package/src/widgets/label-image/__stories__/question-markers.stories.tsx +0 -107
- package/src/widgets/label-image/__stories__/select-image.stories.tsx +0 -59
- package/src/widgets/label-image/answer-choices.tsx +0 -231
- package/src/widgets/label-image/behavior.tsx +0 -122
- package/src/widgets/label-image/marker.tsx +0 -290
- package/src/widgets/label-image/question-markers.tsx +0 -176
- package/src/widgets/label-image/select-image.tsx +0 -81
- package/src/widgets/label-image-editor.tsx +0 -252
- package/src/widgets/lights-puzzle-editor.tsx +0 -278
- package/src/widgets/matcher-editor.tsx +0 -149
- package/src/widgets/matrix-editor.tsx +0 -156
- package/src/widgets/measurer-editor.tsx +0 -253
- package/src/widgets/molecule-editor.tsx +0 -64
- package/src/widgets/number-line-editor.tsx +0 -512
- package/src/widgets/numeric-input-editor.tsx +0 -536
- package/src/widgets/orderer-editor.tsx +0 -172
- package/src/widgets/passage-editor.tsx +0 -117
- package/src/widgets/passage-ref-editor.tsx +0 -80
- package/src/widgets/passage-ref-target-editor.tsx +0 -49
- package/src/widgets/plotter-editor.tsx +0 -542
- package/src/widgets/radio/editor.tsx +0 -398
- package/src/widgets/reaction-diagram-editor.tsx +0 -131
- package/src/widgets/sequence-editor.tsx +0 -205
- package/src/widgets/simple-markdown-tester-editor.tsx +0 -86
- package/src/widgets/simple-markdown-tester.tsx +0 -92
- package/src/widgets/simulator-editor.tsx +0 -146
- package/src/widgets/sorter-editor.tsx +0 -103
- package/src/widgets/table-editor.tsx +0 -164
- package/src/widgets/unit-editor.tsx +0 -299
- package/src/widgets/video-editor.tsx +0 -73
- package/tsconfig-build.json +0 -22
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/**
|
|
3
|
-
* Reusable checkbox component.
|
|
4
|
-
**/
|
|
5
|
-
import {globalStyles} from "@khanacademy/perseus";
|
|
6
|
-
import {css, StyleSheet} from "aphrodite";
|
|
7
|
-
import * as React from "react";
|
|
8
|
-
|
|
9
|
-
import HoverBehavior from "./hover-behavior";
|
|
10
|
-
|
|
11
|
-
const {borderRadius, colors} = globalStyles;
|
|
12
|
-
|
|
13
|
-
export type CheckboxProps = {
|
|
14
|
-
// Whether or not the checkbox is checked. The parent responsible for
|
|
15
|
-
// responding to onChange events and updating this prop appropriately.
|
|
16
|
-
checked: boolean;
|
|
17
|
-
// When disable the checkbox will appear faded out and will not
|
|
18
|
-
// respond to events and cannot be tabbed to.
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
// Appear as if disabled but allow it to respond to events and be
|
|
21
|
-
// tabbed to.
|
|
22
|
-
// This allows us to hook alternate responses into clicking on a
|
|
23
|
-
// disabled checkbox.
|
|
24
|
-
appearDisabled?: boolean;
|
|
25
|
-
// Called when the value has changed.
|
|
26
|
-
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
27
|
-
tabIndex?: number | string;
|
|
28
|
-
style?: {
|
|
29
|
-
marginTop: number;
|
|
30
|
-
};
|
|
31
|
-
// For e2e test
|
|
32
|
-
dataTestId?: string;
|
|
33
|
-
// Unique identifier for this checkbox.
|
|
34
|
-
id?: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// NOTE(jangmi): Checkbox may have `onChange` depends on circumstances but lack
|
|
38
|
-
// of onChange causes Dev only alert so we provide the no op func as default
|
|
39
|
-
// to prevent that.
|
|
40
|
-
const onChangeCheckboxNoop = (event: React.ChangeEvent<HTMLInputElement>) => {};
|
|
41
|
-
|
|
42
|
-
export default class Checkbox extends React.Component<CheckboxProps> {
|
|
43
|
-
static defaultProps: {
|
|
44
|
-
checked: boolean;
|
|
45
|
-
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
46
|
-
} = {
|
|
47
|
-
checked: false,
|
|
48
|
-
onChange: onChangeCheckboxNoop,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
render(): React.ReactNode {
|
|
52
|
-
const {
|
|
53
|
-
checked,
|
|
54
|
-
disabled,
|
|
55
|
-
appearDisabled,
|
|
56
|
-
onChange,
|
|
57
|
-
tabIndex,
|
|
58
|
-
style,
|
|
59
|
-
dataTestId,
|
|
60
|
-
id,
|
|
61
|
-
} = this.props;
|
|
62
|
-
|
|
63
|
-
const checkedColor = colors.gray41;
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<HoverBehavior>
|
|
67
|
-
{({focused}, handlers) => (
|
|
68
|
-
<div
|
|
69
|
-
{...handlers}
|
|
70
|
-
className={css(
|
|
71
|
-
styles.container,
|
|
72
|
-
focused && styles.focused,
|
|
73
|
-
)}
|
|
74
|
-
style={style}
|
|
75
|
-
data-test-id={dataTestId}
|
|
76
|
-
data-checked={checked}
|
|
77
|
-
>
|
|
78
|
-
<svg
|
|
79
|
-
className={css(
|
|
80
|
-
styles.svg,
|
|
81
|
-
(disabled || appearDisabled) && styles.disabled,
|
|
82
|
-
)}
|
|
83
|
-
width={sizeWithPadding}
|
|
84
|
-
height={sizeWithPadding}
|
|
85
|
-
/* pad the viewbox to avoid bad clipping when zooming */
|
|
86
|
-
viewBox={`-${padding} -${padding}
|
|
87
|
-
${sizeWithPadding} ${sizeWithPadding}`}
|
|
88
|
-
>
|
|
89
|
-
<g fill="none" fillRule="evenodd">
|
|
90
|
-
{checked && (
|
|
91
|
-
<g>
|
|
92
|
-
<rect
|
|
93
|
-
fill={checkedColor}
|
|
94
|
-
width={size}
|
|
95
|
-
height={size}
|
|
96
|
-
x="0"
|
|
97
|
-
y="0"
|
|
98
|
-
rx={borderRadius}
|
|
99
|
-
/>
|
|
100
|
-
<path
|
|
101
|
-
fill={colors.white}
|
|
102
|
-
stroke={colors.white}
|
|
103
|
-
d="M4.98 7.41a0.58.58 0 1 0-0.81.81L6.47
|
|
104
|
-
10.53c0.23.23.59.23.81
|
|
105
|
-
0l4.55-4.55a0.58.58 0 0
|
|
106
|
-
0-0.81-0.81L6.88 9.31 4.98 7.41z"
|
|
107
|
-
/>
|
|
108
|
-
</g>
|
|
109
|
-
)}
|
|
110
|
-
{!checked && (
|
|
111
|
-
<rect
|
|
112
|
-
fill={colors.white}
|
|
113
|
-
stroke={colors.gray68}
|
|
114
|
-
width={size - 2 * padding}
|
|
115
|
-
height={size - 2 * padding}
|
|
116
|
-
x={padding}
|
|
117
|
-
y={padding}
|
|
118
|
-
rx="4"
|
|
119
|
-
strokeWidth={borderWidth}
|
|
120
|
-
/>
|
|
121
|
-
)}
|
|
122
|
-
</g>
|
|
123
|
-
</svg>
|
|
124
|
-
<input
|
|
125
|
-
type="checkbox"
|
|
126
|
-
id={id}
|
|
127
|
-
checked={checked}
|
|
128
|
-
className={css(
|
|
129
|
-
styles.checkbox,
|
|
130
|
-
disabled && styles.defaultCursor,
|
|
131
|
-
)}
|
|
132
|
-
disabled={disabled}
|
|
133
|
-
onChange={onChange}
|
|
134
|
-
// @ts-expect-error - TS2322 - Type 'string | number | undefined' is not assignable to type 'number | undefined'.
|
|
135
|
-
tabIndex={tabIndex}
|
|
136
|
-
/>
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
139
|
-
</HoverBehavior>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const size = 16;
|
|
145
|
-
const padding = 0.5;
|
|
146
|
-
const borderWidth = 1;
|
|
147
|
-
const sizeWithPadding = size + 2 * padding;
|
|
148
|
-
|
|
149
|
-
const styles = StyleSheet.create({
|
|
150
|
-
container: {
|
|
151
|
-
position: "relative",
|
|
152
|
-
display: "inline-block",
|
|
153
|
-
verticalAlign: "middle",
|
|
154
|
-
lineHeight: 0,
|
|
155
|
-
borderRadius: borderRadius,
|
|
156
|
-
width: sizeWithPadding,
|
|
157
|
-
height: sizeWithPadding,
|
|
158
|
-
flexShrink: 0,
|
|
159
|
-
},
|
|
160
|
-
focused: {
|
|
161
|
-
"::before": {
|
|
162
|
-
content: '""',
|
|
163
|
-
position: "absolute",
|
|
164
|
-
top: -2,
|
|
165
|
-
right: -2,
|
|
166
|
-
bottom: -2,
|
|
167
|
-
left: -2,
|
|
168
|
-
borderRadius: borderRadius + 2,
|
|
169
|
-
backgroundColor: "lightblue", // TODO(kevinb) get real focus ring
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
svg: {
|
|
173
|
-
position: "absolute",
|
|
174
|
-
left: 0,
|
|
175
|
-
top: 0,
|
|
176
|
-
},
|
|
177
|
-
checkbox: {
|
|
178
|
-
appearance: "none",
|
|
179
|
-
opacity: 0,
|
|
180
|
-
position: "absolute",
|
|
181
|
-
top: padding,
|
|
182
|
-
width: size,
|
|
183
|
-
height: size,
|
|
184
|
-
margin: 0,
|
|
185
|
-
outline: "none",
|
|
186
|
-
cursor: "pointer",
|
|
187
|
-
},
|
|
188
|
-
disabled: {
|
|
189
|
-
opacity: 0.5,
|
|
190
|
-
},
|
|
191
|
-
defaultCursor: {
|
|
192
|
-
cursor: "default",
|
|
193
|
-
},
|
|
194
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A component that displays its contents inside a device frame.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {constants} from "@khanacademy/perseus";
|
|
6
|
-
import PropTypes from "prop-types";
|
|
7
|
-
import * as React from "react";
|
|
8
|
-
|
|
9
|
-
const SCREEN_SIZES = {
|
|
10
|
-
phone: {
|
|
11
|
-
width: 320,
|
|
12
|
-
height: 480,
|
|
13
|
-
framedWidth: 320,
|
|
14
|
-
},
|
|
15
|
-
tablet: {
|
|
16
|
-
width: 750,
|
|
17
|
-
height: 920,
|
|
18
|
-
framedWidth: 525,
|
|
19
|
-
},
|
|
20
|
-
desktop: {
|
|
21
|
-
width: 688,
|
|
22
|
-
height: 600,
|
|
23
|
-
framedWidth: 688,
|
|
24
|
-
},
|
|
25
|
-
} as const;
|
|
26
|
-
|
|
27
|
-
type Props = any;
|
|
28
|
-
|
|
29
|
-
class DeviceFramer extends React.Component<Props> {
|
|
30
|
-
static propTypes = {
|
|
31
|
-
children: PropTypes.element.isRequired,
|
|
32
|
-
deviceType: PropTypes.oneOf([
|
|
33
|
-
constants.devices.PHONE,
|
|
34
|
-
constants.devices.TABLET,
|
|
35
|
-
constants.devices.DESKTOP,
|
|
36
|
-
]).isRequired,
|
|
37
|
-
// TODO(kevinb) rename to variableHeight
|
|
38
|
-
nochrome: PropTypes.bool,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
render(): React.ReactNode {
|
|
42
|
-
const deviceType = this.props.deviceType;
|
|
43
|
-
|
|
44
|
-
if (this.props.nochrome) {
|
|
45
|
-
// Render content inside a variable height iframe. Used on the
|
|
46
|
-
// "edit" table of the content editor. In this mode, PerseusFrame
|
|
47
|
-
// will draw the border and reserve space on the right for
|
|
48
|
-
// lint indicators.
|
|
49
|
-
return (
|
|
50
|
-
<div>
|
|
51
|
-
<div
|
|
52
|
-
key="screen"
|
|
53
|
-
style={{
|
|
54
|
-
width:
|
|
55
|
-
SCREEN_SIZES[deviceType].framedWidth +
|
|
56
|
-
2 * constants.perseusFrameBorderWidth +
|
|
57
|
-
constants.lintGutterWidth,
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
<div>{this.props.children}</div>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
const scale =
|
|
66
|
-
SCREEN_SIZES[deviceType].framedWidth /
|
|
67
|
-
SCREEN_SIZES[deviceType].width;
|
|
68
|
-
|
|
69
|
-
// In this mode we draw our own border and don't reserve
|
|
70
|
-
// space for a lint gutter.
|
|
71
|
-
const screenStyle = {
|
|
72
|
-
backgroundColor: "white",
|
|
73
|
-
color: "black",
|
|
74
|
-
textAlign: "left",
|
|
75
|
-
width: SCREEN_SIZES[deviceType].width,
|
|
76
|
-
height: SCREEN_SIZES[deviceType].height,
|
|
77
|
-
border: "solid 1px #CCC",
|
|
78
|
-
margin: 8,
|
|
79
|
-
zoom: scale,
|
|
80
|
-
} as const;
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<div key="screen" className="screen" style={screenStyle}>
|
|
84
|
-
{this.props.children}
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export default DeviceFramer;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/* This component makes its children a drag target. Example:
|
|
2
|
-
*
|
|
3
|
-
* <DragTarget onDrop={this.handleDrop}>Drag to me</DragTarget>
|
|
4
|
-
*
|
|
5
|
-
* ...
|
|
6
|
-
*
|
|
7
|
-
* handleDrop: function(e) {
|
|
8
|
-
* this.addImages(e.nativeEvent.dataTransfer.files);
|
|
9
|
-
* }
|
|
10
|
-
*
|
|
11
|
-
* Now "Drag to me" will be a drag target - when something is dragged over it,
|
|
12
|
-
* the element will become partially transparent as a visual indicator that
|
|
13
|
-
* it's a target.
|
|
14
|
-
*/
|
|
15
|
-
// TODO(joel) - indicate before the hover is over the target that it's possible
|
|
16
|
-
// to drag into the target. This would (I think) require a high level handler -
|
|
17
|
-
// like on Perseus itself, waiting for onDragEnter, then passing down the
|
|
18
|
-
// event. Sounds like a pain. Possible workaround - create a div covering the
|
|
19
|
-
// entire page...
|
|
20
|
-
//
|
|
21
|
-
// Other extensions:
|
|
22
|
-
// * custom styles for global drag and dragOver
|
|
23
|
-
// * only respond to certain types of drags (only images for instance)!
|
|
24
|
-
|
|
25
|
-
import createReactClass from "create-react-class";
|
|
26
|
-
import PropTypes from "prop-types";
|
|
27
|
-
import * as React from "react";
|
|
28
|
-
|
|
29
|
-
const DragTarget = createReactClass({
|
|
30
|
-
propTypes: {
|
|
31
|
-
// All props not listed here are forwarded to the root element without
|
|
32
|
-
// modification.
|
|
33
|
-
onDrop: PropTypes.func.isRequired,
|
|
34
|
-
component: PropTypes.any, // component type
|
|
35
|
-
shouldDragHighlight: PropTypes.func,
|
|
36
|
-
style: PropTypes.any,
|
|
37
|
-
},
|
|
38
|
-
getDefaultProps: function () {
|
|
39
|
-
return {
|
|
40
|
-
component: "div",
|
|
41
|
-
shouldDragHighlight: () => true,
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
|
-
getInitialState: function () {
|
|
45
|
-
return {dragHover: false};
|
|
46
|
-
},
|
|
47
|
-
handleDrop: function (e) {
|
|
48
|
-
e.stopPropagation();
|
|
49
|
-
e.preventDefault();
|
|
50
|
-
this.setState({dragHover: false});
|
|
51
|
-
this.props.onDrop(e);
|
|
52
|
-
},
|
|
53
|
-
handleDragEnd: function () {
|
|
54
|
-
this.setState({dragHover: false});
|
|
55
|
-
},
|
|
56
|
-
handleDragOver: function (e) {
|
|
57
|
-
e.preventDefault();
|
|
58
|
-
},
|
|
59
|
-
handleDragLeave: function () {
|
|
60
|
-
this.setState({dragHover: false});
|
|
61
|
-
},
|
|
62
|
-
handleDragEnter: function (e) {
|
|
63
|
-
this.setState({dragHover: this.props.shouldDragHighlight(e)});
|
|
64
|
-
},
|
|
65
|
-
render: function () {
|
|
66
|
-
const opacity = this.state.dragHover ? {opacity: 0.3} : {};
|
|
67
|
-
const Component = this.props.component;
|
|
68
|
-
|
|
69
|
-
const forwardProps = Object.assign({}, this.props);
|
|
70
|
-
delete forwardProps.component;
|
|
71
|
-
delete forwardProps.shouldDragHighlight;
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Component
|
|
75
|
-
{...forwardProps}
|
|
76
|
-
style={Object.assign({}, this.props.style, opacity)}
|
|
77
|
-
onDrop={this.handleDrop}
|
|
78
|
-
onDragEnd={this.handleDragEnd}
|
|
79
|
-
onDragOver={this.handleDragOver}
|
|
80
|
-
onDragEnter={this.handleDragEnter}
|
|
81
|
-
onDragLeave={this.handleDragLeave}
|
|
82
|
-
/>
|
|
83
|
-
);
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
export default DragTarget;
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/**
|
|
3
|
-
* The Option is an item to be used in the Select components and the
|
|
4
|
-
* derivations such as MultiSelect, CheckList, and CheckBoxList. Also includes
|
|
5
|
-
* OptionGroup which is an internal component used in the dropdowns to house
|
|
6
|
-
* a list of options.
|
|
7
|
-
**/
|
|
8
|
-
import {components, globalStyles} from "@khanacademy/perseus";
|
|
9
|
-
import {css, StyleSheet} from "aphrodite";
|
|
10
|
-
import * as React from "react";
|
|
11
|
-
import ReactDOM from "react-dom";
|
|
12
|
-
|
|
13
|
-
import {focusWithChromeStickyFocusBugWorkaround} from "./util";
|
|
14
|
-
|
|
15
|
-
export type OptionRenderer = (
|
|
16
|
-
children: React.ReactElement<any> | null | undefined,
|
|
17
|
-
value: string,
|
|
18
|
-
selected: boolean,
|
|
19
|
-
disabled?: boolean,
|
|
20
|
-
) => React.ReactElement<any>;
|
|
21
|
-
|
|
22
|
-
const {Icon} = components;
|
|
23
|
-
const {colors} = globalStyles;
|
|
24
|
-
|
|
25
|
-
const findAndFocusElement = (component?: Element | null) => {
|
|
26
|
-
const DOMNode: Element | Text | null | undefined =
|
|
27
|
-
ReactDOM.findDOMNode(component);
|
|
28
|
-
const button = DOMNode as HTMLInputElement;
|
|
29
|
-
// @ts-expect-error - TS2774 - This condition will always return true since this function is always defined. Did you mean to call it instead?
|
|
30
|
-
if (button.focus) {
|
|
31
|
-
focusWithChromeStickyFocusBugWorkaround(button);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type Props = {
|
|
36
|
-
// The value to use when the option is selected
|
|
37
|
-
value: string;
|
|
38
|
-
// The display of the option
|
|
39
|
-
children?: React.ReactNode;
|
|
40
|
-
// Is the current option selected?
|
|
41
|
-
selected?: boolean;
|
|
42
|
-
// Is the current option disabled?
|
|
43
|
-
disabled?: boolean;
|
|
44
|
-
// An event when an option is clicked
|
|
45
|
-
onClick?: () => void;
|
|
46
|
-
// An optional rendering function to render the details of the option
|
|
47
|
-
optionRenderer?: OptionRenderer;
|
|
48
|
-
// An optional function to call when the dropdown should close
|
|
49
|
-
// Only relevant if the Option is inside of a dropdown menu
|
|
50
|
-
onDropdownClose?: () => void;
|
|
51
|
-
// An optional value to override the focus styling of an option.
|
|
52
|
-
// Use caution when using this - keyboard users need to know what they're
|
|
53
|
-
// focused on in order to interact with the product!
|
|
54
|
-
hideFocusState?: boolean;
|
|
55
|
-
// An optional test id that can be used to identify this Option in automated tests.
|
|
56
|
-
testId?: string;
|
|
57
|
-
// Text to provide for assistive tech users
|
|
58
|
-
ariaLabel?: string;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const check = `M10,3.8C10,4,9.9,4.2,9.8,4.3L5.1,8.9L4.3,9.8C4.2,9.9,4,10,3.8,10
|
|
62
|
-
S3.5,9.9,3.4,9.8L2.5,8.9L0.2,6.6C0.1,6.5,0,6.3,0,6.2s0.1-0.3,0.2-0.4
|
|
63
|
-
l0.9-0.9c0.1-0.1,0.3-0.2,0.4-0.2s0.3,0.1,0.4,0.2l1.9,1.9l4.2-4.2c0.1
|
|
64
|
-
-0.1,0.3-0.2,0.4-0.2c0.2,0,0.3,0.1,0.4,0.2l0.9,0.9C9.9,3.5,10,3.7,
|
|
65
|
-
10,3.8z`;
|
|
66
|
-
|
|
67
|
-
export const optionHeight = 30;
|
|
68
|
-
|
|
69
|
-
class Option extends React.Component<Props> {
|
|
70
|
-
// @ts-expect-error - TS2564 - Property 'node' has no initializer and is not definitely assigned in the constructor.
|
|
71
|
-
node: HTMLDivElement;
|
|
72
|
-
|
|
73
|
-
handleKeyDown(event: any): void {
|
|
74
|
-
const {onDropdownClose} = this.props;
|
|
75
|
-
const pressedKey = event.key;
|
|
76
|
-
const focusedElement = event.target;
|
|
77
|
-
|
|
78
|
-
if (pressedKey === "ArrowDown" && focusedElement.nextSibling) {
|
|
79
|
-
event.preventDefault();
|
|
80
|
-
focusedElement.nextSibling.focus();
|
|
81
|
-
}
|
|
82
|
-
if (pressedKey === "ArrowUp" && focusedElement.previousSibling) {
|
|
83
|
-
event.preventDefault();
|
|
84
|
-
focusedElement.previousSibling.focus();
|
|
85
|
-
}
|
|
86
|
-
if (
|
|
87
|
-
pressedKey === "ArrowUp" &&
|
|
88
|
-
!focusedElement.previousSibling &&
|
|
89
|
-
onDropdownClose
|
|
90
|
-
) {
|
|
91
|
-
event.preventDefault();
|
|
92
|
-
onDropdownClose();
|
|
93
|
-
}
|
|
94
|
-
if (
|
|
95
|
-
(pressedKey === "Escape" || pressedKey === "Tab") &&
|
|
96
|
-
onDropdownClose
|
|
97
|
-
) {
|
|
98
|
-
onDropdownClose();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
render(): React.ReactNode {
|
|
103
|
-
const {
|
|
104
|
-
selected,
|
|
105
|
-
value,
|
|
106
|
-
onClick,
|
|
107
|
-
children,
|
|
108
|
-
optionRenderer,
|
|
109
|
-
disabled,
|
|
110
|
-
hideFocusState,
|
|
111
|
-
testId,
|
|
112
|
-
ariaLabel,
|
|
113
|
-
} = this.props;
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<button
|
|
117
|
-
// @ts-expect-error - TS2322 - Type 'HTMLButtonElement | null' is not assignable to type 'HTMLDivElement'.
|
|
118
|
-
ref={(node) => (this.node = node)}
|
|
119
|
-
value={value}
|
|
120
|
-
role="menuitemradio"
|
|
121
|
-
aria-checked={selected}
|
|
122
|
-
className={css(
|
|
123
|
-
styles.notAButton,
|
|
124
|
-
disabled && styles.cursorDefault,
|
|
125
|
-
hideFocusState && styles.noFocus,
|
|
126
|
-
)}
|
|
127
|
-
// @ts-expect-error - TS2322 - Type '(value: string) => void' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
|
|
128
|
-
onClick={(value: string) => {
|
|
129
|
-
if (!disabled && onClick) {
|
|
130
|
-
// @ts-expect-error - TS2554 - Expected 0 arguments, but got 1.
|
|
131
|
-
onClick(value);
|
|
132
|
-
}
|
|
133
|
-
}}
|
|
134
|
-
// @ts-expect-error - TS2322 - Type '(event: KeyboardEvent) => void' is not assignable to type 'KeyboardEventHandler<HTMLButtonElement>'.
|
|
135
|
-
onKeyDown={(event: KeyboardEvent) => this.handleKeyDown(event)}
|
|
136
|
-
aria-disabled={disabled}
|
|
137
|
-
aria-label={ariaLabel}
|
|
138
|
-
data-test-id={testId}
|
|
139
|
-
>
|
|
140
|
-
{optionRenderer &&
|
|
141
|
-
optionRenderer(
|
|
142
|
-
/**
|
|
143
|
-
* This expects a `React.Element<>` but `children` is a
|
|
144
|
-
* `React.Node`. We can convert a `React.Node` to a
|
|
145
|
-
* `React.Element<>` by wrapping it in a fragment.
|
|
146
|
-
*/
|
|
147
|
-
<>{children}</>,
|
|
148
|
-
value || "",
|
|
149
|
-
selected || false,
|
|
150
|
-
disabled,
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{!optionRenderer && (
|
|
154
|
-
<span
|
|
155
|
-
className={css(
|
|
156
|
-
styles.option,
|
|
157
|
-
selected && styles.optionSelected,
|
|
158
|
-
disabled && styles.optionDisabled,
|
|
159
|
-
)}
|
|
160
|
-
>
|
|
161
|
-
{children}
|
|
162
|
-
{selected && (
|
|
163
|
-
<span className={css(styles.check)}>
|
|
164
|
-
<Icon icon={check} />
|
|
165
|
-
</span>
|
|
166
|
-
)}
|
|
167
|
-
</span>
|
|
168
|
-
)}
|
|
169
|
-
</button>
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// TODO(kevinb):
|
|
175
|
-
// - keep the the option group within the page bounds
|
|
176
|
-
// - do we really want to dismiss this on scroll
|
|
177
|
-
// - how does the drop down interact with tooltips? what's the z-index order?
|
|
178
|
-
class OptionGroup extends React.Component<{
|
|
179
|
-
// An event when an option is selected
|
|
180
|
-
onSelected: (value: string) => void;
|
|
181
|
-
// The list of options to display
|
|
182
|
-
children?: Array<React.ReactElement<React.ComponentProps<typeof Option>>>;
|
|
183
|
-
// The currently selected options
|
|
184
|
-
selectedValues: Array<string>;
|
|
185
|
-
// An optional rendering function to render the details of the options
|
|
186
|
-
optionRenderer?: OptionRenderer;
|
|
187
|
-
// An override to skip the bit of top/bottom spacing around the group
|
|
188
|
-
noMargin?: boolean;
|
|
189
|
-
// An optional function to call when the dropdown should close
|
|
190
|
-
onDropdownClose?: () => void;
|
|
191
|
-
// An optional value to override the focus styling of an option.
|
|
192
|
-
// Use caution when using this - keyboard users need to know what
|
|
193
|
-
// they're focused on in order to interact with the product!
|
|
194
|
-
hideFocusState?: boolean;
|
|
195
|
-
}> {
|
|
196
|
-
// @ts-expect-error - TS2564 - Property 'focusedElement' has no initializer and is not definitely assigned in the constructor.
|
|
197
|
-
focusedElement: Element;
|
|
198
|
-
|
|
199
|
-
componentDidMount() {
|
|
200
|
-
if (this.focusedElement) {
|
|
201
|
-
findAndFocusElement(this.focusedElement);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
render(): React.ReactNode {
|
|
206
|
-
const {
|
|
207
|
-
children,
|
|
208
|
-
onSelected,
|
|
209
|
-
selectedValues,
|
|
210
|
-
optionRenderer,
|
|
211
|
-
noMargin,
|
|
212
|
-
onDropdownClose,
|
|
213
|
-
hideFocusState,
|
|
214
|
-
} = this.props;
|
|
215
|
-
|
|
216
|
-
return (
|
|
217
|
-
<div
|
|
218
|
-
// @ts-expect-error - TS2322 - Type 'Window | null' is not assignable to type 'Top<string | number> | undefined'.
|
|
219
|
-
style={{top}}
|
|
220
|
-
className={css(
|
|
221
|
-
styles.optionGroup,
|
|
222
|
-
noMargin && styles.optionGroupNoMargin,
|
|
223
|
-
)}
|
|
224
|
-
>
|
|
225
|
-
{React.Children.map(children, (child, index) => {
|
|
226
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
227
|
-
const selected = selectedValues.includes(child.props.value);
|
|
228
|
-
|
|
229
|
-
const reference =
|
|
230
|
-
selected || index === 0
|
|
231
|
-
? (node: Element) => (this.focusedElement = node)
|
|
232
|
-
: null;
|
|
233
|
-
|
|
234
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
235
|
-
return React.cloneElement(child, {
|
|
236
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
237
|
-
...child.props,
|
|
238
|
-
key: index,
|
|
239
|
-
selected: selected,
|
|
240
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
241
|
-
onClick: () => onSelected(child.props.value),
|
|
242
|
-
optionRenderer: optionRenderer,
|
|
243
|
-
ref: reference,
|
|
244
|
-
onDropdownClose: onDropdownClose,
|
|
245
|
-
hideFocusState: hideFocusState,
|
|
246
|
-
});
|
|
247
|
-
})}
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const styles = StyleSheet.create({
|
|
254
|
-
optionGroup: {
|
|
255
|
-
margin: "4px 0",
|
|
256
|
-
},
|
|
257
|
-
optionGroupNoMargin: {
|
|
258
|
-
margin: 0,
|
|
259
|
-
},
|
|
260
|
-
option: {
|
|
261
|
-
display: "flex",
|
|
262
|
-
flexDirection: "row",
|
|
263
|
-
alignItems: "center",
|
|
264
|
-
paddingLeft: 32,
|
|
265
|
-
paddingRight: 32,
|
|
266
|
-
height: optionHeight,
|
|
267
|
-
whiteSpace: "nowrap",
|
|
268
|
-
overflow: "hidden",
|
|
269
|
-
textOverflow: "ellipsis",
|
|
270
|
-
color: colors.gray17,
|
|
271
|
-
userSelect: "none",
|
|
272
|
-
|
|
273
|
-
":hover": {
|
|
274
|
-
backgroundColor: colors.gray95,
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
optionSelected: {
|
|
278
|
-
backgroundColor: colors.gray95,
|
|
279
|
-
color: "#11ACCD",
|
|
280
|
-
},
|
|
281
|
-
optionDisabled: {
|
|
282
|
-
color: colors.gray76,
|
|
283
|
-
":hover": {
|
|
284
|
-
backgroundColor: "transparent",
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
check: {
|
|
288
|
-
position: "absolute",
|
|
289
|
-
left: 11,
|
|
290
|
-
},
|
|
291
|
-
notAButton: {
|
|
292
|
-
backgroundColor: "transparent",
|
|
293
|
-
border: "none",
|
|
294
|
-
display: "block",
|
|
295
|
-
padding: 0,
|
|
296
|
-
margin: 0,
|
|
297
|
-
width: "100%",
|
|
298
|
-
// <button> uses user agent stylesheet over inherit so we need to set
|
|
299
|
-
// it explicitly.
|
|
300
|
-
font: "inherit",
|
|
301
|
-
},
|
|
302
|
-
noFocus: {
|
|
303
|
-
outline: "none",
|
|
304
|
-
},
|
|
305
|
-
cursorDefault: {
|
|
306
|
-
cursor: "default",
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
export {OptionGroup};
|
|
311
|
-
export default Option;
|