pxt-core 7.5.2 → 7.5.5
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/built/cli.js +61 -46
- package/built/pxt.js +256 -91
- package/built/pxtblockly.js +652 -657
- package/built/pxtblocks.d.ts +1 -0
- package/built/pxtblocks.js +98 -42
- package/built/pxtcompiler.js +45 -4
- package/built/pxtlib.d.ts +7 -2
- package/built/pxtlib.js +77 -39
- package/built/pxtpy.js +73 -2
- package/built/server.js +4 -0
- package/built/target.js +1 -1
- package/built/web/authcode/css/main.1cf9dc37.css +2 -0
- package/built/web/authcode/js/main.03da4c20.js +2 -0
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtblockly.js +2 -2
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxtcompiler.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtpy.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-authcode.css +6200 -0
- package/built/web/react-common-skillmap.css +1 -1
- package/built/web/rtlreact-common-skillmap.css +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/css/{main.e0620cee.chunk.css → main.73b22966.chunk.css} +1 -1
- package/built/web/skillmap/js/{2.f7cdfd75.chunk.js → 2.3e47a285.chunk.js} +2 -2
- package/built/web/skillmap/js/main.2485091f.chunk.js +1 -0
- package/common-docs/faq.md +1 -1
- package/common-docs/translate.md +2 -2
- package/docfiles/apptracking.html +1 -1
- package/docfiles/tracking.html +1 -1
- package/localtypings/projectheader.d.ts +6 -0
- package/package.json +5 -3
- package/pxtarget.json +1 -1
- package/react-common/components/controls/Button.tsx +4 -1
- package/react-common/components/controls/EditorToggle.tsx +153 -0
- package/react-common/components/controls/FocusList.tsx +120 -0
- package/react-common/components/controls/Input.tsx +4 -4
- package/react-common/components/controls/Link.tsx +36 -0
- package/react-common/components/controls/MenuBar.tsx +5 -95
- package/react-common/components/controls/MenuDropdown.tsx +4 -1
- package/react-common/components/controls/Textarea.tsx +103 -0
- package/react-common/components/share/GifInfo.tsx +63 -0
- package/react-common/components/share/GifRecorder.tsx +97 -0
- package/react-common/components/share/Share.tsx +49 -0
- package/react-common/components/share/ShareInfo.tsx +186 -0
- package/react-common/components/share/SocialButton.tsx +53 -0
- package/react-common/styles/controls/Button.less +4 -0
- package/react-common/styles/controls/EditorToggle.less +271 -0
- package/react-common/styles/controls/Modal.less +7 -5
- package/react-common/styles/controls/Textarea.less +81 -0
- package/react-common/styles/react-common-authcode-core.less +10 -0
- package/react-common/styles/react-common-authcode.less +12 -0
- package/react-common/styles/react-common-variables.less +19 -0
- package/react-common/styles/react-common.less +3 -0
- package/react-common/styles/share/share.less +116 -0
- package/theme/image-editor/imageEditor.less +8 -116
- package/webapp/public/authcode.html +1 -0
- package/webapp/public/blockly/blockly_compressed.js +554 -615
- package/webapp/public/index.html +1 -1
- package/webapp/public/run.html +32 -5
- package/webapp/public/skillmap.html +2 -2
- package/built/web/skillmap/js/main.f6866fc6.chunk.js +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ControlProps } from "../util";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { FocusList } from "./FocusList";
|
|
5
|
+
import { MenuDropdown } from "./MenuDropdown";
|
|
6
|
+
|
|
7
|
+
export interface EditorToggleProps extends ControlProps {
|
|
8
|
+
items: EditorToggleItem[];
|
|
9
|
+
selected: number;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type EditorToggleItem = BasicEditorToggleItem | DropdownEditorToggleItem;
|
|
14
|
+
|
|
15
|
+
export interface BasicEditorToggleItem {
|
|
16
|
+
label: string;
|
|
17
|
+
title: string;
|
|
18
|
+
focusable: boolean;
|
|
19
|
+
icon?: string;
|
|
20
|
+
onClick: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DropdownEditorToggleItem extends BasicEditorToggleItem {
|
|
24
|
+
items: BasicEditorToggleItem[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export const EditorToggle = (props: EditorToggleProps) => {
|
|
29
|
+
const {
|
|
30
|
+
id,
|
|
31
|
+
className,
|
|
32
|
+
ariaHidden,
|
|
33
|
+
ariaLabel,
|
|
34
|
+
role,
|
|
35
|
+
items,
|
|
36
|
+
selected
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const hasDropdown = items.some(item => isDropdownItem(item));
|
|
41
|
+
|
|
42
|
+
const onKeydown = (ev: React.KeyboardEvent) => {
|
|
43
|
+
// TODO
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="common-editor-toggle-outer">
|
|
48
|
+
<EditorToggleAccessibleMenu {...props} />
|
|
49
|
+
<div id={id}
|
|
50
|
+
className={classList("common-editor-toggle", hasDropdown && "has-dropdown", className)}
|
|
51
|
+
role={role || "tablist"}
|
|
52
|
+
aria-hidden={true}
|
|
53
|
+
aria-label={ariaLabel}>
|
|
54
|
+
{items.map((item, index) => {
|
|
55
|
+
const isSelected = selected === index;
|
|
56
|
+
return (
|
|
57
|
+
<div key={index}
|
|
58
|
+
className={classList(
|
|
59
|
+
"common-editor-toggle-item",
|
|
60
|
+
isSelected && "selected",
|
|
61
|
+
isDropdownItem(item) && "common-editor-toggle-item-dropdown",
|
|
62
|
+
)} >
|
|
63
|
+
<ToggleButton item={item} isSelected={isSelected} onKeydown={onKeydown} />
|
|
64
|
+
{ isDropdownItem(item) &&
|
|
65
|
+
<MenuDropdown
|
|
66
|
+
id="toggle-dropdown"
|
|
67
|
+
className="toggle-dropdown"
|
|
68
|
+
icon="fas fa-chevron-down"
|
|
69
|
+
title={lf("More options")}
|
|
70
|
+
tabIndex={-1}
|
|
71
|
+
ariaHidden={true}
|
|
72
|
+
items={item.items.map(item => ({
|
|
73
|
+
title: item.title,
|
|
74
|
+
label: item.label,
|
|
75
|
+
onClick: item.onClick,
|
|
76
|
+
leftIcon: item.icon
|
|
77
|
+
}))}
|
|
78
|
+
/>
|
|
79
|
+
}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
|
|
84
|
+
<div className="common-editor-toggle-handle"
|
|
85
|
+
aria-hidden={true}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ToggleButtonProps {
|
|
93
|
+
item: BasicEditorToggleItem;
|
|
94
|
+
isSelected?: boolean;
|
|
95
|
+
onKeydown: (ev: React.KeyboardEvent) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ToggleButton = (props: ToggleButtonProps) => {
|
|
99
|
+
const { item, isSelected, onKeydown } = props;
|
|
100
|
+
const { label, title, onClick, icon, focusable } = item;
|
|
101
|
+
|
|
102
|
+
return <Button
|
|
103
|
+
role={focusable ? "tab" : undefined}
|
|
104
|
+
tabIndex={-1}
|
|
105
|
+
onKeydown={onKeydown}
|
|
106
|
+
label={label}
|
|
107
|
+
title={title}
|
|
108
|
+
onClick={onClick}
|
|
109
|
+
leftIcon={icon}
|
|
110
|
+
ariaHidden={true}
|
|
111
|
+
/>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
interface ToggleTab extends BasicEditorToggleItem {
|
|
116
|
+
selected?: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const EditorToggleAccessibleMenu = (props: EditorToggleProps) => {
|
|
120
|
+
const { items, id, selected, ariaHidden } = props;
|
|
121
|
+
|
|
122
|
+
const tabs = items.reduce((prev, current, index) => {
|
|
123
|
+
const next: ToggleTab[] = [...prev]
|
|
124
|
+
next.push({...current});
|
|
125
|
+
|
|
126
|
+
// The selected item will always be a top-level option, not in a dropdown
|
|
127
|
+
if (selected === index) next[next.length - 1].selected = true;
|
|
128
|
+
|
|
129
|
+
if (isDropdownItem(current)) {
|
|
130
|
+
next.push(...current.items.filter(i => i.focusable))
|
|
131
|
+
}
|
|
132
|
+
return next;
|
|
133
|
+
}, [] as ToggleTab[]);
|
|
134
|
+
|
|
135
|
+
return <FocusList id={id} role="tablist" className="common-toggle-accessibility" childTabStopId={id + "-selected"}>
|
|
136
|
+
{tabs.map((item, index) =>
|
|
137
|
+
<Button
|
|
138
|
+
key={index}
|
|
139
|
+
className={item.selected ? "selected" : undefined}
|
|
140
|
+
id={item.selected ? id + "-selected" : undefined}
|
|
141
|
+
role="tab"
|
|
142
|
+
title={item.title}
|
|
143
|
+
label={item.label}
|
|
144
|
+
onClick={item.onClick}
|
|
145
|
+
ariaSelected={item.selected}
|
|
146
|
+
ariaHidden={ariaHidden}/>
|
|
147
|
+
)}
|
|
148
|
+
</FocusList>
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isDropdownItem(item: EditorToggleItem): item is DropdownEditorToggleItem {
|
|
152
|
+
return !!(item as DropdownEditorToggleItem).items?.length
|
|
153
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ContainerProps } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface FocusListProps extends ContainerProps {
|
|
5
|
+
role: string;
|
|
6
|
+
childTabStopId?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A list of focusable items that represents a single tab stop in the tab order. The
|
|
11
|
+
* children of the list can be navigated between using the arrow keys. Any child with
|
|
12
|
+
* a tabindex other than -1 will be included in the list.
|
|
13
|
+
*
|
|
14
|
+
* If childTabStopId is specified, then the tab stop will be placed on the child with
|
|
15
|
+
* the given id instead of the outer div.
|
|
16
|
+
*/
|
|
17
|
+
export const FocusList = (props: FocusListProps) => {
|
|
18
|
+
const {
|
|
19
|
+
id,
|
|
20
|
+
className,
|
|
21
|
+
role,
|
|
22
|
+
ariaHidden,
|
|
23
|
+
ariaLabel,
|
|
24
|
+
childTabStopId,
|
|
25
|
+
children,
|
|
26
|
+
} = props;
|
|
27
|
+
|
|
28
|
+
let focusableElements: HTMLElement[];
|
|
29
|
+
let focusList: HTMLDivElement;
|
|
30
|
+
|
|
31
|
+
const handleRef = (ref: HTMLDivElement) => {
|
|
32
|
+
if (!ref || focusList) return;
|
|
33
|
+
|
|
34
|
+
focusList = ref;
|
|
35
|
+
|
|
36
|
+
const focusable = ref.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
|
|
37
|
+
focusableElements = [];
|
|
38
|
+
|
|
39
|
+
for (const element of focusable.values()) {
|
|
40
|
+
focusableElements.push(element as HTMLElement);
|
|
41
|
+
|
|
42
|
+
// Remove them from the tab order, menu items are navigable using the arrow keys
|
|
43
|
+
element.setAttribute("tabindex", "-1");
|
|
44
|
+
element.setAttribute("data-isfocusable", "true");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (childTabStopId) {
|
|
48
|
+
const childTabStop = focusList.querySelector("#" + childTabStopId);
|
|
49
|
+
|
|
50
|
+
if (childTabStop) {
|
|
51
|
+
childTabStop.setAttribute("tabindex", "0");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
|
|
57
|
+
if (!focusableElements?.length) return;
|
|
58
|
+
|
|
59
|
+
const target = document.activeElement as HTMLElement;
|
|
60
|
+
const index = focusableElements.indexOf(target);
|
|
61
|
+
|
|
62
|
+
if (index === -1 && target !== focusList) return;
|
|
63
|
+
|
|
64
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
|
|
68
|
+
if (target.click) {
|
|
69
|
+
target.click();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// SVG Elements
|
|
73
|
+
target.dispatchEvent(new Event("click"));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (e.key === "ArrowRight") {
|
|
77
|
+
if (index === focusableElements.length - 1 || target === focusList) {
|
|
78
|
+
focusableElements[0].focus();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
focusableElements[index + 1].focus();
|
|
82
|
+
}
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
}
|
|
86
|
+
else if (e.key === "ArrowLeft") {
|
|
87
|
+
if (index === 0 || target === focusList) {
|
|
88
|
+
focusableElements[focusableElements.length - 1].focus();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
focusableElements[Math.max(index - 1, 0)].focus();
|
|
92
|
+
}
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
}
|
|
96
|
+
else if (e.key === "Home") {
|
|
97
|
+
focusableElements[0].focus();
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
}
|
|
101
|
+
else if (e.key === "End") {
|
|
102
|
+
focusableElements[focusableElements.length - 1].focus();
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div id={id}
|
|
110
|
+
className={className}
|
|
111
|
+
role={role}
|
|
112
|
+
tabIndex={childTabStopId ? undefined : 0}
|
|
113
|
+
onKeyDown={onKeyDown}
|
|
114
|
+
ref={handleRef}
|
|
115
|
+
aria-hidden={ariaHidden}
|
|
116
|
+
aria-label={ariaLabel}>
|
|
117
|
+
{children}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -21,7 +21,7 @@ export interface InputProps extends ControlProps {
|
|
|
21
21
|
onIconClick?: (value: string) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export
|
|
24
|
+
export const Input = (props: InputProps) => {
|
|
25
25
|
const {
|
|
26
26
|
id,
|
|
27
27
|
className,
|
|
@@ -78,7 +78,7 @@ export function Input(props: InputProps) {
|
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
80
|
<div className={classList("common-input-wrapper", disabled && "disabled", className)}>
|
|
81
|
-
{label && <label className="common-input-label">
|
|
81
|
+
{label && <label className="common-input-label" htmlFor={id}>
|
|
82
82
|
{label}
|
|
83
83
|
</label>}
|
|
84
84
|
<div className="common-input-group">
|
|
@@ -86,8 +86,8 @@ export function Input(props: InputProps) {
|
|
|
86
86
|
id={id}
|
|
87
87
|
className={classList("common-input", icon && "has-icon")}
|
|
88
88
|
title={title}
|
|
89
|
-
role={role || "
|
|
90
|
-
tabIndex={disabled ?
|
|
89
|
+
role={role || "textbox"}
|
|
90
|
+
tabIndex={disabled ? -1 : 0}
|
|
91
91
|
aria-label={ariaLabel}
|
|
92
92
|
aria-hidden={ariaHidden}
|
|
93
93
|
type={type || "text"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ContainerProps } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface LinkProps extends ContainerProps {
|
|
5
|
+
href: string;
|
|
6
|
+
target?: "_self" | "_blank" | "_parent" | "_top";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Link = (props: LinkProps) => {
|
|
10
|
+
const {
|
|
11
|
+
id,
|
|
12
|
+
className,
|
|
13
|
+
ariaLabel,
|
|
14
|
+
href,
|
|
15
|
+
target,
|
|
16
|
+
children
|
|
17
|
+
} = props;
|
|
18
|
+
|
|
19
|
+
const classes = classList(
|
|
20
|
+
"common-link",
|
|
21
|
+
className
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<a
|
|
26
|
+
id={id}
|
|
27
|
+
className={classes}
|
|
28
|
+
aria-label={ariaLabel}
|
|
29
|
+
href={href}
|
|
30
|
+
target={target}
|
|
31
|
+
rel={target === "_blank" ? "noopener noreferrer" : ""}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</a>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -1,101 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { classList, ContainerProps } from "../util";
|
|
3
|
+
import { FocusList } from "./FocusList";
|
|
3
4
|
|
|
4
5
|
export interface MenuBarProps extends ContainerProps {
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
export const MenuBar = (props: MenuBarProps) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
className,
|
|
11
|
-
role,
|
|
12
|
-
ariaHidden,
|
|
13
|
-
ariaLabel,
|
|
14
|
-
children
|
|
15
|
-
} = props;
|
|
16
|
-
|
|
17
|
-
let focusableElements: HTMLElement[];
|
|
18
|
-
let menubar: HTMLDivElement;
|
|
19
|
-
|
|
20
|
-
const handleRef = (ref: HTMLDivElement) => {
|
|
21
|
-
if (!ref || menubar) return;
|
|
22
|
-
|
|
23
|
-
menubar = ref;
|
|
24
|
-
|
|
25
|
-
const focusable = ref.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
|
|
26
|
-
focusableElements = [];
|
|
27
|
-
|
|
28
|
-
for (const element of focusable.values()) {
|
|
29
|
-
focusableElements.push(element as HTMLElement);
|
|
30
|
-
|
|
31
|
-
// Remove them from the tab order, menu items are navigable using the arrow keys
|
|
32
|
-
element.setAttribute("tabindex", "-1");
|
|
33
|
-
element.setAttribute("data-isfocusable", "true");
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
|
|
38
|
-
if (!focusableElements?.length) return;
|
|
39
|
-
|
|
40
|
-
const target = document.activeElement as HTMLElement;
|
|
41
|
-
const index = focusableElements.indexOf(target);
|
|
42
|
-
|
|
43
|
-
if (index === -1 && target !== menubar) return;
|
|
44
|
-
|
|
45
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
46
|
-
e.preventDefault();
|
|
47
|
-
e.stopPropagation();
|
|
48
|
-
|
|
49
|
-
if (target.click) {
|
|
50
|
-
target.click();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
// SVG Elements
|
|
54
|
-
target.dispatchEvent(new Event("click"));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else if (e.key === "ArrowRight") {
|
|
58
|
-
if (index === focusableElements.length - 1 || target === menubar) {
|
|
59
|
-
focusableElements[0].focus();
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
focusableElements[index + 1].focus();
|
|
63
|
-
}
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
e.stopPropagation();
|
|
66
|
-
}
|
|
67
|
-
else if (e.key === "ArrowLeft") {
|
|
68
|
-
if (index === 0 || target === menubar) {
|
|
69
|
-
focusableElements[focusableElements.length - 1].focus();
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
focusableElements[Math.max(index - 1, 0)].focus();
|
|
73
|
-
}
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
e.stopPropagation();
|
|
76
|
-
}
|
|
77
|
-
else if (e.key === "Home") {
|
|
78
|
-
focusableElements[0].focus();
|
|
79
|
-
e.preventDefault();
|
|
80
|
-
e.stopPropagation();
|
|
81
|
-
}
|
|
82
|
-
else if (e.key === "End") {
|
|
83
|
-
focusableElements[focusableElements.length - 1].focus();
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
e.stopPropagation();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<div id={id}
|
|
91
|
-
className={classList("common-menubar", className)}
|
|
92
|
-
role={role || "menubar"}
|
|
93
|
-
tabIndex={0}
|
|
94
|
-
onKeyDown={onKeyDown}
|
|
95
|
-
ref={handleRef}
|
|
96
|
-
aria-hidden={ariaHidden}
|
|
97
|
-
aria-label={ariaLabel}>
|
|
98
|
-
{children}
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
8
|
+
export const MenuBar = (props: MenuBarProps) =>
|
|
9
|
+
<FocusList {...props}
|
|
10
|
+
role="menubar"
|
|
11
|
+
className={classList("common-menubar", props.className)} />
|
|
@@ -15,6 +15,7 @@ export interface MenuDropdownProps extends ControlProps {
|
|
|
15
15
|
label?: string | JSX.Element;
|
|
16
16
|
title: string;
|
|
17
17
|
icon?: string;
|
|
18
|
+
tabIndex?: number;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export const MenuDropdown = (props: MenuDropdownProps) => {
|
|
@@ -27,7 +28,8 @@ export const MenuDropdown = (props: MenuDropdownProps) => {
|
|
|
27
28
|
items,
|
|
28
29
|
label,
|
|
29
30
|
title,
|
|
30
|
-
icon
|
|
31
|
+
icon,
|
|
32
|
+
tabIndex
|
|
31
33
|
} = props;
|
|
32
34
|
|
|
33
35
|
const [ expanded, setExpanded ] = React.useState(false);
|
|
@@ -66,6 +68,7 @@ export const MenuDropdown = (props: MenuDropdownProps) => {
|
|
|
66
68
|
<Button
|
|
67
69
|
id={id}
|
|
68
70
|
label={label}
|
|
71
|
+
tabIndex={tabIndex}
|
|
69
72
|
buttonRef={handleButtonRef}
|
|
70
73
|
title={title}
|
|
71
74
|
leftIcon={icon}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ControlProps } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface TextareaProps extends ControlProps {
|
|
5
|
+
initialValue?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
autoComplete?: boolean;
|
|
10
|
+
cols?: number;
|
|
11
|
+
rows?: number;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
minLength?: number;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
resize?: "both" | "horizontal" | "vertical";
|
|
16
|
+
wrap?: "hard" | "soft" | "off";
|
|
17
|
+
|
|
18
|
+
onChange?: (newValue: string) => void;
|
|
19
|
+
onEnterKey?: (value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Textarea = (props: TextareaProps) => {
|
|
23
|
+
const {
|
|
24
|
+
id,
|
|
25
|
+
className,
|
|
26
|
+
role,
|
|
27
|
+
ariaHidden,
|
|
28
|
+
ariaLabel,
|
|
29
|
+
initialValue,
|
|
30
|
+
label,
|
|
31
|
+
title,
|
|
32
|
+
placeholder,
|
|
33
|
+
autoComplete,
|
|
34
|
+
cols,
|
|
35
|
+
rows,
|
|
36
|
+
disabled,
|
|
37
|
+
minLength,
|
|
38
|
+
readOnly,
|
|
39
|
+
resize,
|
|
40
|
+
wrap,
|
|
41
|
+
onChange,
|
|
42
|
+
onEnterKey
|
|
43
|
+
} = props;
|
|
44
|
+
|
|
45
|
+
const [value, setValue] = React.useState(initialValue || "");
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
setValue(initialValue)
|
|
49
|
+
}, [initialValue])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const changeHandler = (e: React.ChangeEvent<any>) => {
|
|
53
|
+
const newValue = (e.target as any).value;
|
|
54
|
+
if (!readOnly && (value !== newValue)) {
|
|
55
|
+
setValue(newValue);
|
|
56
|
+
}
|
|
57
|
+
if (onChange) {
|
|
58
|
+
onChange(newValue);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const enterKeyHandler = (e: React.KeyboardEvent) => {
|
|
63
|
+
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
|
|
64
|
+
if (charCode === /*enter*/ 13 || charCode === /*space*/ 32) {
|
|
65
|
+
if (onEnterKey) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
onEnterKey(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={classList("common-textarea-wrapper", disabled && "disabled", resize && `resize-${resize}`, className)}>
|
|
74
|
+
{label && <label className="common-textarea-label">
|
|
75
|
+
{label}
|
|
76
|
+
</label>}
|
|
77
|
+
<div className="common-textarea-group">
|
|
78
|
+
<textarea
|
|
79
|
+
id={id}
|
|
80
|
+
className={"common-textarea"}
|
|
81
|
+
title={title}
|
|
82
|
+
role={role || "textbox"}
|
|
83
|
+
tabIndex={disabled ? -1 : 0}
|
|
84
|
+
aria-label={ariaLabel}
|
|
85
|
+
aria-hidden={ariaHidden}
|
|
86
|
+
placeholder={placeholder}
|
|
87
|
+
value={value || ''}
|
|
88
|
+
cols={cols}
|
|
89
|
+
rows={rows}
|
|
90
|
+
minLength={minLength}
|
|
91
|
+
wrap={wrap}
|
|
92
|
+
readOnly={!!readOnly}
|
|
93
|
+
onChange={changeHandler}
|
|
94
|
+
onKeyDown={enterKeyHandler}
|
|
95
|
+
autoComplete={autoComplete ? "" : "off"}
|
|
96
|
+
autoCorrect={autoComplete ? "" : "off"}
|
|
97
|
+
autoCapitalize={autoComplete ? "" : "off"}
|
|
98
|
+
spellCheck={autoComplete}
|
|
99
|
+
disabled={disabled} />
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "../controls/Button";
|
|
3
|
+
import { GifRecorder } from "./GifRecorder";
|
|
4
|
+
|
|
5
|
+
export interface GifInfoProps {
|
|
6
|
+
initialUri?: string;
|
|
7
|
+
|
|
8
|
+
onApply: (uri: string) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
|
|
11
|
+
screenshotAsync?: () => Promise<string>;
|
|
12
|
+
gifRecordAsync?: () => Promise<void>;
|
|
13
|
+
gifRenderAsync?: () => Promise<string | void>;
|
|
14
|
+
gifAddFrame?: (dataUri: ImageData, delay?: number) => boolean;
|
|
15
|
+
|
|
16
|
+
registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
|
|
17
|
+
unregisterSimulatorMsgHandler?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const GifInfo = (props: GifInfoProps) => {
|
|
21
|
+
const { initialUri, onApply, onCancel, screenshotAsync, gifRecordAsync, gifRenderAsync, gifAddFrame,
|
|
22
|
+
registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
|
|
23
|
+
const [ uri, setUri ] = React.useState(initialUri);
|
|
24
|
+
|
|
25
|
+
const handleApplyClick = (evt?: any) => {
|
|
26
|
+
onApply(uri);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleScreenshotClick = async () => {
|
|
30
|
+
const screenshotUri = await screenshotAsync();
|
|
31
|
+
setUri(screenshotUri);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleRecordStopClick = async () => {
|
|
35
|
+
const gifUri = await gifRenderAsync();
|
|
36
|
+
if (gifUri) setUri(gifUri);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return <>
|
|
40
|
+
<span className="thumbnail-label">{lf("Current Thumbnail")}</span>
|
|
41
|
+
<div className="thumbnail-image">
|
|
42
|
+
{uri
|
|
43
|
+
? <img src={uri} />
|
|
44
|
+
: <div className="thumbnail-placeholder" />
|
|
45
|
+
}
|
|
46
|
+
</div>
|
|
47
|
+
<div className="thumbnail-actions">
|
|
48
|
+
<Button className="primary"
|
|
49
|
+
title={lf("Apply")}
|
|
50
|
+
label={lf("Apply")}
|
|
51
|
+
onClick={handleApplyClick} />
|
|
52
|
+
<Button title={lf("Cancel")}
|
|
53
|
+
label={lf("Cancel")}
|
|
54
|
+
onClick={onCancel} />
|
|
55
|
+
</div>
|
|
56
|
+
<GifRecorder onScreenshot={screenshotAsync ? handleScreenshotClick : undefined}
|
|
57
|
+
onRecordStart={gifRecordAsync}
|
|
58
|
+
onRecordStop={handleRecordStopClick}
|
|
59
|
+
onGifFrame={gifAddFrame}
|
|
60
|
+
registerSimulatorMsgHandler={registerSimulatorMsgHandler}
|
|
61
|
+
unregisterSimulatorMsgHandler={unregisterSimulatorMsgHandler} />
|
|
62
|
+
</>
|
|
63
|
+
}
|