tgui-core 1.1.7 → 1.1.9

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 (291) hide show
  1. package/lib/common/assets.ts +38 -0
  2. package/lib/common/collections.ts +27 -0
  3. package/lib/common/color.ts +88 -0
  4. package/lib/common/constants.ts +349 -0
  5. package/lib/common/events.ts +262 -0
  6. package/{dist/common/exhaustive.d.ts → lib/common/exhaustive.ts} +3 -1
  7. package/lib/common/format.ts +167 -0
  8. package/{dist/common/fp.d.ts → lib/common/fp.ts} +16 -2
  9. package/lib/common/hotkeys.ts +207 -0
  10. package/lib/common/http.ts +16 -0
  11. package/lib/common/keycodes.ts +86 -0
  12. package/{dist/common/keys.d.ts → lib/common/keys.ts} +24 -21
  13. package/lib/common/math.ts +76 -0
  14. package/lib/common/perf.ts +72 -0
  15. package/lib/common/random.ts +32 -0
  16. package/lib/common/react.ts +59 -0
  17. package/lib/common/redux.ts +187 -0
  18. package/lib/common/storage.ts +207 -0
  19. package/lib/common/string.ts +169 -0
  20. package/lib/common/timer.ts +63 -0
  21. package/lib/common/type-utils.ts +41 -0
  22. package/lib/common/types.d.ts +12 -0
  23. package/lib/common/uuid.ts +18 -0
  24. package/lib/components/AnimatedNumber.tsx +180 -0
  25. package/lib/components/Autofocus.tsx +23 -0
  26. package/lib/components/Blink.tsx +91 -0
  27. package/lib/components/BlockQuote.tsx +9 -0
  28. package/lib/components/BodyZoneSelector.tsx +149 -0
  29. package/lib/components/Box.tsx +252 -0
  30. package/lib/components/Button.tsx +425 -0
  31. package/lib/components/ByondUi.jsx +110 -0
  32. package/lib/components/Chart.tsx +155 -0
  33. package/lib/components/Collapsible.tsx +43 -0
  34. package/lib/components/ColorBox.tsx +29 -0
  35. package/lib/components/Dialog.tsx +81 -0
  36. package/lib/components/Dimmer.tsx +13 -0
  37. package/lib/components/Divider.tsx +20 -0
  38. package/lib/components/DmIcon.tsx +86 -0
  39. package/lib/components/DraggableControl.jsx +276 -0
  40. package/lib/components/Dropdown.tsx +246 -0
  41. package/lib/components/FakeTerminal.jsx +52 -0
  42. package/lib/components/FitText.tsx +99 -0
  43. package/lib/components/Flex.tsx +159 -0
  44. package/lib/components/Icon.tsx +95 -0
  45. package/lib/components/Image.tsx +54 -0
  46. package/lib/components/InfinitePlane.jsx +192 -0
  47. package/lib/components/Input.tsx +176 -0
  48. package/lib/components/KeyListener.tsx +40 -0
  49. package/lib/components/Knob.tsx +178 -0
  50. package/lib/components/LabeledControls.tsx +44 -0
  51. package/lib/components/LabeledList.tsx +154 -0
  52. package/lib/components/MenuBar.tsx +228 -0
  53. package/lib/components/Modal.tsx +23 -0
  54. package/lib/components/NoticeBox.tsx +45 -0
  55. package/lib/components/NumberInput.tsx +328 -0
  56. package/lib/components/Popper.tsx +100 -0
  57. package/lib/components/ProgressBar.tsx +105 -0
  58. package/lib/components/RestrictedInput.jsx +301 -0
  59. package/lib/components/RoundGauge.tsx +180 -0
  60. package/lib/components/Section.tsx +120 -0
  61. package/lib/components/Slider.tsx +169 -0
  62. package/lib/components/Stack.tsx +96 -0
  63. package/lib/components/StyleableSection.tsx +33 -0
  64. package/lib/components/Table.tsx +84 -0
  65. package/lib/components/Tabs.tsx +89 -0
  66. package/lib/components/TextArea.tsx +182 -0
  67. package/lib/components/TimeDisplay.jsx +64 -0
  68. package/lib/components/Tooltip.tsx +152 -0
  69. package/lib/components/TrackOutsideClicks.tsx +35 -0
  70. package/lib/components/VirtualList.tsx +69 -0
  71. package/lib/styles/atomic/candystripe.scss +8 -0
  72. package/lib/styles/atomic/centered-image.scss +7 -0
  73. package/lib/styles/atomic/color.scss +21 -0
  74. package/lib/styles/atomic/debug-layout.scss +17 -0
  75. package/lib/styles/atomic/fit-text.scss +14 -0
  76. package/lib/styles/atomic/links.scss +12 -0
  77. package/lib/styles/atomic/outline.scss +47 -0
  78. package/lib/styles/atomic/text.scss +44 -0
  79. package/lib/styles/base.scss +32 -0
  80. package/lib/styles/colors.scss +92 -0
  81. package/lib/styles/components/BlockQuote.module.scss +20 -0
  82. package/lib/styles/components/BlockQuote.module.scss.d.ts +4 -0
  83. package/lib/styles/components/Button.module.scss +157 -0
  84. package/lib/styles/components/Button.module.scss.d.ts +46 -0
  85. package/lib/styles/components/ColorBox.module.scss +12 -0
  86. package/lib/styles/components/ColorBox.module.scss.d.ts +4 -0
  87. package/lib/styles/components/Dialog.module.scss +60 -0
  88. package/lib/styles/components/Dialog.module.scss.d.ts +10 -0
  89. package/lib/styles/components/Dimmer.module.scss +22 -0
  90. package/lib/styles/components/Dimmer.module.scss.d.ts +4 -0
  91. package/lib/styles/components/Divider.module.scss +27 -0
  92. package/lib/styles/components/Divider.module.scss.d.ts +6 -0
  93. package/lib/styles/components/Dropdown.scss +72 -0
  94. package/lib/styles/components/Flex.module.scss +13 -0
  95. package/lib/styles/components/Flex.module.scss.d.ts +5 -0
  96. package/lib/styles/components/Icon.module.scss +25 -0
  97. package/lib/styles/components/Icon.module.scss.d.ts +5 -0
  98. package/lib/styles/components/Input.module.scss +64 -0
  99. package/lib/styles/components/Input.module.scss.d.ts +8 -0
  100. package/lib/styles/components/Knob.module.scss +131 -0
  101. package/lib/styles/components/Knob.module.scss.d.ts +33 -0
  102. package/lib/styles/components/LabeledList.module.scss +49 -0
  103. package/lib/styles/components/LabeledList.module.scss.d.ts +8 -0
  104. package/lib/styles/components/MenuBar.module.scss +75 -0
  105. package/lib/styles/components/MenuBar.module.scss.d.ts +14 -0
  106. package/lib/styles/components/Modal.module.scss +14 -0
  107. package/lib/styles/components/Modal.module.scss.d.ts +4 -0
  108. package/lib/styles/components/NoticeBox.module.scss +65 -0
  109. package/lib/styles/components/NoticeBox.module.scss.d.ts +27 -0
  110. package/lib/styles/components/NumberInput.module.scss +71 -0
  111. package/lib/styles/components/NumberInput.module.scss.d.ts +9 -0
  112. package/lib/styles/components/ProgressBar.module.scss +63 -0
  113. package/lib/styles/components/ProgressBar.module.scss.d.ts +27 -0
  114. package/lib/styles/components/RoundGauge.module.scss +85 -0
  115. package/lib/styles/components/RoundGauge.module.scss.d.ts +49 -0
  116. package/lib/styles/components/Section.module.scss +130 -0
  117. package/lib/styles/components/Section.module.scss.d.ts +13 -0
  118. package/lib/styles/components/Slider.module.scss +54 -0
  119. package/lib/styles/components/Slider.module.scss.d.ts +8 -0
  120. package/lib/styles/components/Stack.module.scss +60 -0
  121. package/lib/styles/components/Stack.module.scss.d.ts +12 -0
  122. package/lib/styles/components/Table.module.scss +44 -0
  123. package/lib/styles/components/Table.module.scss.d.ts +10 -0
  124. package/lib/styles/components/Tabs.module.scss +144 -0
  125. package/lib/styles/components/Tabs.module.scss.d.ts +35 -0
  126. package/lib/styles/components/TextArea.module.scss +86 -0
  127. package/lib/styles/components/TextArea.module.scss.d.ts +11 -0
  128. package/lib/styles/components/Tooltip.module.scss +24 -0
  129. package/lib/styles/components/Tooltip.module.scss.d.ts +4 -0
  130. package/lib/styles/functions.scss +79 -0
  131. package/lib/styles/input.scss +9 -0
  132. package/lib/styles/main.scss +20 -0
  133. package/lib/styles/reset.scss +68 -0
  134. package/package.json +6 -6
  135. package/dist/ProgressBar.module-BkAFfFy0.js +0 -29
  136. package/dist/Section.module-CLVHJ4yA.js +0 -15
  137. package/dist/assets/BlockQuote.css +0 -1
  138. package/dist/assets/Button.css +0 -1
  139. package/dist/assets/ColorBox.css +0 -1
  140. package/dist/assets/Dialog.css +0 -1
  141. package/dist/assets/Dimmer.css +0 -1
  142. package/dist/assets/Divider.css +0 -1
  143. package/dist/assets/Flex.css +0 -1
  144. package/dist/assets/Icon.css +0 -6
  145. package/dist/assets/Input.css +0 -1
  146. package/dist/assets/Knob.css +0 -1
  147. package/dist/assets/LabeledList.css +0 -1
  148. package/dist/assets/MenuBar.css +0 -1
  149. package/dist/assets/Modal.css +0 -1
  150. package/dist/assets/NoticeBox.css +0 -1
  151. package/dist/assets/NumberInput.css +0 -1
  152. package/dist/assets/ProgressBar.css +0 -1
  153. package/dist/assets/RoundGauge.css +0 -1
  154. package/dist/assets/Section.css +0 -1
  155. package/dist/assets/Slider.css +0 -1
  156. package/dist/assets/Stack.css +0 -1
  157. package/dist/assets/Table.css +0 -1
  158. package/dist/assets/Tabs.css +0 -1
  159. package/dist/assets/TextArea.css +0 -1
  160. package/dist/assets/Tooltip.css +0 -1
  161. package/dist/common/assets.d.ts +0 -4
  162. package/dist/common/assets.js +0 -21
  163. package/dist/common/collections.d.ts +0 -10
  164. package/dist/common/collections.js +0 -15
  165. package/dist/common/color.d.ts +0 -25
  166. package/dist/common/color.js +0 -69
  167. package/dist/common/constants.d.ts +0 -102
  168. package/dist/common/constants.js +0 -312
  169. package/dist/common/events.d.ts +0 -33
  170. package/dist/common/events.js +0 -147
  171. package/dist/common/exhaustive.js +0 -6
  172. package/dist/common/format.d.ts +0 -11
  173. package/dist/common/format.js +0 -114
  174. package/dist/common/fp.js +0 -9
  175. package/dist/common/hotkeys.d.ts +0 -25
  176. package/dist/common/hotkeys.js +0 -112
  177. package/dist/common/http.d.ts +0 -4
  178. package/dist/common/http.js +0 -10
  179. package/dist/common/keycodes.d.ts +0 -85
  180. package/dist/common/keycodes.js +0 -88
  181. package/dist/common/keys.js +0 -8
  182. package/dist/common/math.d.ts +0 -39
  183. package/dist/common/math.js +0 -41
  184. package/dist/common/perf.d.ts +0 -24
  185. package/dist/common/perf.js +0 -33
  186. package/dist/common/random.d.ts +0 -16
  187. package/dist/common/random.js +0 -18
  188. package/dist/common/react.d.ts +0 -23
  189. package/dist/common/react.js +0 -30
  190. package/dist/common/redux.d.ts +0 -64
  191. package/dist/common/redux.js +0 -72
  192. package/dist/common/storage.js +0 -124
  193. package/dist/common/string.d.ts +0 -65
  194. package/dist/common/string.js +0 -83
  195. package/dist/common/timer.d.ts +0 -18
  196. package/dist/common/timer.js +0 -28
  197. package/dist/common/type-utils.d.ts +0 -9
  198. package/dist/common/type-utils.js +0 -25
  199. package/dist/common/uuid.d.ts +0 -9
  200. package/dist/common/uuid.js +0 -10
  201. package/dist/components/AnimatedNumber.d.ts +0 -60
  202. package/dist/components/AnimatedNumber.js +0 -76
  203. package/dist/components/Autofocus.d.ts +0 -4
  204. package/dist/components/Autofocus.js +0 -17
  205. package/dist/components/Blink.d.ts +0 -26
  206. package/dist/components/Blink.js +0 -56
  207. package/dist/components/BlockQuote.d.ts +0 -3
  208. package/dist/components/BlockQuote.js +0 -13
  209. package/dist/components/BodyZoneSelector.d.ts +0 -28
  210. package/dist/components/BodyZoneSelector.js +0 -115
  211. package/dist/components/Box.d.ts +0 -91
  212. package/dist/components/Box.js +0 -133
  213. package/dist/components/Button.d.ts +0 -93
  214. package/dist/components/Button.js +0 -298
  215. package/dist/components/ByondUi.js +0 -73
  216. package/dist/components/Chart.d.ts +0 -28
  217. package/dist/components/Chart.js +0 -95
  218. package/dist/components/Collapsible.d.ts +0 -15
  219. package/dist/components/Collapsible.js +0 -27
  220. package/dist/components/ColorBox.d.ts +0 -8
  221. package/dist/components/ColorBox.js +0 -24
  222. package/dist/components/Dialog.d.ts +0 -24
  223. package/dist/components/Dialog.js +0 -67
  224. package/dist/components/Dimmer.d.ts +0 -3
  225. package/dist/components/Dimmer.js +0 -13
  226. package/dist/components/Divider.d.ts +0 -6
  227. package/dist/components/Divider.js +0 -22
  228. package/dist/components/DmIcon.d.ts +0 -31
  229. package/dist/components/DmIcon.js +0 -31
  230. package/dist/components/DraggableControl.js +0 -176
  231. package/dist/components/Dropdown.d.ts +0 -48
  232. package/dist/components/Dropdown.js +0 -152
  233. package/dist/components/FakeTerminal.js +0 -38
  234. package/dist/components/FitText.d.ts +0 -22
  235. package/dist/components/FitText.js +0 -63
  236. package/dist/components/Flex.d.ts +0 -93
  237. package/dist/components/Flex.js +0 -72
  238. package/dist/components/Icon.d.ts +0 -30
  239. package/dist/components/Icon.js +0 -51
  240. package/dist/components/Image.d.ts +0 -14
  241. package/dist/components/Image.js +0 -35
  242. package/dist/components/InfinitePlane.js +0 -139
  243. package/dist/components/Input.d.ts +0 -61
  244. package/dist/components/Input.js +0 -89
  245. package/dist/components/KeyListener.d.ts +0 -15
  246. package/dist/components/KeyListener.js +0 -23
  247. package/dist/components/Knob.d.ts +0 -49
  248. package/dist/components/Knob.js +0 -162
  249. package/dist/components/LabeledControls.d.ts +0 -11
  250. package/dist/components/LabeledControls.js +0 -39
  251. package/dist/components/LabeledList.d.ts +0 -57
  252. package/dist/components/LabeledList.js +0 -94
  253. package/dist/components/MenuBar.d.ts +0 -28
  254. package/dist/components/MenuBar.js +0 -174
  255. package/dist/components/Modal.d.ts +0 -3
  256. package/dist/components/Modal.js +0 -25
  257. package/dist/components/NoticeBox.d.ts +0 -20
  258. package/dist/components/NoticeBox.js +0 -49
  259. package/dist/components/NumberInput.d.ts +0 -45
  260. package/dist/components/NumberInput.js +0 -221
  261. package/dist/components/Popper.d.ts +0 -27
  262. package/dist/components/Popper.js +0 -177
  263. package/dist/components/ProgressBar.d.ts +0 -46
  264. package/dist/components/ProgressBar.js +0 -37
  265. package/dist/components/RestrictedInput.js +0 -155
  266. package/dist/components/RoundGauge.d.ts +0 -53
  267. package/dist/components/RoundGauge.js +0 -147
  268. package/dist/components/Section.d.ts +0 -63
  269. package/dist/components/Section.js +0 -62
  270. package/dist/components/Slider.d.ts +0 -46
  271. package/dist/components/Slider.js +0 -124
  272. package/dist/components/Stack.d.ts +0 -27
  273. package/dist/components/Stack.js +0 -67
  274. package/dist/components/StyleableSection.d.ts +0 -11
  275. package/dist/components/StyleableSection.js +0 -16
  276. package/dist/components/Table.d.ts +0 -29
  277. package/dist/components/Table.js +0 -67
  278. package/dist/components/Tabs.d.ts +0 -23
  279. package/dist/components/Tabs.js +0 -89
  280. package/dist/components/TextArea.d.ts +0 -39
  281. package/dist/components/TextArea.js +0 -118
  282. package/dist/components/TimeDisplay.js +0 -34
  283. package/dist/components/Tooltip.d.ts +0 -29
  284. package/dist/components/Tooltip.js +0 -83
  285. package/dist/components/TrackOutsideClicks.d.ts +0 -13
  286. package/dist/components/TrackOutsideClicks.js +0 -24
  287. package/dist/components/VirtualList.d.ts +0 -8
  288. package/dist/components/VirtualList.js +0 -34
  289. package/dist/components/index.js +0 -92
  290. package/dist/popper-CiqSDJTE.js +0 -906
  291. /package/{dist/components/index.d.ts → lib/components/index.ts} +0 -0
@@ -0,0 +1,425 @@
1
+ import { Placement } from '@popperjs/core';
2
+ import {
3
+ ChangeEvent,
4
+ createRef,
5
+ MouseEvent,
6
+ ReactNode,
7
+ useEffect,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+
12
+ import { KEY } from '../common/keys';
13
+ import { BooleanLike, classes } from '../common/react';
14
+ import styles from '../styles/components/Button.module.scss';
15
+ import { Box, BoxProps, computeBoxClassName, computeBoxProps } from './Box';
16
+ import { Icon } from './Icon';
17
+ import { Tooltip } from './Tooltip';
18
+
19
+ /**
20
+ * Getting ellipses to work requires that you use:
21
+ * 1. A string rather than a node
22
+ * 2. A fixed width here or in a parent
23
+ * 3. Children prop rather than content
24
+ */
25
+ type EllipsisUnion =
26
+ | {
27
+ children: string;
28
+ /** @deprecated use children instead */
29
+ content?: never;
30
+ /** Cuts off text with an ellipsis */
31
+ ellipsis: true;
32
+ }
33
+ | Partial<{
34
+ children: ReactNode;
35
+ /** @deprecated use children instead */
36
+ content: ReactNode;
37
+ ellipsis: undefined;
38
+ }>;
39
+
40
+ type Props = Partial<{
41
+ /** Captures keyboard events */
42
+ captureKeys: boolean;
43
+ /** Makes the button circular */
44
+ circular: boolean;
45
+ /** Reduces the padding of the button */
46
+ compact: boolean;
47
+ /** Disables and greys out the button */
48
+ disabled: BooleanLike;
49
+ /** Fill all available horizontal space */
50
+ fluid: boolean;
51
+ /** Adds an icon to the button */
52
+ icon: string | false;
53
+ /** Icon color */
54
+ iconColor: string;
55
+ /** Icon position */
56
+ iconPosition: string;
57
+ /** Icon rotation */
58
+ iconRotation: number;
59
+ /** Makes the icon spin */
60
+ iconSpin: BooleanLike;
61
+ /** Called when element is clicked */
62
+ onClick: (e: any) => void;
63
+ /** Activates the button (gives it a green color) */
64
+ selected: BooleanLike;
65
+ /** A fancy, boxy tooltip, which appears when hovering over the button */
66
+ tooltip: ReactNode;
67
+ /** Position of the tooltip. See [`Popper`](#Popper) for valid options. */
68
+ tooltipPosition: Placement;
69
+ /** Align content vertically using flex. Use lineHeight if the height is static. */
70
+ verticalAlignContent: string;
71
+ }> &
72
+ EllipsisUnion &
73
+ BoxProps;
74
+
75
+ /** Clickable button. Comes with variants. Read more in the documentation. */
76
+ export function Button(props: Props) {
77
+ const {
78
+ captureKeys = true,
79
+ children,
80
+ circular,
81
+ className,
82
+ color,
83
+ compact,
84
+ content,
85
+ disabled,
86
+ ellipsis,
87
+ fluid,
88
+ icon,
89
+ iconColor,
90
+ iconPosition,
91
+ iconRotation,
92
+ iconSpin,
93
+ onClick,
94
+ selected,
95
+ tooltip,
96
+ tooltipPosition,
97
+ verticalAlignContent,
98
+ ...rest
99
+ } = props;
100
+
101
+ const toDisplay: ReactNode = content || children;
102
+
103
+ let buttonContent = (
104
+ <div
105
+ className={classes([
106
+ styles.button,
107
+ fluid && styles.fluid,
108
+ disabled && styles.disabled,
109
+ selected && styles.selected,
110
+ circular && styles.circular,
111
+ compact && styles.compact,
112
+ verticalAlignContent && styles.flex,
113
+ verticalAlignContent && fluid && styles.flex__fluid,
114
+ verticalAlignContent &&
115
+ styles['verticalAlignContent__' + verticalAlignContent],
116
+ color && typeof color === 'string'
117
+ ? styles['color__' + color]
118
+ : styles['color__default'],
119
+ className,
120
+ computeBoxClassName(rest),
121
+ ])}
122
+ tabIndex={!disabled ? 0 : undefined}
123
+ onClick={(event) => {
124
+ if (!disabled && onClick) {
125
+ onClick(event);
126
+ }
127
+ }}
128
+ onKeyDown={(event) => {
129
+ if (!captureKeys) {
130
+ return;
131
+ }
132
+
133
+ // Simulate a click when pressing space or enter.
134
+ if (event.key === KEY.Space || event.key === KEY.Enter) {
135
+ event.preventDefault();
136
+ if (!disabled && onClick) {
137
+ onClick(event);
138
+ }
139
+ return;
140
+ }
141
+
142
+ // Refocus layout on pressing escape.
143
+ if (event.key === KEY.Escape) {
144
+ event.preventDefault();
145
+ }
146
+ }}
147
+ {...computeBoxProps(rest)}
148
+ >
149
+ <div className={styles.content}>
150
+ {icon && iconPosition !== 'right' && (
151
+ <Icon
152
+ mr={1}
153
+ name={icon}
154
+ color={iconColor}
155
+ rotation={iconRotation}
156
+ spin={iconSpin}
157
+ />
158
+ )}
159
+ {!ellipsis ? (
160
+ toDisplay
161
+ ) : (
162
+ <span
163
+ className={classes([styles.ellipsis, icon && styles.textMargin])}
164
+ >
165
+ {toDisplay}
166
+ </span>
167
+ )}
168
+ {icon && iconPosition === 'right' && (
169
+ <Icon
170
+ ml={1}
171
+ name={icon}
172
+ color={iconColor}
173
+ rotation={iconRotation}
174
+ spin={iconSpin}
175
+ />
176
+ )}
177
+ </div>
178
+ </div>
179
+ );
180
+
181
+ if (tooltip) {
182
+ buttonContent = (
183
+ <Tooltip content={tooltip} position={tooltipPosition as Placement}>
184
+ {buttonContent}
185
+ </Tooltip>
186
+ );
187
+ }
188
+
189
+ return buttonContent;
190
+ }
191
+
192
+ type CheckProps = Partial<{
193
+ checked: BooleanLike;
194
+ }> &
195
+ Props;
196
+
197
+ /** Visually toggles between checked and unchecked states. */
198
+ export function ButtonCheckbox(props: CheckProps) {
199
+ const { checked, ...rest } = props;
200
+
201
+ return (
202
+ <Button
203
+ color="transparent"
204
+ icon={checked ? 'check-square-o' : 'square-o'}
205
+ selected={checked}
206
+ {...rest}
207
+ />
208
+ );
209
+ }
210
+
211
+ Button.Checkbox = ButtonCheckbox;
212
+
213
+ type ConfirmProps = Partial<{
214
+ confirmColor: string;
215
+ confirmContent: ReactNode;
216
+ confirmIcon: string;
217
+ }> &
218
+ Props;
219
+
220
+ /** Requires user confirmation before triggering its action. */
221
+ function ButtonConfirm(props: ConfirmProps) {
222
+ const {
223
+ children,
224
+ color,
225
+ confirmColor = 'bad',
226
+ confirmContent = 'Confirm?',
227
+ confirmIcon,
228
+ ellipsis = true,
229
+ icon,
230
+ onClick,
231
+ ...rest
232
+ } = props;
233
+ const [clickedOnce, setClickedOnce] = useState(false);
234
+
235
+ function handleClick(event: MouseEvent<HTMLDivElement>) {
236
+ if (!clickedOnce) {
237
+ setClickedOnce(true);
238
+ return;
239
+ }
240
+
241
+ onClick?.(event);
242
+ setClickedOnce(false);
243
+ }
244
+
245
+ return (
246
+ <Button
247
+ icon={clickedOnce ? confirmIcon : icon}
248
+ color={clickedOnce ? confirmColor : color}
249
+ onClick={handleClick}
250
+ {...rest}
251
+ >
252
+ {clickedOnce ? confirmContent : children}
253
+ </Button>
254
+ );
255
+ }
256
+
257
+ Button.Confirm = ButtonConfirm;
258
+
259
+ type InputProps = Partial<{
260
+ currentValue: string;
261
+ defaultValue: string;
262
+ fluid: boolean;
263
+ maxLength: number;
264
+ onCommit: (e: any, value: string) => void;
265
+ placeholder: string;
266
+ }> &
267
+ Props;
268
+
269
+ /** Accepts and handles user input. */
270
+ function ButtonInput(props: InputProps) {
271
+ const {
272
+ children,
273
+ color = 'default',
274
+ content,
275
+ currentValue,
276
+ defaultValue,
277
+ disabled,
278
+ fluid,
279
+ icon,
280
+ iconRotation,
281
+ iconSpin,
282
+ maxLength,
283
+ onCommit = () => null,
284
+ placeholder,
285
+ tooltip,
286
+ tooltipPosition,
287
+ ...rest
288
+ } = props;
289
+ const [inInput, setInInput] = useState(false);
290
+ const inputRef = createRef<HTMLInputElement>();
291
+
292
+ const toDisplay = content || children;
293
+
294
+ function commitResult(e) {
295
+ const input = inputRef.current;
296
+ if (!input) return;
297
+
298
+ const hasValue = input.value !== '';
299
+ if (hasValue) {
300
+ onCommit(e, input.value);
301
+ } else {
302
+ if (defaultValue) {
303
+ onCommit(e, defaultValue);
304
+ }
305
+ }
306
+ }
307
+
308
+ useEffect(() => {
309
+ const input = inputRef.current;
310
+ if (!input) return;
311
+
312
+ if (inInput) {
313
+ input.value = currentValue || '';
314
+ try {
315
+ input.focus();
316
+ input.select();
317
+ } catch {
318
+ // Ignore errors
319
+ }
320
+ }
321
+ }, [inInput, currentValue]);
322
+
323
+ let buttonContent = (
324
+ <Box
325
+ className={classes([
326
+ styles.button,
327
+ fluid && styles.fluid,
328
+ styles['color__' + color],
329
+ ])}
330
+ {...rest}
331
+ onClick={() => setInInput(true)}
332
+ >
333
+ {icon && <Icon name={icon} rotation={iconRotation} spin={iconSpin} />}
334
+ <div>{toDisplay}</div>
335
+ <input
336
+ disabled={!!disabled}
337
+ ref={inputRef}
338
+ className="NumberInput__input"
339
+ style={{
340
+ display: !inInput ? 'none' : '',
341
+ textAlign: 'left',
342
+ }}
343
+ onBlur={(event) => {
344
+ if (!inInput) {
345
+ return;
346
+ }
347
+ setInInput(false);
348
+ commitResult(event);
349
+ }}
350
+ onKeyDown={(event) => {
351
+ if (event.key === KEY.Enter) {
352
+ setInInput(false);
353
+ commitResult(event);
354
+ return;
355
+ }
356
+ if (event.key === KEY.Escape) {
357
+ setInInput(false);
358
+ }
359
+ }}
360
+ />
361
+ </Box>
362
+ );
363
+
364
+ if (tooltip) {
365
+ buttonContent = (
366
+ <Tooltip content={tooltip} position={tooltipPosition as Placement}>
367
+ {buttonContent}
368
+ </Tooltip>
369
+ );
370
+ }
371
+
372
+ return buttonContent;
373
+ }
374
+
375
+ Button.Input = ButtonInput;
376
+
377
+ type FileProps = {
378
+ accept: string;
379
+ multiple?: boolean;
380
+ onSelectFiles: (files: string | string[]) => void;
381
+ } & Props;
382
+
383
+ /** Accepts file input */
384
+ function ButtonFile(props: FileProps) {
385
+ const { accept, multiple, onSelectFiles, ...rest } = props;
386
+
387
+ const inputRef = useRef<HTMLInputElement>(null);
388
+
389
+ async function read(files: FileList) {
390
+ const promises = Array.from(files).map((file) => {
391
+ const reader = new FileReader();
392
+
393
+ return new Promise<string>((resolve) => {
394
+ reader.onload = () => resolve(reader.result as string);
395
+ reader.readAsText(file);
396
+ });
397
+ });
398
+
399
+ return await Promise.all(promises);
400
+ }
401
+
402
+ async function handleChange(event: ChangeEvent<HTMLInputElement>) {
403
+ const files = event.target.files;
404
+ if (files?.length) {
405
+ const readFiles = await read(files);
406
+ onSelectFiles(multiple ? readFiles : readFiles[0]);
407
+ }
408
+ }
409
+
410
+ return (
411
+ <>
412
+ <Button onClick={() => inputRef.current?.click()} {...rest} />
413
+ <input
414
+ hidden
415
+ type="file"
416
+ ref={inputRef}
417
+ accept={accept}
418
+ multiple={multiple}
419
+ onChange={handleChange}
420
+ />
421
+ </>
422
+ );
423
+ }
424
+
425
+ Button.File = ButtonFile;
@@ -0,0 +1,110 @@
1
+ import { Component, createRef } from 'react';
2
+
3
+ import { shallowDiffers } from '../common/react';
4
+ import { debounce } from '../common/timer';
5
+ import { computeBoxProps } from './Box';
6
+
7
+ // Stack of currently allocated BYOND UI element ids.
8
+ const byondUiStack = [];
9
+
10
+ function createByondUiElement(elementId) {
11
+ // Reserve an index in the stack
12
+ const index = byondUiStack.length;
13
+ byondUiStack.push(null);
14
+ // Get a unique id
15
+ const id = elementId || 'byondui_' + index;
16
+ // Return a control structure
17
+ return {
18
+ render: (params) => {
19
+ byondUiStack[index] = id;
20
+ Byond.winset(id, params);
21
+ },
22
+ unmount: () => {
23
+ byondUiStack[index] = null;
24
+ Byond.winset(id, {
25
+ parent: '',
26
+ });
27
+ },
28
+ };
29
+ }
30
+
31
+ window.addEventListener('beforeunload', () => {
32
+ // Cleanly unmount all visible UI elements
33
+ for (let index = 0; index < byondUiStack.length; index++) {
34
+ const id = byondUiStack[index];
35
+ if (typeof id === 'string') {
36
+ byondUiStack[index] = null;
37
+ Byond.winset(id, {
38
+ parent: '',
39
+ });
40
+ }
41
+ }
42
+ });
43
+
44
+ /**
45
+ * Get the bounding box of the DOM element in display-pixels.
46
+ */
47
+ function getBoundingBox(element) {
48
+ const pixelRatio = window.devicePixelRatio ?? 1;
49
+ const rect = element.getBoundingClientRect();
50
+
51
+ return {
52
+ pos: [rect.left * pixelRatio, rect.top * pixelRatio],
53
+ size: [
54
+ (rect.right - rect.left) * pixelRatio,
55
+ (rect.bottom - rect.top) * pixelRatio,
56
+ ],
57
+ };
58
+ }
59
+
60
+ export class ByondUi extends Component {
61
+ constructor(props) {
62
+ super(props);
63
+ this.containerRef = createRef();
64
+ this.byondUiElement = createByondUiElement(props.params?.id);
65
+ this.handleResize = debounce(() => {
66
+ this.forceUpdate();
67
+ }, 100);
68
+ }
69
+
70
+ shouldComponentUpdate(nextProps) {
71
+ const { params: prevParams = {}, ...prevRest } = this.props;
72
+ const { params: nextParams = {}, ...nextRest } = nextProps;
73
+ return (
74
+ shallowDiffers(prevParams, nextParams) ||
75
+ shallowDiffers(prevRest, nextRest)
76
+ );
77
+ }
78
+
79
+ componentDidMount() {
80
+ window.addEventListener('resize', this.handleResize);
81
+ this.componentDidUpdate();
82
+ this.handleResize();
83
+ }
84
+
85
+ componentDidUpdate() {
86
+ const { params = {} } = this.props;
87
+ const box = getBoundingBox(this.containerRef.current);
88
+ this.byondUiElement.render({
89
+ parent: Byond.windowId,
90
+ ...params,
91
+ pos: box.pos[0] + ',' + box.pos[1],
92
+ size: box.size[0] + 'x' + box.size[1],
93
+ });
94
+ }
95
+
96
+ componentWillUnmount() {
97
+ window.removeEventListener('resize', this.handleResize);
98
+ this.byondUiElement.unmount();
99
+ }
100
+
101
+ render() {
102
+ const { params, ...rest } = this.props;
103
+ return (
104
+ <div ref={this.containerRef} {...computeBoxProps(rest)}>
105
+ {/* Filler */}
106
+ <div style={{ minHeight: '22px' }} />
107
+ </div>
108
+ );
109
+ }
110
+ }
@@ -0,0 +1,155 @@
1
+ import { Component, createRef, RefObject } from 'react';
2
+
3
+ import { zip } from '../common/collections';
4
+ import { Box, BoxProps } from './Box';
5
+
6
+ type Props = {
7
+ data: number[][];
8
+ } & Partial<{
9
+ fillColor: string;
10
+ rangeX: [number, number];
11
+ rangeY: [number, number];
12
+ strokeColor: string;
13
+ strokeWidth: number;
14
+ }> &
15
+ BoxProps;
16
+
17
+ type State = {
18
+ viewBox: [number, number];
19
+ };
20
+
21
+ type Point = number[];
22
+ type Range = [number, number];
23
+
24
+ function normalizeData(
25
+ data: Point[],
26
+ scale: number[],
27
+ rangeX?: Range,
28
+ rangeY?: Range,
29
+ ) {
30
+ if (data.length === 0) {
31
+ return [];
32
+ }
33
+
34
+ const zipped = zip(...data);
35
+
36
+ const min = zipped.map((p) => Math.min(...p));
37
+ const max = zipped.map((p) => Math.max(...p));
38
+
39
+ if (rangeX !== undefined) {
40
+ min[0] = rangeX[0];
41
+ max[0] = rangeX[1];
42
+ }
43
+
44
+ if (rangeY !== undefined) {
45
+ min[1] = rangeY[0];
46
+ max[1] = rangeY[1];
47
+ }
48
+
49
+ const normalized = data.map((point) =>
50
+ zip(point, min, max, scale).map(
51
+ ([value, min, max, scale]) => ((value - min) / (max - min)) * scale,
52
+ ),
53
+ );
54
+
55
+ return normalized;
56
+ }
57
+
58
+ function dataToPolylinePoints(data) {
59
+ let points = '';
60
+ for (let i = 0; i < data.length; i++) {
61
+ const point = data[i];
62
+ points += point[0] + ',' + point[1] + ' ';
63
+ }
64
+ return points;
65
+ }
66
+
67
+ class LineChart extends Component<Props> {
68
+ ref: RefObject<HTMLDivElement>;
69
+ state: State;
70
+
71
+ constructor(props: Props) {
72
+ super(props);
73
+ this.ref = createRef();
74
+ this.state = {
75
+ // Initial guess
76
+ viewBox: [600, 200],
77
+ };
78
+ this.handleResize = this.handleResize.bind(this);
79
+ }
80
+
81
+ componentDidMount() {
82
+ window.addEventListener('resize', this.handleResize);
83
+ this.handleResize();
84
+ }
85
+
86
+ componentWillUnmount() {
87
+ window.removeEventListener('resize', this.handleResize);
88
+ }
89
+
90
+ handleResize = () => {
91
+ const element = this.ref.current;
92
+ if (!element) {
93
+ return;
94
+ }
95
+ this.setState({
96
+ viewBox: [element.offsetWidth, element.offsetHeight],
97
+ });
98
+ };
99
+
100
+ render() {
101
+ const {
102
+ data = [],
103
+ rangeX,
104
+ rangeY,
105
+ fillColor = 'none',
106
+ strokeColor = '#ffffff',
107
+ strokeWidth = 2,
108
+ ...rest
109
+ } = this.props;
110
+ const { viewBox } = this.state;
111
+ const normalized = normalizeData(data, viewBox, rangeX, rangeY);
112
+ // Push data outside viewBox and form a fillable polygon
113
+ if (normalized.length > 0) {
114
+ const first = normalized[0];
115
+ const last = normalized[normalized.length - 1];
116
+ normalized.push([viewBox[0] + strokeWidth, last[1]]);
117
+ normalized.push([viewBox[0] + strokeWidth, -strokeWidth]);
118
+ normalized.push([-strokeWidth, -strokeWidth]);
119
+ normalized.push([-strokeWidth, first[1]]);
120
+ }
121
+ const points = dataToPolylinePoints(normalized);
122
+ const divProps = { ...rest, className: '', ref: this.ref };
123
+
124
+ return (
125
+ <Box position="relative" {...rest}>
126
+ <Box {...divProps}>
127
+ <svg
128
+ viewBox={`0 0 ${viewBox[0]} ${viewBox[1]}`}
129
+ preserveAspectRatio="none"
130
+ style={{
131
+ position: 'absolute',
132
+ top: 0,
133
+ left: 0,
134
+ right: 0,
135
+ bottom: 0,
136
+ overflow: 'hidden',
137
+ }}
138
+ >
139
+ <polyline
140
+ transform={`scale(1, -1) translate(0, -${viewBox[1]})`}
141
+ fill={fillColor}
142
+ stroke={strokeColor}
143
+ strokeWidth={strokeWidth}
144
+ points={points}
145
+ />
146
+ </svg>
147
+ </Box>
148
+ </Box>
149
+ );
150
+ }
151
+ }
152
+
153
+ export const Chart = {
154
+ Line: LineChart,
155
+ };