pxt-core 7.4.13 → 7.4.17

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 (59) hide show
  1. package/built/buildengine.js +1 -1
  2. package/built/cli.js +29 -6
  3. package/built/gdb.js +1 -1
  4. package/built/nodeutil.d.ts +1 -1
  5. package/built/nodeutil.js +2 -2
  6. package/built/pxt.js +30 -6
  7. package/built/pxtblockly.js +5 -2
  8. package/built/pxtblocks.d.ts +1 -0
  9. package/built/pxtblocks.js +5 -2
  10. package/built/pxteditor.d.ts +6 -1
  11. package/built/pxteditor.js +5 -0
  12. package/built/pxtlib.js +1 -0
  13. package/built/target.js +1 -1
  14. package/built/web/main.js +1 -1
  15. package/built/web/pxtapp.js +1 -1
  16. package/built/web/pxtasseteditor.js +1 -1
  17. package/built/web/pxtblockly.js +1 -1
  18. package/built/web/pxtblocks.js +1 -1
  19. package/built/web/pxteditor.js +1 -1
  20. package/built/web/pxtembed.js +2 -2
  21. package/built/web/pxtlib.js +1 -1
  22. package/built/web/pxtworker.js +1 -1
  23. package/built/web/react-common-skillmap.css +13 -1
  24. package/built/web/rtlreact-common-skillmap.css +13 -1
  25. package/built/web/rtlsemantic.css +14 -2
  26. package/built/web/semantic.css +14 -2
  27. package/built/web/skillmap/css/main.cc257f3c.chunk.css +1 -0
  28. package/built/web/skillmap/js/2.261d5eab.chunk.js +2 -0
  29. package/built/web/skillmap/js/main.7c45ef9f.chunk.js +1 -0
  30. package/package.json +4 -3
  31. package/react-common/components/controls/Button.tsx +85 -0
  32. package/react-common/components/controls/Checkbox.tsx +47 -0
  33. package/react-common/components/controls/FocusTrap.tsx +92 -0
  34. package/react-common/components/controls/Input.tsx +117 -0
  35. package/react-common/components/controls/List.tsx +28 -0
  36. package/react-common/components/controls/MenuBar.tsx +94 -0
  37. package/react-common/components/controls/MenuDropdown.tsx +108 -0
  38. package/react-common/components/controls/Modal.tsx +108 -0
  39. package/react-common/components/profile/Profile.tsx +1 -1
  40. package/react-common/components/profile/UserPane.tsx +12 -8
  41. package/react-common/components/util.tsx +35 -0
  42. package/react-common/styles/controls/Button.less +192 -0
  43. package/react-common/styles/controls/Checkbox.less +24 -0
  44. package/react-common/styles/controls/Icon.less +11 -0
  45. package/react-common/styles/controls/Input.less +113 -0
  46. package/react-common/styles/controls/List.less +12 -0
  47. package/react-common/styles/controls/MenuDropdown.less +54 -0
  48. package/react-common/styles/controls/Modal.less +134 -0
  49. package/react-common/styles/controls/Spinner.less +24 -0
  50. package/react-common/styles/profile/profile.less +32 -0
  51. package/react-common/styles/react-common-skillmap.less +1 -1
  52. package/react-common/styles/react-common-variables.less +67 -0
  53. package/react-common/styles/react-common.less +13 -1
  54. package/theme/asset-editor.less +13 -29
  55. package/webapp/public/skillmap.html +2 -2
  56. package/built/web/skillmap/css/main.96b1b3f1.chunk.css +0 -1
  57. package/built/web/skillmap/js/2.7dd06a3a.chunk.js +0 -2
  58. package/built/web/skillmap/js/main.b96caef3.chunk.js +0 -1
  59. package/react-common/components/Checkbox.tsx +0 -25
@@ -0,0 +1,117 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+
4
+ import { Button } from "./Button";
5
+
6
+ export interface InputProps extends ControlProps {
7
+ initialValue?: string;
8
+ label?: string;
9
+ title?: string;
10
+ placeholder?: string;
11
+ icon?: string;
12
+ iconTitle?: string;
13
+ disabled?: boolean;
14
+ type?: string;
15
+ readOnly?: boolean;
16
+ autoComplete?: boolean;
17
+ selectOnClick?: boolean;
18
+
19
+ onChange?: (newValue: string) => void;
20
+ onEnterKey?: (value: string) => void;
21
+ onIconClick?: (value: string) => void;
22
+ }
23
+
24
+ export function Input(props: InputProps) {
25
+ const {
26
+ id,
27
+ className,
28
+ role,
29
+ ariaHidden,
30
+ ariaLabel,
31
+ initialValue,
32
+ label,
33
+ title,
34
+ placeholder,
35
+ icon,
36
+ iconTitle,
37
+ disabled,
38
+ type,
39
+ readOnly,
40
+ autoComplete,
41
+ selectOnClick,
42
+ onChange,
43
+ onEnterKey,
44
+ onIconClick
45
+ } = props;
46
+
47
+ const [value, setValue] = React.useState(initialValue || "");
48
+
49
+ const clickHandler = (evt: React.MouseEvent<any>) => {
50
+ if (selectOnClick) {
51
+ (evt.target as any).select()
52
+ }
53
+ }
54
+
55
+ const changeHandler = (e: React.ChangeEvent<any>) => {
56
+ const newValue = (e.target as any).value;
57
+ if (!readOnly && (value !== newValue)) {
58
+ setValue(newValue);
59
+ }
60
+ if (onChange) {
61
+ onChange(newValue);
62
+ }
63
+ }
64
+
65
+ const enterKeyHandler = (e: React.KeyboardEvent) => {
66
+ const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
67
+ if (charCode === /*enter*/13 || charCode === /*space*/32) {
68
+ if (onEnterKey) {
69
+ e.preventDefault();
70
+ onEnterKey(value);
71
+ }
72
+ }
73
+ }
74
+
75
+ const iconClickHandler = () => {
76
+ if (onIconClick) onIconClick(value);
77
+ }
78
+
79
+ return (
80
+ <div className={classList("common-input-wrapper", disabled && "disabled", className)}>
81
+ {label && <label className="common-input-label">
82
+ {label}
83
+ </label>}
84
+ <div className="common-input-group">
85
+ <input
86
+ id={id}
87
+ className={classList("common-input", icon && "has-icon")}
88
+ title={title}
89
+ role={role || "button"}
90
+ tabIndex={disabled ? 0 : -1}
91
+ aria-label={ariaLabel}
92
+ aria-hidden={ariaHidden}
93
+ type={type || "text"}
94
+ placeholder={placeholder}
95
+ value={value || ''}
96
+ readOnly={!!readOnly}
97
+ onClick={clickHandler}
98
+ onChange={changeHandler}
99
+ onKeyDown={enterKeyHandler}
100
+ autoComplete={autoComplete ? "" : "off"}
101
+ autoCorrect={autoComplete ? "" : "off"}
102
+ autoCapitalize={autoComplete ? "" : "off"}
103
+ spellCheck={autoComplete}
104
+ disabled={disabled} />
105
+ {icon && (onIconClick
106
+ ? <Button
107
+ leftIcon={icon}
108
+ title={iconTitle}
109
+ disabled={disabled}
110
+ onClick={iconClickHandler} />
111
+ : <i
112
+ className={icon}
113
+ aria-hidden={true} />) }
114
+ </div>
115
+ </div>
116
+ );
117
+ }
@@ -0,0 +1,28 @@
1
+ import * as React from "react";
2
+ import { classList, ContainerProps } from "../util";
3
+
4
+ export interface ListProps extends ContainerProps {
5
+ }
6
+
7
+ export const List = (props: ListProps) => {
8
+ const {
9
+ id,
10
+ className,
11
+ ariaHidden,
12
+ ariaLabel,
13
+ role
14
+ } = props;
15
+
16
+ return <div
17
+ id={id}
18
+ aria-hidden={ariaHidden}
19
+ aria-label={ariaLabel}
20
+ role={role}
21
+ className={classList("common-list", className)}>
22
+ {React.Children.map(props.children, (child, index) =>
23
+ <div key={index} className="common-list-item">
24
+ {child}
25
+ </div>
26
+ )}
27
+ </div>
28
+ }
@@ -0,0 +1,94 @@
1
+ import * as React from "react";
2
+ import { classList, ContainerProps } from "../util";
3
+
4
+ export interface MenuBarProps extends ContainerProps {
5
+ }
6
+
7
+ export const MenuBar = (props: MenuBarProps) => {
8
+ const {
9
+ id,
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
+ target.click();
49
+ }
50
+ else if (e.key === "ArrowRight") {
51
+ if (index === focusableElements.length - 1 || target === menubar) {
52
+ focusableElements[0].focus();
53
+ }
54
+ else {
55
+ focusableElements[index + 1].focus();
56
+ }
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ }
60
+ else if (e.key === "ArrowLeft") {
61
+ if (index === 0 || target === menubar) {
62
+ focusableElements[focusableElements.length - 1].focus();
63
+ }
64
+ else {
65
+ focusableElements[Math.max(index - 1, 0)].focus();
66
+ }
67
+ e.preventDefault();
68
+ e.stopPropagation();
69
+ }
70
+ else if (e.key === "Home") {
71
+ focusableElements[0].focus();
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ }
75
+ else if (e.key === "End") {
76
+ focusableElements[focusableElements.length - 1].focus();
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+ }
80
+ }
81
+
82
+ return (
83
+ <div id={id}
84
+ className={classList("common-menubar", className)}
85
+ role={role || "menubar"}
86
+ tabIndex={0}
87
+ onKeyDown={onKeyDown}
88
+ ref={handleRef}
89
+ aria-hidden={ariaHidden}
90
+ aria-label={ariaLabel}>
91
+ {children}
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1,108 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+ import { Button, ButtonProps } from "./Button";
4
+ import { FocusTrap } from "./FocusTrap";
5
+
6
+ export interface MenuItem extends ButtonProps {
7
+ role?: "menuitem" | undefined;
8
+ ariaPosInSet?: undefined;
9
+ ariaSetSize?: undefined;
10
+ }
11
+
12
+ export interface MenuDropdownProps extends ControlProps {
13
+ id: string;
14
+ items: MenuItem[];
15
+ label?: string | JSX.Element;
16
+ title: string;
17
+ icon?: string;
18
+ }
19
+
20
+ export const MenuDropdown = (props: MenuDropdownProps) => {
21
+ const {
22
+ id,
23
+ className,
24
+ ariaHidden,
25
+ ariaLabel,
26
+ role,
27
+ items,
28
+ label,
29
+ title,
30
+ icon
31
+ } = props;
32
+
33
+ const [ expanded, setExpanded ] = React.useState(false);
34
+
35
+ let container: HTMLDivElement;
36
+ let expandButton: HTMLButtonElement;
37
+
38
+ const handleContainerRef = (ref: HTMLDivElement) => {
39
+ if (!ref) return;
40
+ container = ref;
41
+ }
42
+
43
+ const handleButtonRef = (ref: HTMLButtonElement) => {
44
+ if (!ref) return;
45
+ expandButton = ref;
46
+ }
47
+
48
+ const onMenuButtonClick = () => {
49
+ setExpanded(!expanded);
50
+ }
51
+
52
+ const onSubpaneEscape = () => {
53
+ setExpanded(false);
54
+ if (expandButton) expandButton.focus();
55
+ }
56
+
57
+ const onBlur = (e: React.FocusEvent) => {
58
+ if (!container) return;
59
+ if (expanded && !container.contains(e.relatedTarget as HTMLElement)) setExpanded(false);
60
+ }
61
+
62
+ const classes = classList("common-menu-dropdown", className);
63
+ const menuId = id + "-menu";
64
+
65
+ return <div className={classes} ref={handleContainerRef} onBlur={onBlur}>
66
+ <Button
67
+ id={id}
68
+ label={label}
69
+ buttonRef={handleButtonRef}
70
+ title={title}
71
+ leftIcon={icon}
72
+ role={role || "menuitem"}
73
+ className={classList("menu-button", expanded && "expanded")}
74
+ onClick={onMenuButtonClick}
75
+ ariaHasPopup="true"
76
+ ariaExpanded={expanded}
77
+ ariaControls={expanded ? menuId : undefined}
78
+ ariaLabel={ariaLabel}
79
+ ariaHidden={ariaHidden}
80
+ />
81
+ {expanded &&
82
+ <div role="menu"
83
+ className="common-menu-dropdown-pane"
84
+ tabIndex={0}
85
+ id={menuId}
86
+ aria-labelledby={id}>
87
+ <FocusTrap arrowKeyNavigation={true} onEscape={onSubpaneEscape}>
88
+ <ul role="presentation">
89
+ { items.map((item, index) =>
90
+ <li key={index} role="presentation">
91
+ <Button
92
+ {...item}
93
+ className={classList("common-menu-dropdown-item", item.className)}
94
+ onClick={() => {
95
+ setExpanded(false);
96
+ item.onClick();
97
+ }}
98
+ role="menuitem"
99
+ ariaPosInSet={index + 1}
100
+ ariaSetSize={items.length}/>
101
+ </li>
102
+ )}
103
+ </ul>
104
+ </FocusTrap>
105
+ </div>
106
+ }
107
+ </div>
108
+ }
@@ -0,0 +1,108 @@
1
+ import React = require("react");
2
+ import { classList, ContainerProps } from "../util";
3
+ import { Button } from "./Button";
4
+ import { FocusTrap } from "./FocusTrap";
5
+
6
+ export interface ModalAction {
7
+ label: string;
8
+ className?: string;
9
+ disabled?: boolean;
10
+ icon?: string;
11
+ xicon?: boolean;
12
+ onClick: () => void;
13
+ url?: string;
14
+
15
+ // TODO: It would be nice to make fullscreen modals their own thing and deprecate this prop. right
16
+ // now it's required to render the back arrow
17
+ fullscreen?: boolean;
18
+ }
19
+
20
+ export interface ModalProps extends ContainerProps {
21
+ title: string;
22
+ ariaDescribedBy?: string;
23
+ actions?: ModalAction[];
24
+ onClose?: () => void;
25
+ fullscreen?: boolean;
26
+ }
27
+
28
+ export const Modal = (props: ModalProps) => {
29
+ const {
30
+ children,
31
+ id,
32
+ className,
33
+ ariaLabel,
34
+ ariaHidden,
35
+ ariaDescribedBy,
36
+ role,
37
+ title,
38
+ actions,
39
+ onClose,
40
+ fullscreen
41
+ } = props;
42
+
43
+ const closeClickHandler = (e?: React.MouseEvent<HTMLButtonElement>) => {
44
+ if (onClose) onClose();
45
+ }
46
+
47
+ const classes = classList(
48
+ "common-modal-container",
49
+ fullscreen && "fullscreen",
50
+ className
51
+ );
52
+
53
+ return <FocusTrap className={classes} onEscape={closeClickHandler}>
54
+ <div id={id}
55
+ className="common-modal"
56
+ role={role || "dialog"}
57
+ aria-hidden={ariaHidden}
58
+ aria-label={ariaLabel}
59
+ aria-describedby={ariaDescribedBy}
60
+ aria-labelledby="modal-title">
61
+ <div className="common-modal-header">
62
+ {fullscreen &&
63
+ <div className="common-modal-back">
64
+ <Button
65
+ className="menu-button"
66
+ onClick={closeClickHandler}
67
+ title={lf("Go Back")}
68
+ label={lf("Go Back")}
69
+ leftIcon="fas fa-arrow-left"
70
+ />
71
+ </div>
72
+ }
73
+ <div id="modal-title" className="common-modal-title">
74
+ {title}
75
+ </div>
76
+ {!fullscreen &&
77
+ <div className="common-modal-close">
78
+ <Button
79
+ className="menu-button"
80
+ onClick={closeClickHandler}
81
+ title={lf("Close")}
82
+ rightIcon="fas fa-times-circle"
83
+ />
84
+ </div>
85
+ }
86
+ </div>
87
+ <div className="common-modal-body">
88
+ {children}
89
+ </div>
90
+ {actions?.length &&
91
+ <div className="common-modal-footer">
92
+ { actions.map((action, index) =>
93
+ <Button
94
+ key={index}
95
+ className="primary inverted"
96
+ disabled={action.disabled}
97
+ onClick={action.onClick}
98
+ href={action.url}
99
+ label={action.label}
100
+ title={action.label}
101
+ rightIcon={(action.xicon ? "xicon " : "") + action.icon}
102
+ />
103
+ )}
104
+ </div>
105
+ }
106
+ </div>
107
+ </FocusTrap>
108
+ }
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { BadgeList } from "./BadgeList";
5
5
  import { UserPane } from "./UserPane";
6
6
  import { BadgeInfo } from "./BadgeInfo";
7
- import { CheckboxStatus } from "../Checkbox";
7
+ import { CheckboxStatus } from "../util";
8
8
 
9
9
  export interface ProfileProps {
10
10
  user: pxt.auth.State;
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
- import { fireClickOnEnter } from "../util";
2
+ import { fireClickOnEnter, CheckboxStatus } from "../util";
3
3
  import { UserNotification } from "./UserNotification";
4
- import { Checkbox, CheckboxStatus } from "../Checkbox";
4
+ import { Checkbox } from "../controls/Checkbox";
5
5
 
6
6
  export interface UserPaneProps {
7
7
  profile: pxt.auth.UserProfile;
@@ -18,7 +18,11 @@ export const UserPane = (props: UserPaneProps) => {
18
18
 
19
19
  const { username, displayName, picture } = profile.idp;
20
20
 
21
- const checkboxLabel = "email-optin-label"
21
+ const emailLabel = <>
22
+ {emailChecked === CheckboxStatus.Waiting ? <div className="common-spinner" /> : undefined}
23
+ {lf("I would like to receive the MakeCode newsletter. ")}
24
+ <a href="https://makecode.com/privacy" target="_blank" rel="noopener noreferrer">{lf("View Privacy Statement")}</a>
25
+ </>
22
26
 
23
27
  return <div className="profile-user-pane">
24
28
  <div className="profile-portrait">
@@ -42,11 +46,11 @@ export const UserPane = (props: UserPaneProps) => {
42
46
  { notification && <UserNotification notification={notification}/> }
43
47
  <div className="profile-spacer"></div>
44
48
  <div className="profile-email">
45
- <Checkbox isChecked={emailChecked} onClick={onEmailCheckClick} label={checkboxLabel}/>
46
- <div id={checkboxLabel}>
47
- {lf("I would like to receive the MakeCode newsletter. ")}
48
- <a href="https://makecode.com/privacy" target="_blank" rel="noopener noreferrer">{lf("View Privacy Statement")}</a>
49
- </div>
49
+ <Checkbox id="profile-email-checkbox"
50
+ className={emailChecked === CheckboxStatus.Waiting ? "loading" : ""}
51
+ isChecked={emailChecked === CheckboxStatus.Selected}
52
+ onChange={onEmailCheckClick}
53
+ label={emailLabel}/>
50
54
  </div>
51
55
  <div className="profile-actions">
52
56
  <a role="button"
@@ -1,5 +1,16 @@
1
1
  import * as React from "react";
2
2
 
3
+ export interface ControlProps {
4
+ id?: string;
5
+ className?: string;
6
+ ariaLabel?: string;
7
+ ariaHidden?: boolean;
8
+ role?: string;
9
+ }
10
+
11
+ export interface ContainerProps extends React.PropsWithChildren<ControlProps> {
12
+ }
13
+
3
14
  export function jsxLF(loc: string, ...rest: JSX.Element[]) {
4
15
  const indices: number[] = [];
5
16
 
@@ -32,4 +43,28 @@ export function fireClickOnEnter(e: React.KeyboardEvent<HTMLElement>) {
32
43
  e.preventDefault();
33
44
  e.currentTarget.click();
34
45
  }
46
+ }
47
+
48
+ export function classList(...classes: string[]) {
49
+ return classes
50
+ .filter(c => typeof c === "string")
51
+ .reduce((prev, c) => prev.concat(c.split(" ")), [] as string[])
52
+ .map(c => c.trim())
53
+ .filter(c => !!c)
54
+ .join(" ");
55
+ }
56
+
57
+ export function nodeListToArray<U extends Node>(list: NodeListOf<U>): U[] {
58
+ const out: U[] = [];
59
+
60
+ for (const node of list) {
61
+ out.push(node);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ export enum CheckboxStatus {
67
+ Selected,
68
+ Unselected,
69
+ Waiting
35
70
  }