Skip to content

Instantly share code, notes, and snippets.

@sirisian
Created March 18, 2025 18:31
Show Gist options
  • Save sirisian/ff534c72496fe40be8b874deffbe6aa2 to your computer and use it in GitHub Desktop.
Save sirisian/ff534c72496fe40be8b874deffbe6aa2 to your computer and use it in GitHub Desktop.
input web component interface
type Option<T> = {
label: string;
value: T;
}
type Options<T> =
| Record<string, T>
| Option<T>[]
| (() => Option<T>[]);
type Node = {
id: number;
type: number;
name: string;
};
/**
* Inputs of type 'int', 'float', and 'string' can have tree pickers
* All options in this type propagate through the input to the tree picker
* This is like {@link OptionsInput.options} but for nested arrays more or less.
* Not going to comment all of this unless requested.
*/
export type TreeOptions<T> = {
tree?: string;
source?: () => [];
fullSelect?: false;
checkbox?: boolean;
groupTypes?: () => number[];
groupIcon?: (node: Node) => string;
leafIcon?: (node: Node) => string;
nodeTooltip?: (node: Node) => string;
disable?: () => boolean;
filter?: (() => boolean)[];
dragAppendTo?: string | object;
deleteConfirmation?: () => boolean;
clickNode?: () => void;
dblClickNode?: () => void;
saveState?: boolean;
thumbnail?: boolean;
overrideThumbnailDimensions?: (node: Node) => { width: number, height: number };
editable?: (node: Node) => boolean;
showRoot?: boolean;
defaultOptions?: [];
largeImages?: boolean;
filterDraggable?: (node: Node) => boolean;
filterDroppable?: (node: Node) => boolean;
dragHelper?: (e: any, dropClass: string) => Element | undefined;
showMinimizeMaximizeAll?: boolean;
showSmallLargeThumbnail?: boolean;
nameTransform?: (node: Node) => string;
dispatchChangeOnLoad?: boolean;
getValue?: () => T | T[];
setValue?: () => void;
disableDrag?: boolean;
dispatchChangeOnUpDown?: boolean;
emptyValue?: boolean;
processNode?: (node: Node) => void;
setText?: (node: Node) => void;
}
type InputDefaultPart<T> = {
/**
* Default value for the input
*/
default?: T;
/**
* Value to use when opening picker in indeterminate state
*/
defaultOnOpen?: T;
}
/**
* {@link InputDefaults.multiple} will make the type T[]
* {@link InputDefaults.emptyValue} will make the type T | null
* {@link undefinedToIndeterminate} will make the type T | undefined
* This results in 8 possible configurations
*/
type InputDefaults<T> =
| (
& InputDefaultPart<T>
& {
/** Enable multiple value selection, transforms value into an array
* @default false
*/
multiple?: false;
/** Text to display when value is null. If set, enables null values
* @default false
*/
emptyValue?: false;
/** Controls whether undefined values set the input to an indeterminate state
* @default true
*/
undefinedToIndeterminate: false;
}
)
| (
& {
multiple?: false;
emptyValue?: false;
undefinedToIndeterminate?: true;
}
& InputDefaultPart<T | undefined>
)
| (
& {
multiple?: false;
emptyValue: string;
undefinedToIndeterminate: false;
}
& InputDefaultPart<T | null>
)
| (
& {
multiple?: false;
emptyValue: string;
undefinedToIndeterminate?: true;
}
& InputDefaultPart<T | null | undefined>
)
| (
& {
multiple: true;
emptyValue?: false;
undefinedToIndeterminate: false;
}
& InputDefaultPart<T[]>
)
| (
& {
multiple: true;
emptyValue?: false;
undefinedToIndeterminate?: true;
}
& InputDefaultPart<T[] | undefined>
)
| (
& {
multiple: true;
emptyValue: string;
undefinedToIndeterminate: false;
}
& InputDefaultPart<T[] | null>
)
| (
& {
multiple: true;
emptyValue: string;
undefinedToIndeterminate?: true;
}
& InputDefaultPart<T[] | null | undefined>
);
type InputOptions<T> = {
/**
* Attaches a picker with options. One of:
* - Record: An enumeration. Use enumLabels symbol to map labels
* - Array: An explicit array of options
* - Function: A function to call to get an array of options. See {@link refreshOnShow} to load new options on picker open
*/
options?: Options<T>;
/**
* Enables custom inputs when {@link options} is set
*/
customValue?: boolean;
/**
* Allows a custom rendering for each option. e.g. to render font family options as their font family
*/
processOption?: (
$option: Element,
option: {
label: string;
value: T;
}
) => void;
/**
* By default the {@link options} function is evaluated once. Set this to true to reload when the picker is opened
*/
refreshOnShow?: boolean;
/**
* {@link options} are displayed as radio buttons
* {@link multiple} cannot be set at the same time. See {@link groupedButton} for that
*/
radio?: boolean;
/**
* {@link options} are displayed as grouped buttons. e.g. [A|B|C]
* multiple makes the buttons toggleable
*/
groupedButton?: boolean;
/**
* {@link options} are displayed in a list rather than a picker
*/
inlinePicker?: boolean;
/**
* The minimum height in option items. If {@link multiple} is enabled and size is not 0 then the picker is displayed as if {@link inlinePicker} is set
*/
size?: number;
/**
* If true then this records the last 10 entered values. Use a number for a custom history amount
*/
history?: boolean | number;
/**
* localStorage key used to hold the {@link history} values. Can be used to share histories between inputs of the same {@link type}
*/
historyKey?: string;
/**
* Enables autocomplete for {@link options} and {@link history}
*/
search?: boolean;
}
type InputProgress = {
/**
* Turns this into a progress bar
*/
progress?: boolean;
/**
* If progress is set, this customizes the color
*/
progressColor?: string;
}
type InputMasked = {
/**
* Enables masked input mode
*/
masked?: boolean;
/**
* Used in masked inputs for up key
*/
increment?: () => void;
/**
* Used in masked inputs for down key
*/
decrement?: () => void;
}
type InputDisplay = {
/**
* When not focused this can transform the value into a custom string. On focus it will show the value
* As an example if you want to display a value as '100%'
*/
display?: () => string;
}
/**
* An input for storing a boolean
*/
export type BooleanInput =
& {
type: 'boolean';
}
& InputDefaults<boolean>;
/**
* An input for storing an integer. Disallows decimals
*/
export type IntInput =
& {
type: 'int';
/**
* Turns this into a slider input
*/
slider?: boolean;
/**
* Minimum inclusive value
*/
min?: number;
/**
* Maximum inclusive value
*/
max?: number;
/**
* The step size when incrementing/decrementing
*/
step?: number;
}
& InputDefaults<number>
& InputOptions<number>
& InputProgress
& InputMasked
& InputDisplay
& TreeOptions<number>;
/**
* An input for storing a number. Allow decimals
*/
export type FloatInput =
& {
type: 'number';
/**
* Turns this into a slider input
*/
slider?: boolean;
/**
* Minimum inclusive value
*/
min?: number;
/**
* Maximum inclusive value
*/
max?: number;
/**
* The step size when incrementing/decrementing
*/
step?: number;
}
& InputDefaults<number>
& InputOptions<number>
& InputProgress
& InputMasked
& InputDisplay
& TreeOptions<number>;
/**
* An input for storing a string
*/
export type StringInput =
& {
type: 'string';
/**
* A placeholder when equal to clearValue
*/
placeholder?: string;
/**
* Creates a pseudo textarea control
*/
multiline?: boolean;
/**
* Controls the height when multiline is used. lines === 0 creates a picker, lines >= 1 is resizable, default is lines === 1
*/
lines?: number;
/**
* Turns this into a password input
*/
password?: boolean;
/**
* Enables spellcheck
*/
spellcheck?: boolean;
/**
* The value must match this pattern to validate
*/
pattern?: RegExp | string;
}
& InputDefaults<string>
& InputOptions<string>
& InputMasked
& InputDisplay
& TreeOptions<string>;
/**
* This is a container for constructing custom objects
* Implement {@link getValue} and {@link setValue}
*/
export type ObjectInput = {
type: 'object';
default?: Object;
/**
* Called when the input value is accessed
*/
getValue: () => Object;
/**
* Called when the input value is set. Use this to update the input rendering
*/
setValue: (value: Object) => void;
};
/**
* This is a container for constructing custom arrays
* Implement {@link getValue} and {@link setValue}
*/
export type ArrayInput = {
type: 'array';
default?: [];
/**
* Called when the input value is accessed
*/
getValue: () => [];
/**
* Called when the input value is set. Use this to update the input rendering
*/
setValue: (value: []) => void;
};
export type ColorInput = InputDefaults<string> | {
type: 'color';
/**
* Enables opacity
*/
opacity?: boolean;
/**
* A custom set of colors that user can pick. This is similar to options in other input types
*/
palette?: string[];
/**
* Only shows palette colors listed in palette
*/
showPaletteOnly?: boolean;
/**
* Renders in a compact width form that only displays the color, not the text value
*/
compact?: boolean;
/**
* If true then this records the last 10 entered values. Use a number for a custom history amount
*/
history?: boolean | number;
/**
* localStorage key used to hold the history values. Can be used to share histories between inputs
*/
historyKey?: string;
/**
* Create a palette option for transparent
*/
transparentButton?: boolean;
/**
* Called as the user previews colors by either selecting a color or by hovering over a palette color
* This uses a 100 ms bounce
*/
previewChange?: () => void;
};
export type FileInput = {
type: 'file';
/**
* Allow multiple files to be selected
*/
multiple?: boolean;
/**
*
*/
fileType?: string;
}
export type CustomInput = InputDefaults<unknown> | {
type: 'custom';
};
/**
* WIP
*/
export type FontInput = {
propertyContainer: string;
type: string;
/**
* Enables font family selector
*/
fontFamily?: boolean;
/**
* Enables font size selector
*/
fontSize?: boolean;
/**
* The minimum font size
* default: 1
*/
fontSizeMin?: number;
/**
* The maximum font size
* default: 300
*/
fontSizeMax?: number;
/**
* Enables font style selector
*/
fontStyle?: boolean;
/**
* Enables font weight selector
*/
fontWeight?: boolean;
/**
* Enables font color selector
*/
fontColor?: boolean;
/**
* Enables font decorator selector
*/
fontDecoration?: boolean;
}
/**
* value: Temporal.PlainDate
*/
export type DateInput =
& {
type: 'date';
/**
* The inclusive minimum date
*/
min?: string;
/**
* The inclusive minimum date
*/
max?: string;
}
& InputDefaults<Temporal.PlainDate>
/**
* value: Temporal.PlainTime
*/
export type TimeInput =
& {
type: 'time';
/**
* The inclusive minimum time
*/
min?: string;
/**
* The inclusive maximum time
*/
max?: string;
}
& InputDefaults<Temporal.PlainTime>
/**
* value: Temporal.PlainDateTime
*/
export type DateTimeInput =
& {
type: 'datetime';
/**
* The inclusive minimum datetime
*/
min?: string;
/**
* The inclusive maximum datetime
*/
max?: string;
}
& InputDefaults<Temporal.PlainDateTime>
export type GeneralInput = {
/**
* Hides the input
*/
hidden?: boolean;
/**
* Renders in a readonly state that allows selection
*/
readonly?: boolean;
/**
* Renders in a disabled state, disabling selection
*/
disabled?: boolean;
/**
* Whether this will trigger form validation when equal to clearValue
*/
required?: boolean;
/**
* Whether to show a clear value button. The clear displays if not equal to the default
*/
clearValue?: boolean;
/**
* Hides the clear button
*/
hideClearValue?: boolean;
/**
* Requires the user to double-click to begin editing
*/
doubleClickToEdit?: boolean;
/**
* Convert input to change events
*/
inputToChange?: boolean;
/**
* Sets the input to display: contents so the input can use the parent layout
*/
displayContents?: boolean;
/**
* Default position for the picker relative to the input
*/
pickerAnchor?: 'left' | 'right' | 'bottom' | 'top';
/**
* By default the picker is anchored to the input, but it can be anchored to any element
*/
pickerAnchorElement?: Element;
/**
* Emits 'dblclick' and 'tripleclick' events blocking single click events for both previous events
*/
tripleClick?: boolean;
/**
* Tooltip
*/
tooltip?: string;
}
export type Input = GeneralInput
& (
| BooleanInput
| IntInput
| FloatInput
| StringInput
| ObjectInput
| ArrayInput
| ColorInput
| FileInput
| DateInput
| TimeInput
| DateTimeInput
);
export type Property = {
label: string;
property: string;
} & Input;
const input1: Input = { type: 'string', password: true, placeholder: 'Password' }; // Config for a basic password input
const input2: Input = { type: 'int', min: 0, max: 100 }; // 'int' just makes value accessors an integer which will automatically parse using parseInt(n, 10)
const input3: Input = { type: 'int', default: 0 };
const input4: Input = { // select with two options
type: 'number',
options: [
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 }
]
};
const input5: Input = {
type: 'string',
options: [
{ label: 'Landscape', value: 'landscape' },
{ label: 'Portrait', value: 'portrait' }
],
radio: true // Displays the options in a radio input
};
const input6: Input = { type: 'date', min: '2024-01-01' };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment