tgui-core 2.1.0 → 3.0.0

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.
@@ -36,9 +36,11 @@ export declare enum KEY {
36
36
  Home = "Home",
37
37
  Insert = "Insert",
38
38
  Left = "ArrowLeft",
39
+ Minus = "-",
39
40
  N = "n",
40
41
  PageDown = "PageDown",
41
42
  PageUp = "PageUp",
43
+ Plus = "+",
42
44
  Right = "ArrowRight",
43
45
  S = "s",
44
46
  Shift = "Shift",
@@ -1 +1 @@
1
- var r,e=((r={}).A="a",r.Alt="Alt",r.Backspace="Backspace",r.Control="Control",r.D="d",r.Delete="Delete",r.Down="ArrowDown",r.E="e",r.End="End",r.Enter="Enter",r.Esc="Esc",r.Escape="Escape",r.Home="Home",r.Insert="Insert",r.Left="ArrowLeft",r.N="n",r.PageDown="PageDown",r.PageUp="PageUp",r.Right="ArrowRight",r.S="s",r.Shift="Shift",r.Space=" ",r.Tab="Tab",r.Up="ArrowUp",r.W="w",r.Z="z",r);function n(r){return"Esc"===r||"Escape"===r}function t(r){return r>="a"&&r<="z"}function o(r){return r>="0"&&r<="9"}function a(r){return"n"===r||"s"===r||"w"===r||"e"===r}function s(r){return"ArrowUp"===r||"ArrowDown"===r||"ArrowLeft"===r||"ArrowRight"===r}function i(r){return"w"===r||"a"===r||"s"===r||"d"===r}function c(r){return i(r)||s(r)}export{e as KEY,t as isAlphabetic,s as isArrow,a as isCardinal,n as isEscape,c as isMovement,o as isNumeric,i as isWasd};
1
+ var r,e=((r={}).A="a",r.Alt="Alt",r.Backspace="Backspace",r.Control="Control",r.D="d",r.Delete="Delete",r.Down="ArrowDown",r.E="e",r.End="End",r.Enter="Enter",r.Esc="Esc",r.Escape="Escape",r.Home="Home",r.Insert="Insert",r.Left="ArrowLeft",r.Minus="-",r.N="n",r.PageDown="PageDown",r.PageUp="PageUp",r.Plus="+",r.Right="ArrowRight",r.S="s",r.Shift="Shift",r.Space=" ",r.Tab="Tab",r.Up="ArrowUp",r.W="w",r.Z="z",r);function n(r){return"Esc"===r||"Escape"===r}function t(r){return r>="a"&&r<="z"}function o(r){return r>="0"&&r<="9"}function s(r){return"n"===r||"s"===r||"w"===r||"e"===r}function a(r){return"ArrowUp"===r||"ArrowDown"===r||"ArrowLeft"===r||"ArrowRight"===r}function i(r){return"w"===r||"a"===r||"s"===r||"d"===r}function c(r){return i(r)||a(r)}export{e as KEY,t as isAlphabetic,a as isArrow,s as isCardinal,n as isEscape,c as isMovement,o as isNumeric,i as isWasd};
@@ -12,7 +12,7 @@ type EventHandlers = {
12
12
  onMouseMove: MouseEventHandler<HTMLDivElement>;
13
13
  onMouseOver: MouseEventHandler<HTMLDivElement>;
14
14
  onMouseUp: MouseEventHandler<HTMLDivElement>;
15
- onScroll: UIEventHandler<HTMLDivElement>;
15
+ onScroll: UIEventHandler<HTMLDivElement | HTMLTextAreaElement>;
16
16
  };
17
17
  type InternalProps = {
18
18
  /** The component used for the root node. */
@@ -1 +1 @@
1
- import*as e from"react/jsx-runtime";import*as t from"@floating-ui/react";import*as a from"react";import*as n from"../common/react.js";function o(o){let s,{allowedOutsideClasses:i,animationDuration:r,children:l,closeAfterInteract:c,content:m,contentAutoWidth:d,contentClasses:p,contentOffset:f=6,contentStyles:u,disabled:g,hoverDelay:h,hoverOpen:x,onMounted:F,placement:b,stopChildPropagation:j,onOpenChange:C}=o,[k,v]=(0,a.useState)(!1),{refs:w,floatingStyles:E,context:P}=(0,t.useFloating)({open:k,onOpenChange(e){v(e),C?.(e)},whileElementsMounted:(e,a,n)=>(void 0!==F&&F(),(0,t.autoUpdate)(e,a,n)),placement:b||"bottom",transform:!1,middleware:[(0,t.offset)(f),(0,t.flip)({padding:6,fallbackPlacements:["bottom-start","bottom-end","top","top-start","top-end"]}),d&&(0,t.size)({apply({rects:e,elements:t}){t.floating.style.width=`${e.reference.width}px`}})]}),{isMounted:y,status:S}=(0,t.useTransitionStatus)(P,{duration:r||200}),M=(0,t.useDismiss)(P,{ancestorScroll:!0,outsidePress:e=>i&&e.target instanceof Element&&!e.target.closest(i)||!1}),O=(0,t.useClick)(P,{enabled:!g}),z=(0,t.useHover)(P,{enabled:!g,restMs:h||200}),{getReferenceProps:D,getFloatingProps:H}=(0,t.useInteractions)([M,x?z:O]),I=D({ref:w.setReference,...j&&{onClick:e=>e.stopPropagation()}}),N=H({onClick:()=>{c&&P.onOpenChange(!1)}});return s=(0,a.isValidElement)(l)?(0,a.cloneElement)(l,I):(0,e.jsx)("div",{...I,children:l}),(0,e.jsxs)(e.Fragment,{children:[s,y&&!!m&&(0,e.jsx)(t.FloatingPortal,{children:(0,e.jsx)("div",{ref:w.setFloating,className:(0,n.classes)(["Floating",!r&&"Floating--animated",p]),"data-position":P.placement,"data-transition":S,style:{...E,...u},...N,children:m})})]})}export{o as Floating};
1
+ import*as e from"react/jsx-runtime";import*as t from"@floating-ui/react";import*as a from"react";import*as n from"../common/react.js";function o(o){let s,{allowedOutsideClasses:i,animationDuration:r,children:l,closeAfterInteract:c,content:m,contentAutoWidth:d,contentClasses:p,contentOffset:f=6,contentStyles:u,disabled:g,hoverDelay:h,hoverOpen:x,onMounted:F,placement:b,stopChildPropagation:j,onOpenChange:C}=o,[k,v]=(0,a.useState)(!1),{refs:w,floatingStyles:E,context:P}=(0,t.useFloating)({open:k,onOpenChange(e){v(e),C?.(e)},whileElementsMounted:(e,a,n)=>(void 0!==F&&F(),(0,t.autoUpdate)(e,a,n)),placement:b||"bottom",transform:!1,middleware:[(0,t.offset)(f),(0,t.flip)({padding:6,fallbackPlacements:["bottom-start","bottom-end","top","top-start","top-end"]}),d&&(0,t.size)({apply({rects:e,elements:t}){t.floating.style.width=`${e.reference.width}px`}})]}),{isMounted:y,status:S}=(0,t.useTransitionStatus)(P,{duration:r||200}),M=(0,t.useDismiss)(P,{ancestorScroll:!0,outsidePress:e=>!i||e.target instanceof Element&&!e.target.closest(i)}),O=(0,t.useClick)(P,{enabled:!g}),z=(0,t.useHover)(P,{enabled:!g,restMs:h||200}),{getReferenceProps:D,getFloatingProps:H}=(0,t.useInteractions)([M,x?z:O]),I=D({ref:w.setReference,...j&&{onClick:e=>e.stopPropagation()}}),N=H({onClick:()=>{c&&P.onOpenChange(!1)}});return s=(0,a.isValidElement)(l)?(0,a.cloneElement)(l,I):(0,e.jsx)("div",{...I,children:l}),(0,e.jsxs)(e.Fragment,{children:[s,y&&!!m&&(0,e.jsx)(t.FloatingPortal,{children:(0,e.jsx)("div",{ref:w.setFloating,className:(0,n.classes)(["Floating",!r&&"Floating--animated",p]),"data-position":P.placement,"data-transition":S,style:{...E,...u},...N,children:m})})]})}export{o as Floating};
@@ -1 +1 @@
1
- import*as t from"react/jsx-runtime";import*as e from"../common/react.js";import*as s from"../common/ui.js";import*as n from"./DmIcon.js";import*as o from"./Icon.js";import*as a from"./Image.js";import*as i from"./Stack.js";import*as c from"./Tooltip.js";function m(o){let{asset:i,assetSize:m=32,base64:r,buttons:p,buttonsAlt:u,children:x,className:d,color:g,disabled:_,dmFallback:h,dmIcon:I,dmIconState:j,fluid:B,imageSize:f=64,imageSrc:$,onClick:y,onRightClick:N,selected:b,title:v,tooltip:w,tooltipPosition:k,...D}=o,S=(0,t.jsxs)("div",{className:"ImageButton__container",tabIndex:_?void 0:0,onClick:t=>{!_&&y&&y(t)},onKeyDown:t=>{"Enter"===t.key&&!_&&y&&y(t)},onContextMenu:t=>{t.preventDefault(),!_&&N&&N(t)},style:{width:B?"auto":`calc(${f}px + 0.5em + 2px)`},children:[(0,t.jsx)("div",{className:"ImageButton__image",children:r||$?(0,t.jsx)(a.Image,{src:r?`data:image/png;base64,${r}`:$,height:`${f}px`,width:`${f}px`}):I&&j?(0,t.jsx)(n.DmIcon,{icon:I,icon_state:j,fallback:h||(0,t.jsx)(l,{icon:"spinner",spin:!0,size:f}),height:`${f}px`,width:`${f}px`}):i?(0,t.jsx)(a.Image,{className:(0,e.classes)(i||[]),height:`${f}px`,width:`${f}px`,style:{transform:`scale(${f/m})`,transformOrigin:"top left"}}):(0,t.jsx)(l,{icon:"question",size:f})}),B?(0,t.jsxs)("div",{className:"ImageButton__content",children:[v&&(0,t.jsx)("span",{className:(0,e.classes)(["ImageButton__content--title",!!x&&"ImageButton__content--divider"]),children:v}),x&&(0,t.jsx)("span",{className:"ImageButton__content--text",children:x})]}):!!x&&(0,t.jsx)("span",{className:"ImageButton__content",children:x})]});return w&&(S=(0,t.jsx)(c.Tooltip,{content:w,position:k,children:S})),(0,t.jsxs)("div",{className:(0,e.classes)(["ImageButton",B&&"ImageButton--fluid",b&&"ImageButton--selected",_&&"ImageButton--disabled",g&&"string"==typeof g?`ImageButton__color--${g}`:"ImageButton__color--default",d]),...(0,s.computeBoxProps)(D),children:[S,p&&(0,t.jsx)("div",{className:(0,e.classes)(["ImageButton__buttons",!x&&"ImageButton__buttons--empty"]),style:{width:"auto"},children:p}),u&&(0,t.jsx)("div",{className:(0,e.classes)(["ImageButton__buttons","ImageButton__buttons--alt",!x&&"ImageButton__buttons--empty"]),style:{width:`calc(${f}px + ${.5*!B}em)`,maxWidth:B?"auto":`calc(${f}px + 0.5em)`},children:u})]})}function l(e){let{icon:s,spin:n=!1,size:a=64}=e;return(0,t.jsx)(i.Stack,{height:`${a}px`,width:`${a}px`,children:(0,t.jsx)(i.Stack.Item,{grow:!0,textAlign:"center",align:"center",children:(0,t.jsx)(o.Icon,{spin:n,name:s,color:"gray",style:{fontSize:`calc(${a}px * 0.75)`}})})})}export{m as ImageButton};
1
+ import*as t from"react/jsx-runtime";import*as e from"../common/react.js";import*as s from"../common/ui.js";import*as n from"./DmIcon.js";import*as o from"./Icon.js";import*as a from"./Image.js";import*as i from"./Stack.js";import*as c from"./Tooltip.js";function m(o){let{asset:i,assetSize:m=32,base64:r,buttons:u,buttonsAlt:p,children:x,className:g,color:d,disabled:I,dmFallback:_,dmIcon:h,dmIconState:j,fluid:B,imageSize:f=64,imageSrc:$,onClick:y,onRightClick:N,selected:b,title:v,tooltip:w,tooltipPosition:k,...D}=o,S=(0,t.jsxs)("div",{className:"ImageButton__container",tabIndex:I?void 0:0,onClick:t=>{!I&&y&&y(t)},onKeyDown:t=>{"Enter"===t.key&&!I&&y&&y(t)},onContextMenu:t=>{t.preventDefault(),!I&&N&&N(t)},style:{width:B?"auto":`calc(${f}px + 0.5em + 2px)`},children:[(0,t.jsx)("div",{className:"ImageButton__image",children:r||$?(0,t.jsx)(a.Image,{src:r?`data:image/png;base64,${r}`:$,height:`${f}px`,width:`${f}px`}):h&&j?(0,t.jsx)(n.DmIcon,{icon:h,icon_state:j,fallback:_||(0,t.jsx)(l,{spin:!0,icon:"spinner",size:f}),height:`${f}px`,width:`${f}px`}):i?(0,t.jsx)(a.Image,{className:(0,e.classes)(i||[]),height:`${f}px`,width:`${f}px`,style:{transform:`scale(${f/m})`,transformOrigin:"top left"}}):(0,t.jsx)(l,{icon:"question",size:f})}),B?(0,t.jsxs)("div",{className:"ImageButton__content",children:[v&&(0,t.jsx)("span",{className:(0,e.classes)(["ImageButton__content--title",!!x&&"ImageButton__content--divider"]),children:v}),x&&(0,t.jsx)("span",{className:"ImageButton__content--text",children:x})]}):!!x&&(0,t.jsx)("span",{className:"ImageButton__content",children:x})]});return w&&(S=(0,t.jsx)(c.Tooltip,{content:w,position:k,children:S})),(0,t.jsxs)("div",{className:(0,e.classes)(["ImageButton",B&&"ImageButton--fluid",b&&"ImageButton--selected",I&&"ImageButton--disabled",!x&&"ImageButton--empty",!(y||N)&&"ImageButton--noAction",d&&"string"==typeof d?`ImageButton__color--${d}`:"ImageButton__color--default",g]),...(0,s.computeBoxProps)(D),children:[S,u&&(0,t.jsx)("div",{className:(0,e.classes)(["ImageButton__buttons",!x&&"ImageButton__buttons--empty"]),style:{width:"auto"},children:u}),p&&(0,t.jsx)("div",{className:(0,e.classes)(["ImageButton__buttons","ImageButton__buttons--alt",!x&&"ImageButton__buttons--empty"]),style:{width:`calc(${f}px + ${.5*!B}em)`,maxWidth:B?"auto":`calc(${f}px + 0.5em)`},children:p})]})}function l(e){let{icon:s,spin:n,size:a=64}=e;return(0,t.jsx)(i.Stack,{height:`${a}px`,width:`${a}px`,children:(0,t.jsx)(i.Stack.Item,{grow:!0,textAlign:"center",align:"center",children:(0,t.jsx)(o.Icon,{spin:n,name:s,color:"gray",style:{fontSize:`calc(${a}px * 0.75)`}})})})}export{m as ImageButton};
@@ -1,63 +1,73 @@
1
- import { type SyntheticEvent } from 'react';
2
- import { type BoxProps } from './Box';
3
- type ConditionalProps = {
4
- /**
5
- * Mark this if you want to debounce onInput.
6
- *
7
- * This is useful for expensive filters, large lists etc.
8
- *
9
- * Requires `onInput` to be set.
10
- */
11
- expensive?: boolean;
12
- /**
13
- * Fires on each key press / value change. Used for searching.
14
- *
15
- * If it's a large list, consider using `expensive` prop.
16
- */
17
- onInput: (event: SyntheticEvent<HTMLInputElement>, value: string) => void;
18
- } | {
19
- /** This prop requires onInput to be set */
20
- expensive?: never;
21
- onInput?: never;
22
- };
23
- type OptionalProps = Partial<{
1
+ import { type RefObject } from 'react';
2
+ import type { BoxProps } from './Box';
3
+ export type BaseInputProps = Partial<{
24
4
  /** Automatically focuses the input on mount */
25
5
  autoFocus: boolean;
26
6
  /** Automatically selects the input value on focus */
27
7
  autoSelect: boolean;
28
- /** The class name of the input */
8
+ /** Custom css classes */
29
9
  className: string;
30
- /** Disables the input */
10
+ /** Disables the input. Outlined in gray */
31
11
  disabled: boolean;
32
- /** Mark this if you want the input to be as wide as possible */
12
+ /** Fills the parent container */
33
13
  fluid: boolean;
34
- /** The maximum length of the input value */
35
- maxLength: number;
36
14
  /** Mark this if you want to use a monospace font */
37
15
  monospace: boolean;
38
- /** Fires when user is 'done typing': Clicked out, blur, enter key */
39
- onChange: (event: SyntheticEvent<HTMLInputElement>, value: string) => void;
16
+ }> & BoxProps;
17
+ export type TextInputProps = Partial<{
18
+ /** The maximum length of the input value */
19
+ maxLength: number;
20
+ /** Fires each time the input has been changed */
21
+ onChange: (value: string) => void;
40
22
  /** Fires once the enter key is pressed */
41
- onEnter: (event: SyntheticEvent<HTMLInputElement>, value: string) => void;
23
+ onEnter: (value: string) => void;
42
24
  /** Fires once the escape key is pressed */
43
- onEscape: (event: SyntheticEvent<HTMLInputElement>) => void;
25
+ onEscape: (value: string) => void;
44
26
  /** The placeholder text when everything is cleared */
45
27
  placeholder: string;
46
28
  /** Clears the input value on enter */
47
29
  selfClear: boolean;
30
+ /**
31
+ * Generally, input can handle its own state value.
32
+ *
33
+ * Use this if you want to hold the value in the parent for external manipulation.
34
+ *
35
+ * ```tsx
36
+ * const [value, setValue] = useState('');
37
+ *
38
+ * return (
39
+ * <>
40
+ * <Button onClick={() => act('inputVal', {inputVal: value})}>
41
+ * Submit
42
+ * </Button>
43
+ * <Input value={value} onChange={setValue} />
44
+ * <Button onClick={() => setValue('')}>
45
+ * Clear
46
+ * </Button>
47
+ * </>
48
+ * )
49
+ * ```
50
+ */
51
+ value: string;
52
+ }> & BaseInputProps;
53
+ type Props = Partial<{
54
+ /**
55
+ * Whether to debounce the input.
56
+ * Do this if it's performing expensive ops on each input.
57
+ * It will only fire once every 250ms.
58
+ */
59
+ expensive: boolean;
60
+ /** Ref of the input element */
61
+ ref: RefObject<HTMLInputElement | null>;
48
62
  /** Auto-updates the input value on props change, ie, data from Byond */
49
63
  updateOnPropsChange: boolean;
50
- /** The state variable of the input. */
51
- value: string | number;
52
- }>;
53
- type Props = OptionalProps & ConditionalProps & Omit<BoxProps, 'children'>;
54
- type InputValue = string | number | undefined;
55
- export declare function toInputValue(value: InputValue): string;
64
+ }> & BaseInputProps & TextInputProps;
56
65
  /**
57
- * ### Input
66
+ * ## Input
67
+ *
58
68
  * A basic text input which allow users to enter text into a UI.
59
- * > Input does not support custom font size and height due to the way
60
- * > it's implemented in CSS. Eventually, this needs to be fixed.
69
+ *
70
+ * @see https://github.com/tgstation/tgui-core/blob/main/lib/components/Input.tsx
61
71
  */
62
72
  export declare function Input(props: Props): import("react/jsx-runtime").JSX.Element;
63
73
  export {};
@@ -1 +1 @@
1
- import*as e from"react/jsx-runtime";import*as t from"react";import*as r from"../common/keys.js";import*as n from"../common/react.js";import*as u from"../common/timer.js";import*as a from"./Box.js";function o(e){return"number"!=typeof e&&"string"!=typeof e?"":String(e)}let c=(0,u.debounce)(e=>e(),250);function s(u){let{autoFocus:s,autoSelect:l,className:i,disabled:m,expensive:f,fluid:p,maxLength:g,monospace:v,onChange:d,onEnter:j,onEscape:x,onInput:T,placeholder:E,selfClear:I,updateOnPropsChange:b,value:y,...h}=u,_=(0,t.useRef)(null);function k(e){let t=_.current;if(!t)return;let r=o(e);t.value!==r&&(t.value=r)}return(0,t.useEffect)(()=>{let e=_.current;if(e){k(y);let t=document.activeElement===e;(s||l)&&!t&&setTimeout(()=>{e.focus(),l&&e.select()},1)}},[]),(0,t.useEffect)(()=>{if(b){let e=_.current;e&&document.activeElement!==e&&k(y)}},[y]),(0,e.jsxs)(a.Box,{className:(0,n.classes)(["Input",p&&"Input--fluid",v&&"Input--monospace",i]),...h,children:[(0,e.jsx)("div",{className:"Input__baseline",children:"."}),(0,e.jsx)("input",{className:"Input__input",disabled:m,maxLength:g,onBlur:e=>d?.(e,e.target.value),onChange:function(e){if(!T)return;let t=e.currentTarget?.value;f?c(()=>T(e,t)):T(e,t)},onKeyDown:function(e){if(e.key===r.KEY.Enter){j?.(e,e.currentTarget.value),I?e.currentTarget.value="":(e.currentTarget.blur(),d?.(e,e.currentTarget.value));return}(0,r.isEscape)(e.key)&&(x?.(e),e.currentTarget.value=o(y),e.currentTarget.blur())},placeholder:E,ref:_})]})}export{s as Input,o as toInputValue};
1
+ import*as e from"react/jsx-runtime";import*as t from"react";import*as r from"../common/keys.js";import*as u from"../common/react.js";import*as o from"../common/timer.js";import*as n from"../common/ui.js";let a=(0,o.debounce)(e=>e(),250);function c(o){let{autoFocus:c,autoSelect:s,className:m,disabled:l,expensive:p,fluid:f,maxLength:i,monospace:v,onChange:d,onEnter:g,onEscape:T,placeholder:j,ref:x,selfClear:E,updateOnPropsChange:b,...y}=o,I=(0,t.useRef)(null),h=x??I,[k,C]=(0,t.useState)(o.value),D=(0,t.useMemo)(()=>(0,n.computeBoxProps)(y),[y]),K=(0,t.useMemo)(()=>(0,u.classes)(["Input",l&&"Input--disabled",f&&"Input--fluid",v&&"Input--monospace",m]),[m,f,v]);return(0,t.useEffect)(()=>{let e;return(c||s)&&(e=setTimeout(()=>{h.current?.focus(),s&&h.current?.select()},1)),()=>clearTimeout(e)},[]),(0,t.useEffect)(()=>{b&&h.current&&document.activeElement!==h.current&&o.value!==k&&C(o.value??"")},[o.value]),(0,e.jsx)("input",{...D,autoComplete:"off",className:K,disabled:l,maxLength:i,onChange:function(e){let t=e.currentTarget?.value;p?a(()=>d?.(t)):d?.(t),C(t)},onKeyDown:function(e){if(e.key===r.KEY.Enter){e.preventDefault(),g?.(e.currentTarget.value),E&&C(""),e.currentTarget.blur();return}(0,r.isEscape)(e.key)&&(e.preventDefault(),T?.(e.currentTarget.value),e.currentTarget.blur())},placeholder:j,ref:h,type:"text",value:k,spellCheck:!1})}export{c as Input};
@@ -1,29 +1,46 @@
1
- import type { BoxProps } from './Box';
2
- type Props = {
3
- value: number;
4
- } & Partial<{
1
+ import type { BaseInputProps } from './Input';
2
+ type Props = Partial<{
3
+ /** Restricted inputs round by default. */
5
4
  allowFloats: boolean;
6
- autoFocus: boolean;
7
- autoSelect: boolean;
8
- disabled: boolean;
9
- fluid: boolean;
10
- maxValue: number | null;
11
- minValue: number | null;
12
- onBlur: (e: Event, value: number) => void;
13
- onChange: (e: Event, value: number) => void;
14
- onEnter: (e: Event, value: number) => void;
15
- }> & BoxProps;
5
+ /** Max value. 10,000 by default. */
6
+ maxValue: number;
7
+ /** Min value. 0 by default. */
8
+ minValue: number;
9
+ /** Fires each time the input has been changed */
10
+ onChange: (value: number) => void;
11
+ /** Fires once the enter key is pressed */
12
+ onEnter: (value: number) => void;
13
+ /** Fires once the escape key is pressed */
14
+ onEscape: (value: number) => void;
15
+ /**
16
+ * Generally, input can handle its own state value.
17
+ *
18
+ * Use this if you want to hold the value in the parent for external manipulation.
19
+ *
20
+ * ```tsx
21
+ * const [value, setValue] = useState(1;
22
+ *
23
+ * return (
24
+ * <>
25
+ * <Button onClick={() => act('inputVal', {inputVal: value})}>
26
+ * Submit
27
+ * </Button>
28
+ * <RestrictedInput value={value} onChange={setValue} />
29
+ * <Button onClick={() => setValue(1)}>
30
+ * Clear
31
+ * </Button>
32
+ * </>
33
+ * )
34
+ * ```
35
+ */
36
+ value: number;
37
+ }> & BaseInputProps;
16
38
  /**
17
39
  * ## RestrictedInput
18
- * Creates an input which rejects improper keys.
19
- *
20
- * @deprecated Use `NumberInput` instead.
21
40
  *
22
- * This will server as a wrapper for NumberInput until removal. This decision was
23
- * made because it's poor UX. Users should be allowed to type in whatever they
24
- * want, but have the UI notify them it's invalid after it's entered.
41
+ * Creates a numerical input which rejects improper keys.
25
42
  *
26
- * It also gives a false sense of security. It's just an annoying input.
43
+ * @see https://github.com/tgstation/tgui-core/blob/main/lib/components/RestrictedInput.tsx
27
44
  */
28
45
  export declare function RestrictedInput(props: Props): import("react/jsx-runtime").JSX.Element;
29
46
  export {};
@@ -1 +1 @@
1
- import*as e from"react/jsx-runtime";import*as r from"../common/keys.js";import*as t from"./NumberInput.js";function m(m){let{minValue:n,maxValue:u,onChange:o,onEnter:a,onBlur:s,...p}=m;return(0,e.jsx)(t.NumberInput,{...p,minValue:n??0,maxValue:u??Number.POSITIVE_INFINITY,onChange:e=>{o?.({},e)},onKeyDown:e=>{e.key===r.KEY.Enter&&a?.({},Number(e.currentTarget.value))},step:1})}export{m as RestrictedInput};
1
+ import*as e from"react/jsx-runtime";import*as t from"../common/math.js";import*as r from"../common/react.js";import*as u from"../common/ui.js";import*as n from"react";import*as o from"../common/keys.js";function m(m){let{allowFloats:s,autoFocus:a,autoSelect:c,className:i,disabled:l,fluid:f,maxValue:p=1e4,minValue:v=0,monospace:d,onChange:b,onEnter:E,onEscape:j,...y}=m,I=(0,n.useRef)(null),[k,x]=(0,n.useState)(m.value??v),N=(0,n.useMemo)(()=>(0,u.computeBoxProps)(y),[y]),h=(0,n.useMemo)(()=>(0,r.classes)(["Input","RestrictedInput",l&&"Input--disabled",f&&"Input--fluid",d&&"Input--monospace",i]),[i,f,d]);function D(e){if(e===k)return;let r=e;Number.isNaN(e)?r=v:s||(r=Math.round(e)),x(r=(0,t.clamp)(r,v,p)),b?.(r)}return(0,n.useEffect)(()=>{let e;return(a||c)&&(e=setTimeout(()=>{I.current?.focus(),c&&I.current?.select()},1)),()=>clearTimeout(e)},[]),(0,n.useEffect)(()=>{document.activeElement===I.current&&m.value!==k&&x(m.value??v)},[m.value]),(0,e.jsx)("input",{...N,autoComplete:"off",className:h,disabled:l,max:p,min:v,onChange:function(e){D(Number(e.target.value))},onKeyDown:function(e){if(e.key===o.KEY.Enter){e.preventDefault(),E?.(k),I.current?.blur();return}if((0,o.isEscape)(e.key)){e.preventDefault(),j?.(k),I.current?.blur();return}if(e.key===o.KEY.Minus){e.preventDefault(),D(-1*k);return}},ref:I,spellCheck:!1,type:"number",value:k})}export{m as RestrictedInput};
@@ -1,46 +1,24 @@
1
- import type { RefObject, SyntheticEvent } from 'react';
2
- import { type BoxProps } from './Box';
1
+ import type { RefObject } from 'react';
2
+ import type { TextInputProps } from './Input';
3
3
  type Props = Partial<{
4
- /** Automatically focus the textarea on mount */
5
- autoFocus: boolean;
6
- /** Selects all text on mount */
7
- autoSelect: boolean;
8
- /** The value to display in the textarea */
9
- displayedValue: string;
10
4
  /** Don't use tab for indent */
11
5
  dontUseTabForIndent: boolean;
12
- /** Sets width to 100% */
13
- fluid: boolean;
14
- /** Maximum length of the textarea */
15
- maxLength: number;
16
- /** Removes the border. */
17
- noborder: boolean;
18
- /** Fires when user is 'done typing': Clicked out, blur, enter key (but not shift+enter) */
19
- onChange: (event: SyntheticEvent<HTMLTextAreaElement>, value: string) => void;
20
- /** Fires once the enter key is pressed */
21
- onEnter: (event: SyntheticEvent<HTMLTextAreaElement>, value: string) => void;
22
- /** Fires once the escape key is pressed */
23
- onEscape: (event: SyntheticEvent<HTMLTextAreaElement>) => void;
24
- /** Fires on each key press / value change. Used for searching */
25
- onInput: (event: SyntheticEvent<HTMLTextAreaElement>, value: string) => void;
26
- /** Dummy text inside the textarea when it's empty */
27
- placeholder: string;
28
6
  /** Ref to the textarea element. */
29
7
  ref: RefObject<HTMLTextAreaElement | null>;
30
- /** Whether the textarea is scrollable when it has more content than height */
31
- scrollbar: boolean;
32
- /** Clears the textarea when the enter key is pressed */
33
- selfClear: boolean;
34
- /** Provides a Record with key: markupChar entries which can be used for ctrl + key combinations to surround a selected text with the markup character */
8
+ /**
9
+ * Provides a Record with key: markupChar entries which can be used for
10
+ * ctrl + key combinations to surround a selected text with the markup
11
+ * character
12
+ */
35
13
  userMarkup: Record<string, string>;
36
- /** The value of the textarea */
37
- value: string;
38
- }> & BoxProps;
14
+ }> & TextInputProps;
39
15
  /**
40
16
  * ## Textarea
41
17
  *
42
18
  * An input for larger amounts of text. Use this when you want inputs larger
43
19
  * than one row.
20
+ *
21
+ * @see https://github.com/tgstation/tgui-core/blob/main/lib/components/TextArea.tsx
44
22
  */
45
23
  export declare function TextArea(props: Props): import("react/jsx-runtime").JSX.Element;
46
24
  export {};
@@ -1 +1 @@
1
- import*as e from"react/jsx-runtime";import*as r from"react";import*as t from"../common/keys.js";import*as a from"../common/react.js";import*as s from"./Box.js";import*as n from"./Input.js";function u(u){let{autoFocus:l,autoSelect:c,displayedValue:o,dontUseTabForIndent:i,maxLength:f,noborder:m,onChange:T,onEnter:x,onEscape:g,onInput:p,placeholder:v,ref:_,scrollbar:d,selfClear:y,userMarkup:b,value:h,...A}=u,{className:j,fluid:E,nowrap:$,...k}=A,K=(0,r.useRef)(null),N=_??K,[B,C]=(0,r.useState)(0);return(0,r.useEffect)(()=>{if(l||c){let e=N.current;e&&setTimeout(()=>{e.focus(),c&&e.select()},1)}},[]),(0,r.useEffect)(()=>{let e=N.current;if(e){let r=(0,n.toInputValue)(h);e.value!==r&&(e.value=r)}},[h]),(0,e.jsxs)(s.Box,{className:(0,a.classes)(["TextArea",E&&"TextArea--fluid",m&&"TextArea--noborder",j]),...k,children:[!!o&&(0,e.jsx)("div",{className:"TextArea__value-container",children:(0,e.jsx)("div",{className:(0,a.classes)(["TextArea__textarea","TextArea__textarea_custom"]),style:{transform:`translateY(-${B}px)`},children:o})}),(0,e.jsx)("textarea",{autoComplete:"off",className:(0,a.classes)(["TextArea__textarea",d&&"TextArea__textarea--scrollable",$&&"TextArea__nowrap"]),maxLength:f,onBlur:e=>T?.(e,e.target.value),onChange:e=>p?.(e,e.target.value),onKeyDown:function(e){if(e.key===t.KEY.Enter)return e.shiftKey?void e.currentTarget.focus():(x?.(e,e.currentTarget.value),y&&(e.currentTarget.value=""),void e.currentTarget.blur());if((0,t.isEscape)(e.key)){g?.(e),y?e.currentTarget.value="":(e.currentTarget.value=(0,n.toInputValue)(h),e.currentTarget.blur());return}if(!i&&e.key===t.KEY.Tab){e.preventDefault();let{value:r,selectionStart:t,selectionEnd:a}=e.currentTarget;e.currentTarget.value=`${r.substring(0,t)} ${r.substring(a)}`,e.currentTarget.selectionEnd=t+1}if(b&&(e.ctrlKey||e.metaKey)&&b[e.key]){e.preventDefault();let{value:r,selectionStart:t,selectionEnd:a}=e.currentTarget,s=b[e.key];e.currentTarget.value=`${r.substring(0,t)}${s}${r.substring(t,a)}${s}${r.substring(a)}`,e.currentTarget.selectionEnd=a+2*s.length}},onScroll:()=>{o&&N.current&&C(N.current.scrollTop)},placeholder:v,ref:N,spellCheck:!1,style:{color:o?"rgba(0, 0, 0, 0)":"inherit"}})]})}export{u as TextArea};
1
+ import*as e from"react/jsx-runtime";import*as t from"react";import*as r from"../common/keys.js";import*as u from"../common/react.js";import*as n from"../common/ui.js";function a(a){let{autoFocus:s,autoSelect:c,disabled:o,dontUseTabForIndent:l,fluid:m,maxLength:i,monospace:f,onChange:p,onEnter:g,onEscape:T,placeholder:v,ref:y,selfClear:b,userMarkup:E,...x}=a,d=(0,t.useRef)(null),k=y??d,[$,h]=(0,t.useState)(a.value??""),K=(0,t.useMemo)(()=>(0,n.computeBoxProps)(x),[x]),j=(0,t.useMemo)(()=>(0,u.classes)(["Input","TextArea",m&&"Input--fluid",f&&"Input--monospace",o&&"Input--disabled"]),[o,m,f]);return(0,t.useEffect)(()=>{(s||c)&&setTimeout(()=>{k.current?.focus(),c&&k.current?.select()},1)},[]),(0,t.useEffect)(()=>{document.activeElement===k.current&&a.value!==$&&h(a.value??"")},[a.value]),(0,e.jsx)("textarea",{...K,autoComplete:"off",className:j,maxLength:i,onChange:function(e){h(e.currentTarget.value)},onKeyDown:function(e){if(e.key===r.KEY.Enter&&!e.shiftKey){e.preventDefault(),g?.(e.currentTarget.value),b&&h(""),e.currentTarget.blur();return}if((0,r.isEscape)(e.key)){T?.(e.currentTarget.value),e.currentTarget.blur();return}if(!l&&e.key===r.KEY.Tab){e.preventDefault();let{value:t,selectionStart:r,selectionEnd:u}=e.currentTarget;h(`${t.substring(0,r)} ${t.substring(u)}`),e.currentTarget.selectionEnd=r+1,p?.(e.currentTarget.value);return}if(E&&(e.ctrlKey||e.metaKey)&&E[e.key]){e.preventDefault();let{selectionStart:t,selectionEnd:r,value:u}=e.currentTarget,n=E[e.key];h(`${u.substring(0,t)}${n}${u.substring(t,r)}${n}${u.substring(r)}`),e.currentTarget.selectionEnd=r+2*n.length,p?.(e.currentTarget.value);return}},placeholder:v,ref:k,spellCheck:!1,value:$})}export{a as TextArea};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tgui-core",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "TGUI core component library",
5
5
  "keywords": ["TGUI", "library", "typescript"],
6
6
  "files": ["dist", "styles"],
@@ -4,15 +4,24 @@ $bg-map: colors.$color-map !default;
4
4
 
5
5
  @mixin button-style($color) {
6
6
  background-color: hsl(
7
- from $color h s calc(l - var(--adjust-color) * 2) / 0.2
7
+ from $color h s calc(l - var(--adjust-color) * 2) /
8
+ var(--imagebutton-transparecy)
8
9
  );
9
- border: 1px solid hsl(from $color h s calc(l + var(--adjust-color) * 2) / 0.2);
10
+ border: var(--border-thickness-tiny) solid
11
+ hsl(
12
+ from $color h s calc(l + var(--adjust-color)) /
13
+ var(--imagebutton-transparecy)
14
+ );
15
+
16
+ &:not(.ImageButton--disabled, .ImageButton--noAction) {
17
+ .ImageButton__container {
18
+ cursor: var(--cursor-pointer);
19
+ }
10
20
 
11
- &:not(.ImageButton--disabled) {
12
21
  &:hover {
13
22
  background-color: hsl(
14
23
  from $color h s calc(l - var(--adjust-color) + var(--adjust-hover)) /
15
- 0.2
24
+ var(--imagebutton-transparecy)
16
25
  );
17
26
  }
18
27
 
@@ -27,7 +36,8 @@ $bg-map: colors.$color-map !default;
27
36
  &:has(.ImageButton__buttons:hover),
28
37
  &:has(.ImageButton__buttons:active) {
29
38
  background-color: hsl(
30
- from $color h s calc(l - var(--adjust-color) * 2) / 0.2
39
+ from $color h s calc(l - var(--adjust-color) * 2) /
40
+ var(--imagebutton-transparecy)
31
41
  );
32
42
  box-shadow: none;
33
43
  }
@@ -51,6 +61,27 @@ $bg-map: colors.$color-map !default;
51
61
  @include button-style(hsl(from var(--color-base) h s 30));
52
62
  }
53
63
 
64
+ .ImageButton__color--primary {
65
+ @include button-style(hsl(from var(--color-primary) h s 30));
66
+ }
67
+
68
+ .ImageButton__color--transparent {
69
+ @include button-style(hsl(from var(--color-base) h s 30));
70
+
71
+ background-color: transparent;
72
+ border-color: transparent !important;
73
+
74
+ .ImageButton__content {
75
+ background-color: transparent !important;
76
+ color: var(--color-text-translucent);
77
+ border-color: transparent !important;
78
+ }
79
+
80
+ &:hover:not(.ImageButton--disabled) .ImageButton__content {
81
+ color: var(--color-text);
82
+ }
83
+ }
84
+
54
85
  .ImageButton--selected {
55
86
  @include button-style(var(--button-background-selected));
56
87
  }
@@ -83,7 +114,6 @@ $bg-map: colors.$color-map !default;
83
114
  border-width: 0;
84
115
 
85
116
  &__container {
86
- cursor: var(--cursor-pointer);
87
117
  display: flex;
88
118
  flex-direction: column;
89
119
  transition: opacity var(--transition-time-medium);
@@ -97,8 +127,8 @@ $bg-map: colors.$color-map !default;
97
127
  overflow: hidden;
98
128
  line-height: 0;
99
129
  padding: var(--space-s);
100
- border: 1px solid;
101
- border-bottom: 0;
130
+ border: var(--border-thickness-tiny) solid;
131
+ border-bottom: none;
102
132
  border-color: inherit;
103
133
  border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0;
104
134
  }
@@ -116,7 +146,7 @@ $bg-map: colors.$color-map !default;
116
146
  display: flex;
117
147
  position: absolute;
118
148
  overflow: hidden;
119
- left: 0;
149
+ left: var(--border-thickness-tiny);
120
150
  bottom: 1.8em;
121
151
  max-width: 100%;
122
152
  z-index: 1;
@@ -125,8 +155,8 @@ $bg-map: colors.$color-map !default;
125
155
  overflow: visible;
126
156
  flex-direction: column;
127
157
  pointer-events: none;
128
- top: 1px;
129
- bottom: inherit !important;
158
+ top: var(--border-thickness-tiny);
159
+ bottom: unset !important;
130
160
  /** Text outline. Sort of. */
131
161
  text-shadow:
132
162
  0px 1px 2px var(--color-base),
@@ -135,6 +165,11 @@ $bg-map: colors.$color-map !default;
135
165
  0px -1px 2px var(--color-base);
136
166
  }
137
167
 
168
+ &--empty {
169
+ bottom: 0;
170
+ left: 0;
171
+ }
172
+
138
173
  & > * {
139
174
  /* I know !important is bad, but here's no other way */
140
175
  margin: 0 !important;
@@ -142,6 +177,20 @@ $bg-map: colors.$color-map !default;
142
177
  border-radius: 0 !important;
143
178
  }
144
179
  }
180
+
181
+ &--empty {
182
+ border-width: var(--border-thickness-tiny);
183
+
184
+ .ImageButton__image {
185
+ border: none;
186
+ border-radius: var(--border-radius-medium);
187
+ }
188
+ }
189
+
190
+ .Stack > &,
191
+ .Stack__item > & {
192
+ margin: 0;
193
+ }
145
194
  }
146
195
 
147
196
  .ImageButton--fluid {
@@ -1,63 +1,45 @@
1
- @use "../base";
2
-
3
- $text-color: 0 !default;
4
- $background-color: 0 !default;
5
- $border-color: 0 !default;
6
- $border-radius: 0 !default;
7
- $font-family: 0 !default;
8
- $mono-font-family: 0 !default;
9
-
10
1
  .Input {
11
- overflow: visible;
12
- position: relative;
13
- display: inline-block;
14
- width: base.em(120px);
15
- line-height: base.em(17px);
16
- padding: 0 var(--space-sm);
17
- margin-right: var(--space-xs);
18
2
  background-color: var(--input-background);
19
- color: var(--input-color);
20
- border: var(--border-thickness-tiny) solid var(--input-border-color);
21
3
  border-radius: var(--input-border-radius);
22
-
4
+ border: var(--border-thickness-tiny) solid var(--input-border-color);
5
+ color: var(--input-color);
6
+ font-family: var(--input-font-family);
7
+ padding: var(--space-xxs) var(--space-sm);
8
+ width: var(--input-width);
9
+ &:focus {
10
+ outline: none;
11
+ }
23
12
  &:focus-within {
24
13
  border-color: var(--input-border-color-focus);
25
14
  }
15
+ &::placeholder {
16
+ font-style: italic;
17
+ color: var(--input-color-placeholder);
18
+ }
26
19
  }
27
20
 
28
- .Input--fluid {
29
- display: block;
30
- width: auto;
31
- }
32
-
33
- .Input__baseline {
34
- display: inline-block;
35
- color: transparent;
21
+ .Input--disabled {
22
+ border-color: var(--input-border-disabled);
23
+ color: var(--input-color-placeholder);
36
24
  }
37
25
 
38
- .Input__input {
39
- display: block;
40
- position: absolute;
41
- inset: 0;
42
- border: 0;
43
- outline: 0;
26
+ .Input--fluid {
44
27
  width: 100%;
45
- font-size: base.em(12px);
46
- line-height: base.em(17px);
47
- height: base.em(17px);
48
- margin: 0;
49
- padding: 0 var(--space-m);
50
- font-family: var(--input-font-family);
51
- background-color: transparent;
52
- color: var(--input-color);
53
- color: inherit;
54
-
55
- &::placeholder {
56
- font-style: italic;
57
- color: var(--input-color-placeholder);
58
- }
59
28
  }
60
29
 
61
- .Input--monospace .Input__input {
30
+ .Input--monospace {
62
31
  font-family: var(--input-font-family-mono);
63
32
  }
33
+
34
+ .RestrictedInput {
35
+ border-color: var(--restricted-input-border-color);
36
+ text-align: right;
37
+ &:focus-within {
38
+ border-color: var(--restricted-input-border-color-focus);
39
+ }
40
+ &::-webkit-inner-spin-button,
41
+ &::-webkit-outer-spin-button {
42
+ -webkit-appearance: none;
43
+ margin: 0;
44
+ }
45
+ }
@@ -1,82 +1,3 @@
1
- @use "../base";
2
- @use "./Input";
3
-
4
- $text-color: 0 !default;
5
- $background-color: 0 !default;
6
- $border-color: 0 !default;
7
- $border-radius: 0 !default;
8
-
9
1
  .TextArea {
10
- background-color: var(--input-background);
11
- border-radius: var(--input-border-radius);
12
- border: var(--border-thickness-tiny) solid var(--input-border-color);
13
- box-sizing: border-box;
14
- display: inline-block;
15
- line-height: base.em(17px);
16
- margin-right: base.em(2px);
17
- position: relative;
18
- width: 100%;
19
- &:focus-within {
20
- border-color: var(--input-border-color-focus);
21
- }
22
- }
23
-
24
- .TextArea--fluid {
25
- display: block;
26
- height: auto;
27
- width: auto;
28
- }
29
-
30
- .TextArea--noborder {
31
- border: 0px;
32
- }
33
-
34
- .TextArea__textarea.TextArea__textarea--scrollable {
35
- overflow: hidden auto;
36
- }
37
-
38
- .TextArea__textarea {
39
- background-color: transparent;
40
- border: 0;
41
- box-sizing: border-box;
42
- color: var(--input-color);
43
- display: block;
44
- font-family: inherit;
45
- font-size: 1em;
46
- height: 100%;
47
- inset: 0;
48
- line-height: base.em(17px);
49
- margin: 0;
50
- min-height: base.em(17px);
51
- outline: 0;
52
- overflow: hidden;
53
- padding: 0 var(--space-m);
54
- position: absolute;
55
2
  resize: none;
56
- width: 100%;
57
- // Make sure the div and the textarea wrap words in the same way
58
- word-wrap: break-word;
59
-
60
- &::placeholder {
61
- font-style: italic;
62
- color: var(--input-color-placeholder);
63
- }
64
- }
65
-
66
- .TextArea__textarea_custom {
67
- overflow: visible;
68
- white-space: pre-wrap;
69
- }
70
-
71
- .TextArea__nowrap {
72
- overflow-wrap: normal;
73
- overflow-x: scroll;
74
- white-space: nowrap;
75
- }
76
-
77
- .TextArea__value-container {
78
- height: 100%;
79
- overflow: hidden;
80
- position: absolute;
81
- width: 100%;
82
3
  }
@@ -80,6 +80,9 @@
80
80
  --tab-transition: var(--transition-time-medium);
81
81
  --tabs-container-background: var(--color-section);
82
82
 
83
+ /* ImageButton */
84
+ --imagebutton-transparecy: 0.25;
85
+
83
86
  /* Input */
84
87
  --input-background-lightness: 5;
85
88
  --input-background: hsl(
@@ -90,9 +93,11 @@
90
93
  --input-border-color: var(--color-border-primary);
91
94
  --input-border-color-focus: var(--color-border-secondary);
92
95
  --input-border-radius: var(--border-radius-tiny);
96
+ --input-border-disabled: var(--color-gray);
93
97
  --input-transition: var(--transition-time-medium);
94
98
  --input-font-family: var(--font-family);
95
99
  --input-font-family-mono: var(--font-family-mono);
100
+ --input-width: 9em;
96
101
 
97
102
  /* Knob */
98
103
  --knob-color: hsl(from var(--color-base) h 5 20);
@@ -131,6 +136,12 @@
131
136
  --progress-bar-border-radius: var(--border-radius-tiny);
132
137
  --progress-bar-transition: var(--transition-time-slowest);
133
138
 
139
+ /* Restricted Input */
140
+ --restricted-input-border-color: var(--color-green);
141
+ --restricted-input-border-color-focus: hsl(
142
+ from var(--color-green) h s calc(l + 10)
143
+ );
144
+
134
145
  /* Round Gauge */
135
146
  --round-gauge-ring-color: var(--input-border-color);
136
147
  --round-gauge-transition: var(--transition-time-medium);