first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,3 @@
import * as React from 'react';
import type { ISpinButtonProps } from './SpinButton.types';
export declare const SpinButtonBase: React.FunctionComponent<ISpinButtonProps>;
@@ -0,0 +1,305 @@
define(["require", "exports", "tslib", "react", "../../Button", "../../Label", "../../Icon", "../../Utilities", "./SpinButton.styles", "./SpinButton.types", "../../Positioning", "@fluentui/react-hooks"], function (require, exports, tslib_1, React, Button_1, Label_1, Icon_1, Utilities_1, SpinButton_styles_1, SpinButton_types_1, Positioning_1, react_hooks_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpinButtonBase = void 0;
var getClassNames = (0, Utilities_1.classNamesFunction)();
var COMPONENT_NAME = 'SpinButton';
var DEFAULT_PROPS = {
disabled: false,
label: '',
step: 1,
labelPosition: Positioning_1.Position.start,
incrementButtonIcon: { iconName: 'ChevronUpSmall' },
decrementButtonIcon: { iconName: 'ChevronDownSmall' },
};
var INITIAL_STEP_DELAY = 400;
var STEP_DELAY = 75;
var useComponentRef = function (props, input, value) {
React.useImperativeHandle(props.componentRef, function () { return ({
get value() {
return value;
},
focus: function () {
if (input.current) {
input.current.focus();
}
},
}); }, [input, value]);
};
var noOp = function () {
/**
* A noop input change handler. Using onInput instead of onChange was meant to address an issue
* which apparently has been resolved in React 16 (https://github.com/facebook/react/issues/7027).
* The no-op onChange handler was still needed because React gives console errors if an input
* doesn't have onChange.
*
* TODO (Fabric 8?) - switch to just calling onChange (this is a breaking change for any tests,
* ours or 3rd-party, which simulate entering text in a SpinButton)
*/
};
/** Clamp the value to the provided min and/or max */
var clampValue = function (value, _a) {
var min = _a.min, max = _a.max;
if (typeof max === 'number') {
value = Math.min(value, max);
}
if (typeof min === 'number') {
value = Math.max(value, min);
}
return value;
};
exports.SpinButtonBase = React.forwardRef(function (propsWithoutDefaults, ref) {
var props = (0, Utilities_1.getPropsWithDefaults)(DEFAULT_PROPS, propsWithoutDefaults);
var disabled = props.disabled, label = props.label, min = props.min, max = props.max, step = props.step, defaultValue = props.defaultValue, valueFromProps = props.value, precisionFromProps = props.precision, labelPosition = props.labelPosition, iconProps = props.iconProps, incrementButtonIcon = props.incrementButtonIcon, incrementButtonAriaLabel = props.incrementButtonAriaLabel, decrementButtonIcon = props.decrementButtonIcon, decrementButtonAriaLabel = props.decrementButtonAriaLabel, ariaLabel = props.ariaLabel, ariaDescribedBy = props.ariaDescribedBy, customUpArrowButtonStyles = props.upArrowButtonStyles, customDownArrowButtonStyles = props.downArrowButtonStyles, theme = props.theme, ariaPositionInSet = props.ariaPositionInSet, ariaSetSize = props.ariaSetSize, ariaValueNow = props.ariaValueNow, ariaValueText = props.ariaValueText, className = props.className, inputProps = props.inputProps, onDecrement = props.onDecrement, onIncrement = props.onIncrement, iconButtonProps = props.iconButtonProps, onValidate = props.onValidate, onChange = props.onChange, styles = props.styles;
var input = React.useRef(null);
var inputId = (0, react_hooks_1.useId)('input');
var labelId = (0, react_hooks_1.useId)('Label');
var _a = React.useState(false), isFocused = _a[0], setIsFocused = _a[1];
var _b = React.useState(SpinButton_types_1.KeyboardSpinDirection.notSpinning), keyboardSpinDirection = _b[0], setKeyboardSpinDirection = _b[1];
var async = (0, react_hooks_1.useAsync)();
var precision = React.useMemo(function () {
return precisionFromProps !== null && precisionFromProps !== void 0 ? precisionFromProps : Math.max((0, Utilities_1.calculatePrecision)(step), 0);
}, [precisionFromProps, step]);
/**
* Actual current value. If `props.value` is provided (controlled), it will always be used.
* If not (uncontrolled), this tracks the current value based on user modifications.
* Note that while the user is editing text in the field, this will not be updated until "commit"
* (blur or press enter).
*/
var _c = (0, react_hooks_1.useControllableValue)(valueFromProps, defaultValue !== null && defaultValue !== void 0 ? defaultValue : String(min || 0), onChange), value = _c[0], setValue = _c[1];
/**
* "Uncommitted" internal value while the user is editing text in the field. This lets us wait to
* call `onChange` (and possibly update the real value) until the user "commits" the value by
* pressing enter or blurring the field.
*/
var _d = React.useState(), intermediateValue = _d[0], setIntermediateValue = _d[1];
var internalState = React.useRef({
stepTimeoutHandle: -1,
latestValue: undefined,
latestIntermediateValue: undefined,
}).current;
// On each render, update this saved value used by callbacks. (This should be safe even if render
// is called multiple times, because an event handler or timeout callback will only run once.)
internalState.latestValue = value;
internalState.latestIntermediateValue = intermediateValue;
var previousValueFromProps = (0, react_hooks_1.usePrevious)(valueFromProps);
React.useEffect(function () {
// If props.value changes while editing, clear the intermediate value
if (valueFromProps !== previousValueFromProps && intermediateValue !== undefined) {
setIntermediateValue(undefined);
}
}, [valueFromProps, previousValueFromProps, intermediateValue]);
var classNames = getClassNames(styles, {
theme: theme,
disabled: disabled,
isFocused: isFocused,
keyboardSpinDirection: keyboardSpinDirection,
labelPosition: labelPosition,
className: className,
});
var nativeProps = (0, Utilities_1.getNativeProps)(props, Utilities_1.divProperties, [
'onBlur',
'onFocus',
'className',
'onChange',
]);
/** Validate (commit) function called on blur or enter keypress. */
var validate = React.useCallback(function (ev) {
// Only run validation if the value changed
var enteredValue = internalState.latestIntermediateValue;
if (enteredValue !== undefined && enteredValue !== internalState.latestValue) {
var newValue = void 0;
if (onValidate) {
newValue = onValidate(enteredValue, ev);
}
else if (enteredValue && enteredValue.trim().length && !isNaN(Number(enteredValue))) {
// default validation handling
newValue = String(clampValue(Number(enteredValue), { min: min, max: max }));
}
if (newValue !== undefined && newValue !== internalState.latestValue) {
// Commit the value if it changed
setValue(newValue, ev);
}
}
// Done validating, so clear the intermediate typed value (if any)
setIntermediateValue(undefined);
}, [internalState, max, min, onValidate, setValue]);
/**
* Stop spinning (clear any currently pending update and set spinning to false)
*/
var stop = React.useCallback(function () {
if (internalState.stepTimeoutHandle >= 0) {
async.clearTimeout(internalState.stepTimeoutHandle);
internalState.stepTimeoutHandle = -1;
}
if (internalState.spinningByMouse || keyboardSpinDirection !== SpinButton_types_1.KeyboardSpinDirection.notSpinning) {
internalState.spinningByMouse = false;
setKeyboardSpinDirection(SpinButton_types_1.KeyboardSpinDirection.notSpinning);
}
}, [internalState, keyboardSpinDirection, async]);
/**
* Update the value with the given stepFunction.
* Also starts spinning for mousedown events by scheduling another update with setTimeout.
* @param stepFunction - function to use to step by
* @param event - The event that triggered the updateValue
*/
var updateValue = React.useCallback(function (stepFunction, ev) {
ev.persist();
if (internalState.latestIntermediateValue !== undefined) {
// Edge case: if intermediateValue is set, this means that the user was editing the input
// text and then started spinning (either with mouse or keyboard). We need to validate and
// call onChange before starting to spin.
if (ev.type === 'keydown' || ev.type === 'mousedown') {
// For the arrow keys, we have to manually trigger validation.
// (For the buttons, validation will happen automatically since the input's onBlur will
// be triggered after mousedown on the button completes.)
validate(ev);
}
async.requestAnimationFrame(function () {
// After handling any value updates, do the spinning update
updateValue(stepFunction, ev);
});
return;
}
// Call the step function and update the value.
// (Note: we access the latest value via internalState (not directly) to ensure we don't use
// a stale captured value. This is mainly important for spinning by mouse, where we trigger
// additional calls to the original updateValue function via setTimeout. It also lets us
// avoid useCallback deps on frequently changing values.)
var newValue = stepFunction(internalState.latestValue || '', ev);
if (newValue !== undefined && newValue !== internalState.latestValue) {
setValue(newValue, ev);
}
// Schedule the next spin if applicable
// (will be canceled if there's a mouseup before the timeout runs)
var wasSpinning = internalState.spinningByMouse;
internalState.spinningByMouse = ev.type === 'mousedown';
if (internalState.spinningByMouse) {
internalState.stepTimeoutHandle = async.setTimeout(function () {
updateValue(stepFunction, ev);
}, wasSpinning ? STEP_DELAY : INITIAL_STEP_DELAY);
}
}, [internalState, async, validate, setValue]);
/** Composed increment handler (uses `props.onIncrement` or default) */
var handleIncrement = React.useCallback(function (newValue) {
if (onIncrement) {
return onIncrement(newValue);
}
else {
var numericValue = clampValue(Number(newValue) + Number(step), { max: max });
numericValue = (0, Utilities_1.precisionRound)(numericValue, precision);
return String(numericValue);
}
}, [precision, max, onIncrement, step]);
/** Composed decrement handler (uses `props.onDecrement` or default) */
var handleDecrement = React.useCallback(function (newValue) {
if (onDecrement) {
return onDecrement(newValue);
}
else {
var numericValue = clampValue(Number(newValue) - Number(step), { min: min });
numericValue = (0, Utilities_1.precisionRound)(numericValue, precision);
return String(numericValue);
}
}, [precision, min, onDecrement, step]);
/** Handles when the user types in the input */
var handleInputChange = function (ev) {
setIntermediateValue(ev.target.value);
};
/** Composed focus handler (does internal stuff and calls `props.onFocus`) */
var handleFocus = function (ev) {
var _a;
// We can't set focus on a non-existing element
if (!input.current) {
return;
}
if (internalState.spinningByMouse || keyboardSpinDirection !== SpinButton_types_1.KeyboardSpinDirection.notSpinning) {
stop();
}
input.current.select();
setIsFocused(true);
(_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props, ev);
};
/** Composed blur handler (does internal stuff and calls `props.onBlur`) */
var handleBlur = function (ev) {
var _a;
validate(ev);
setIsFocused(false);
(_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, ev);
};
/** Update value when arrow keys are pressed, commit on enter, or revert on escape */
var handleKeyDown = function (ev) {
// eat the up and down arrow keys to keep focus in the spinButton
// (especially when a spinButton is inside of a FocusZone)
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (ev.which === Utilities_1.KeyCodes.up || ev.which === Utilities_1.KeyCodes.down || ev.which === Utilities_1.KeyCodes.enter) {
ev.preventDefault();
ev.stopPropagation();
}
if (disabled) {
stop();
return;
}
var spinDirection = SpinButton_types_1.KeyboardSpinDirection.notSpinning;
// eslint-disable-next-line @typescript-eslint/no-deprecated
switch (ev.which) {
case Utilities_1.KeyCodes.up:
spinDirection = SpinButton_types_1.KeyboardSpinDirection.up;
updateValue(handleIncrement, ev);
break;
case Utilities_1.KeyCodes.down:
spinDirection = SpinButton_types_1.KeyboardSpinDirection.down;
updateValue(handleDecrement, ev);
break;
case Utilities_1.KeyCodes.enter:
// Commit the edited value
validate(ev);
break;
case Utilities_1.KeyCodes.escape:
// Revert to previous value
setIntermediateValue(undefined);
break;
}
// style the increment/decrement button to look active
// when the corresponding up/down arrow keys trigger a step
if (keyboardSpinDirection !== spinDirection) {
setKeyboardSpinDirection(spinDirection);
}
};
/** Stop spinning on keyUp if the up or down arrow key fired this event */
var handleKeyUp = React.useCallback(function (ev) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (disabled || ev.which === Utilities_1.KeyCodes.up || ev.which === Utilities_1.KeyCodes.down) {
stop();
return;
}
}, [disabled, stop]);
var handleIncrementMouseDown = React.useCallback(function (ev) {
updateValue(handleIncrement, ev);
}, [handleIncrement, updateValue]);
var handleDecrementMouseDown = React.useCallback(function (ev) {
updateValue(handleDecrement, ev);
}, [handleDecrement, updateValue]);
useComponentRef(props, input, value);
useDebugWarnings(props);
var valueIsNumber = !!value && !isNaN(Number(value)); // Number('') is 0 which may not be desirable
var labelContent = (iconProps || label) && (React.createElement("div", { className: classNames.labelWrapper },
iconProps && React.createElement(Icon_1.Icon, tslib_1.__assign({}, iconProps, { className: classNames.icon, "aria-hidden": "true" })),
label && (React.createElement(Label_1.Label, { id: labelId, htmlFor: inputId, className: classNames.label, disabled: disabled }, label))));
return (React.createElement("div", { className: classNames.root, ref: ref },
labelPosition !== Positioning_1.Position.bottom && labelContent,
React.createElement("div", tslib_1.__assign({}, nativeProps, { className: classNames.spinButtonWrapper, "aria-label": ariaLabel && ariaLabel, "aria-posinset": ariaPositionInSet, "aria-setsize": ariaSetSize, "data-ktp-target": true }),
React.createElement("input", tslib_1.__assign({
// Display intermediateValue while editing the text (before commit)
value: intermediateValue !== null && intermediateValue !== void 0 ? intermediateValue : value, id: inputId, onChange: noOp, onInput: handleInputChange, className: classNames.input, type: "text", autoComplete: "off", role: "spinbutton", "aria-labelledby": label && labelId, "aria-valuetext": ariaValueText !== null && ariaValueText !== void 0 ? ariaValueText : (valueIsNumber ? undefined : value), "aria-valuenow": ariaValueNow, "aria-valuemin": min, "aria-valuemax": max, "aria-describedby": ariaDescribedBy, onBlur: handleBlur, ref: input, onFocus: handleFocus, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, disabled: disabled, "aria-disabled": disabled, "data-lpignore": true, "data-ktp-execute-target": true }, inputProps)),
React.createElement("span", { className: classNames.arrowButtonsContainer },
React.createElement(Button_1.IconButton, tslib_1.__assign({ styles: (0, SpinButton_styles_1.getArrowButtonStyles)(theme, true, customUpArrowButtonStyles), className: 'ms-UpButton', checked: keyboardSpinDirection === SpinButton_types_1.KeyboardSpinDirection.up, disabled: disabled, iconProps: incrementButtonIcon, onMouseDown: handleIncrementMouseDown, onMouseLeave: stop, onMouseUp: stop, tabIndex: -1, ariaLabel: incrementButtonAriaLabel, "data-is-focusable": false }, iconButtonProps)),
React.createElement(Button_1.IconButton, tslib_1.__assign({ styles: (0, SpinButton_styles_1.getArrowButtonStyles)(theme, false, customDownArrowButtonStyles), className: 'ms-DownButton', checked: keyboardSpinDirection === SpinButton_types_1.KeyboardSpinDirection.down, disabled: disabled, iconProps: decrementButtonIcon, onMouseDown: handleDecrementMouseDown, onMouseLeave: stop, onMouseUp: stop, tabIndex: -1, ariaLabel: decrementButtonAriaLabel, "data-is-focusable": false }, iconButtonProps)))),
labelPosition === Positioning_1.Position.bottom && labelContent));
});
exports.SpinButtonBase.displayName = COMPONENT_NAME;
var useDebugWarnings = function (props) {
};
});
//# sourceMappingURL=SpinButton.base.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
import * as React from 'react';
import type { ISpinButtonProps } from './SpinButton.types';
/**
* The SpinButton control and related tabs pattern are used for navigating frequently accessed,
* distinct content categories. SpinButtons allow for navigation between two or more content
* views and relies on text headers to articulate the different sections of content.
*/
export declare const SpinButton: React.FunctionComponent<ISpinButtonProps>;
@@ -0,0 +1,14 @@
define(["require", "exports", "../../Utilities", "./SpinButton.base", "./SpinButton.styles"], function (require, exports, Utilities_1, SpinButton_base_1, SpinButton_styles_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpinButton = void 0;
/**
* The SpinButton control and related tabs pattern are used for navigating frequently accessed,
* distinct content categories. SpinButtons allow for navigation between two or more content
* views and relies on text headers to articulate the different sections of content.
*/
exports.SpinButton = (0, Utilities_1.styled)(SpinButton_base_1.SpinButtonBase, SpinButton_styles_1.getStyles, undefined, {
scope: 'SpinButton',
});
});
//# sourceMappingURL=SpinButton.js.map
@@ -0,0 +1 @@
{"version":3,"file":"SpinButton.js","sourceRoot":"../src/","sources":["components/SpinButton/SpinButton.tsx"],"names":[],"mappings":";;;;IAMA;;;;OAIG;IACU,QAAA,UAAU,GAA8C,IAAA,kBAAM,EACzE,gCAAc,EACd,6BAAS,EACT,SAAS,EACT;QACE,KAAK,EAAE,YAAY;KACpB,CACF,CAAC","sourcesContent":["import * as React from 'react';\nimport { styled } from '../../Utilities';\nimport { SpinButtonBase } from './SpinButton.base';\nimport { getStyles } from './SpinButton.styles';\nimport type { ISpinButtonProps, ISpinButtonStyles } from './SpinButton.types';\n\n/**\n * The SpinButton control and related tabs pattern are used for navigating frequently accessed,\n * distinct content categories. SpinButtons allow for navigation between two or more content\n * views and relies on text headers to articulate the different sections of content.\n */\nexport const SpinButton: React.FunctionComponent<ISpinButtonProps> = styled<ISpinButtonProps, {}, ISpinButtonStyles>(\n SpinButtonBase,\n getStyles,\n undefined,\n {\n scope: 'SpinButton',\n },\n);\n"]}
@@ -0,0 +1,5 @@
import type { ITheme } from '../../Styling';
import type { IButtonStyles } from '../../Button';
import type { ISpinButtonStyles, ISpinButtonStyleProps } from './SpinButton.types';
export declare const getArrowButtonStyles: (theme: ITheme, isUpArrow: boolean, customSpecificArrowStyles?: Partial<IButtonStyles>) => IButtonStyles;
export declare const getStyles: (props: ISpinButtonStyleProps) => ISpinButtonStyles;
@@ -0,0 +1,252 @@
define(["require", "exports", "tslib", "../../Styling", "../../Utilities", "../../Positioning"], function (require, exports, tslib_1, Styling_1, Utilities_1, Positioning_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStyles = exports.getArrowButtonStyles = void 0;
var ARROW_BUTTON_WIDTH = 23;
var ARROW_BUTTON_ICON_SIZE = 8;
var DEFAULT_HEIGHT = 32;
var DEFAULT_MIN_WIDTH = 86;
var LABEL_MARGIN = 10;
var getDisabledStyles = (0, Utilities_1.memoizeFunction)(function (theme) {
var _a;
var semanticColors = theme.semanticColors;
var SpinButtonTextColorDisabled = semanticColors.disabledText;
var SpinButtonBackgroundColorDisabled = semanticColors.disabledBackground;
return {
backgroundColor: SpinButtonBackgroundColorDisabled,
pointerEvents: 'none',
cursor: 'default',
color: SpinButtonTextColorDisabled,
selectors: (_a = {
':after': {
borderColor: SpinButtonBackgroundColorDisabled,
}
},
_a[Styling_1.HighContrastSelector] = {
color: 'GrayText',
},
_a),
};
});
exports.getArrowButtonStyles = (0, Utilities_1.memoizeFunction)(function (theme, isUpArrow, customSpecificArrowStyles) {
var _a, _b, _c;
var palette = theme.palette, semanticColors = theme.semanticColors, effects = theme.effects;
// TODO: after updating the semanticColor slots all this need to be reevaluated.
var ArrowButtonTextColor = palette.neutralSecondary;
var ArrowButtonTextColorHovered = semanticColors.buttonText;
var ArrowButtonTextColorPressed = semanticColors.buttonText;
var ArrowButtonBackgroundHovered = semanticColors.buttonBackgroundHovered;
var ArrowButtonBackgroundPressed = semanticColors.buttonBackgroundPressed;
var defaultArrowButtonStyles = {
root: {
outline: 'none',
display: 'block',
height: '50%',
width: ARROW_BUTTON_WIDTH,
padding: 0,
backgroundColor: 'transparent',
textAlign: 'center',
cursor: 'default',
color: ArrowButtonTextColor,
selectors: {
'&.ms-DownButton': {
borderRadius: "0 0 ".concat(effects.roundedCorner2, " 0"),
},
'&.ms-UpButton': {
borderRadius: "0 ".concat(effects.roundedCorner2, " 0 0"),
},
},
},
rootHovered: {
backgroundColor: ArrowButtonBackgroundHovered,
color: ArrowButtonTextColorHovered,
},
rootChecked: {
backgroundColor: ArrowButtonBackgroundPressed,
color: ArrowButtonTextColorPressed,
selectors: (_a = {},
_a[Styling_1.HighContrastSelector] = {
backgroundColor: 'Highlight',
color: 'HighlightText',
},
_a),
},
rootPressed: {
backgroundColor: ArrowButtonBackgroundPressed,
color: ArrowButtonTextColorPressed,
selectors: (_b = {},
_b[Styling_1.HighContrastSelector] = {
backgroundColor: 'Highlight',
color: 'HighlightText',
},
_b),
},
rootDisabled: {
opacity: 0.5,
selectors: (_c = {},
_c[Styling_1.HighContrastSelector] = {
color: 'GrayText',
opacity: 1,
},
_c),
},
icon: {
fontSize: ARROW_BUTTON_ICON_SIZE,
marginTop: 0,
marginRight: 0,
marginBottom: 0,
marginLeft: 0,
},
};
// No specific styles needed as of now.
var defaultUpArrowButtonStyles = {};
var defaultDownArrowButtonStyles = {};
return (0, Styling_1.concatStyleSets)(defaultArrowButtonStyles, isUpArrow ? defaultUpArrowButtonStyles : defaultDownArrowButtonStyles, customSpecificArrowStyles);
});
var getStyles = function (props) {
var _a, _b, _c, _d;
var theme = props.theme, className = props.className, labelPosition = props.labelPosition, disabled = props.disabled, isFocused = props.isFocused;
var palette = theme.palette, semanticColors = theme.semanticColors, effects = theme.effects, fonts = theme.fonts;
var SpinButtonRootBorderColor = semanticColors.inputBorder;
var SpinButtonRootBackgroundColor = semanticColors.inputBackground;
var SpinButtonRootBorderColorHovered = semanticColors.inputBorderHovered;
var SpinButtonRootBorderColorFocused = semanticColors.inputFocusBorderAlt;
var SpinButtonInputTextColor = semanticColors.inputText;
var SpinButtonInputTextColorSelected = palette.white;
var SpinButtonInputBackgroundColorSelected = semanticColors.inputBackgroundChecked;
var SpinButtonIconDisabledColor = semanticColors.disabledText;
return {
root: [
fonts.medium,
{
outline: 'none',
width: '100%',
minWidth: DEFAULT_MIN_WIDTH,
},
className,
],
labelWrapper: [
{
display: 'inline-flex',
alignItems: 'center',
},
labelPosition === Positioning_1.Position.start && {
height: DEFAULT_HEIGHT,
float: 'left',
marginRight: LABEL_MARGIN,
},
labelPosition === Positioning_1.Position.end && {
height: DEFAULT_HEIGHT,
float: 'right',
marginLeft: LABEL_MARGIN,
},
labelPosition === Positioning_1.Position.top && {
// Due to the lineHeight set on the label (below), the height of the wrapper (contains icon+label)
// ends up 1px taller than a standard label height, causing the vertical alignment to be off when
// the SpinButton is displayed with the label on top next to other form fields.
// Decrease the wrapper's effective height slightly to compensate.
marginBottom: -1,
},
],
icon: [
{
padding: '0 5px',
fontSize: Styling_1.IconFontSizes.large,
},
disabled && {
color: SpinButtonIconDisabledColor,
},
],
label: {
pointerEvents: 'none',
// centering the label with the icon by forcing the exact same height as the icon.
lineHeight: Styling_1.IconFontSizes.large,
},
spinButtonWrapper: [
tslib_1.__assign(tslib_1.__assign({ display: 'flex', position: 'relative', boxSizing: 'border-box', height: DEFAULT_HEIGHT, minWidth: DEFAULT_MIN_WIDTH }, (0, Styling_1.getInputFocusStyle)(SpinButtonRootBorderColor, effects.roundedCorner2, 'border', 0)), { ':after': (_a = {
borderWidth: '1px'
},
_a[Styling_1.HighContrastSelector] = {
borderColor: 'GrayText',
},
_a) }),
(labelPosition === Positioning_1.Position.top || labelPosition === Positioning_1.Position.bottom) && {
width: '100%',
},
!disabled && [
{
':hover:after': (_b = {
borderColor: SpinButtonRootBorderColorHovered
},
_b[Styling_1.HighContrastSelector] = {
borderColor: 'Highlight',
},
_b),
},
isFocused && {
':hover:after, :after': (_c = {
borderColor: SpinButtonRootBorderColorFocused,
borderWidth: '2px'
},
_c[Styling_1.HighContrastSelector] = {
borderColor: 'Highlight',
},
_c),
},
],
disabled && getDisabledStyles(theme),
],
input: [
'ms-spinButton-input',
{
boxSizing: 'border-box',
boxShadow: 'none',
borderStyle: 'none',
flex: 1,
margin: 0,
fontSize: fonts.medium.fontSize,
fontFamily: 'inherit',
color: SpinButtonInputTextColor,
backgroundColor: SpinButtonRootBackgroundColor,
height: '100%',
padding: '0 8px 0 9px',
outline: 0,
display: 'block',
minWidth: DEFAULT_MIN_WIDTH - ARROW_BUTTON_WIDTH - 2,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
cursor: 'text',
userSelect: 'text',
borderRadius: "".concat(effects.roundedCorner2, " 0 0 ").concat(effects.roundedCorner2),
},
!disabled && {
selectors: {
'::selection': {
backgroundColor: SpinButtonInputBackgroundColorSelected,
color: SpinButtonInputTextColorSelected,
selectors: (_d = {},
_d[Styling_1.HighContrastSelector] = {
backgroundColor: 'Highlight',
borderColor: 'Highlight',
color: 'HighlightText',
},
_d),
},
},
},
disabled && getDisabledStyles(theme),
],
arrowButtonsContainer: [
{
display: 'block',
height: '100%',
cursor: 'default',
},
disabled && getDisabledStyles(theme),
],
};
};
exports.getStyles = getStyles;
});
//# sourceMappingURL=SpinButton.styles.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,256 @@
import * as React from 'react';
import { Position } from '../../Positioning';
import type { IButtonStyles, IButtonProps } from '../../Button';
import type { IIconProps } from '../../Icon';
import type { ITheme, IStyle, IShadowDomStyle } from '../../Styling';
import type { IKeytipProps } from '../../Keytip';
import type { IRefObject, IStyleFunctionOrObject } from '../../Utilities';
/**
* {@docCategory SpinButton}
*/
export interface ISpinButton {
/**
* Current committed/validated value of the control. Note that this does *not* update on every
* keystroke while the user is editing text in the input field.
* "committed" the edit yet by focusing away (blurring) or pressing enter,
*/
value?: string;
/**
* Sets focus to the control.
*/
focus: () => void;
}
/**
* {@docCategory SpinButton}
*/
export declare enum KeyboardSpinDirection {
down = -1,
notSpinning = 0,
up = 1
}
/**
* {@docCategory SpinButton}
*/
export interface ISpinButtonProps extends React.HTMLAttributes<HTMLDivElement>, React.RefAttributes<HTMLDivElement> {
/**
* Gets the component ref.
*/
componentRef?: IRefObject<ISpinButton>;
/**
* Initial value of the control (assumed to be valid). Updates to this prop will not be respected.
*
* Use this if you intend for the SpinButton to be an uncontrolled component which maintains its
* own value. For a controlled component, use `value` instead. (Mutually exclusive with `value`.)
* @defaultvalue 0
*/
defaultValue?: string;
/**
* Current value of the control (assumed to be valid).
*
* Only provide this if the SpinButton is a controlled component where you are maintaining its
* current state and passing updates based on change events; otherwise, use the `defaultValue`
* property. (Mutually exclusive with `defaultValue`.)
*/
value?: string;
/**
* Min value of the control. If not provided, the control has no minimum value.
*/
min?: number;
/**
* Max value of the control. If not provided, the control has no maximum value.
*/
max?: number;
/**
* Difference between two adjacent values of the control.
* This value is used to calculate the precision of the input if no `precision` is given.
* The precision calculated this way will always be \>= 0.
* @defaultvalue 1
*/
step?: number;
/**
* A description of the control for the benefit of screen reader users.
*/
ariaLabel?: string;
/**
* ID of a label which describes the control, if not using the default label.
*/
ariaDescribedBy?: string;
/**
* A more descriptive title for the control, visible on its tooltip.
*/
title?: string;
/**
* Whether or not the control is disabled.
*/
disabled?: boolean;
/**
* Custom className for the control.
*/
className?: string;
/**
* Descriptive label for the control.
*/
label?: string;
/**
* Where to position the control's label.
* @defaultvalue Left
*/
labelPosition?: Position;
/**
* Props for an icon to display alongside the control's label.
*/
iconProps?: IIconProps;
/**
* Callback for when the committed/validated value changes. This is called *after* `onIncrement`,
* `onDecrement`, or `onValidate`, on the following events:
* - User presses the up/down buttons (on single press or every spin)
* - User presses the up/down arrow keys (on single press or every spin)
* - User *commits* edits to the input text by focusing away (blurring) or pressing enter.
* Note that this is NOT called for every key press while the user is editing.
*/
onChange?: (event: React.SyntheticEvent<HTMLElement>, newValue?: string) => void;
/**
* Callback for when the entered value should be validated.
* @param value - The entered value to validate
* @param event - The event that triggered this validate, if any (for accessibility)
* @returns If a string is returned, it will be used as the new value
*/
onValidate?: (value: string, event?: React.SyntheticEvent<HTMLElement>) => string | void;
/**
* Callback for when the increment button or up arrow key is pressed.
* @param value - The current value to be incremented
* @param event - The event that triggered this increment
* @returns If a string is returned, it will be used as the new value
*/
onIncrement?: (value: string, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => string | void;
/**
* Callback for when the decrement button or down arrow key is pressed.
* @param value - The current value to be decremented
* @param event - The event that triggered this decrement
* @returns If a string is returned, it will be used as the new value
*/
onDecrement?: (value: string, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => string | void;
/**
* Callback for when the user focuses the control.
*/
onFocus?: React.FocusEventHandler<HTMLInputElement>;
/**
* Callback for when the control loses focus.
*/
onBlur?: React.FocusEventHandler<HTMLInputElement>;
/**
* Custom props for the increment button.
*/
incrementButtonIcon?: IIconProps;
/**
* Custom props for the decrement button.
*/
decrementButtonIcon?: IIconProps;
/**
* Custom styling for individual elements within the control.
*/
styles?: IStyleFunctionOrObject<ISpinButtonStyleProps, ISpinButtonStyles>;
/**
* Custom styles for the up arrow button.
*
* Note: The buttons are in a checked state when arrow keys are used to increment/decrement
* the SpinButton. Use `rootChecked` instead of `rootPressed` for styling when that is the case.
*/
upArrowButtonStyles?: Partial<IButtonStyles>;
/**
* Custom styles for the down arrow button.
*
* Note: The buttons are in a checked state when arrow keys are used to increment/decrement
* the SpinButton. Use `rootChecked` instead of `rootPressed` for styling when that is the case.
*/
downArrowButtonStyles?: Partial<IButtonStyles>;
/**
* Theme provided by HOC.
*/
theme?: ITheme;
/**
* Accessible label text for the increment button (for screen reader users).
*/
incrementButtonAriaLabel?: string;
/**
* Accessible label text for the decrement button (for screen reader users).
*/
decrementButtonAriaLabel?: string;
/**
* How many decimal places the value should be rounded to.
*
* The default is calculated based on the precision of `step`: i.e. if step = 1, precision = 0.
* step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2.
*/
precision?: number;
/**
* The position in the parent set (if in a set).
*/
ariaPositionInSet?: number;
/**
* The total size of the parent set (if in a set).
*/
ariaSetSize?: number;
/**
* Sets the control's aria-valuenow. This is the numeric form of `value`.
* Providing this only makes sense when using as a controlled component.
*/
ariaValueNow?: number;
ariaValueText?: string;
/**
* Keytip for the control.
*/
keytipProps?: IKeytipProps;
/**
* Additional props for the input field.
*/
inputProps?: React.InputHTMLAttributes<HTMLElement | HTMLInputElement>;
/**
* Additional props for the up and down arrow buttons.
*/
iconButtonProps?: IButtonProps;
}
/**
* {@docCategory SpinButton}
*/
export interface ISpinButtonStyles extends IShadowDomStyle {
/**
* Styles for the root of the component.
*/
root: IStyle;
/**
* Style for the label wrapper element, which contains the icon and label.
*/
labelWrapper: IStyle;
/**
* Style for the icon.
*/
icon: IStyle;
/**
* Style for the label text.
*/
label: IStyle;
/**
* Style for the wrapper element of the input field and arrow buttons.
*/
spinButtonWrapper: IStyle;
/**
* Styles for the input.
*/
input: IStyle;
/**
* Styles for the arrowButtonsContainer
*/
arrowButtonsContainer: IStyle;
}
/**
* {@docCategory SpinButton}
*/
export interface ISpinButtonStyleProps {
theme: ITheme;
className: string | undefined;
disabled: boolean;
isFocused: boolean;
keyboardSpinDirection: KeyboardSpinDirection;
labelPosition: Position;
}
@@ -0,0 +1,15 @@
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeyboardSpinDirection = void 0;
/**
* {@docCategory SpinButton}
*/
var KeyboardSpinDirection;
(function (KeyboardSpinDirection) {
KeyboardSpinDirection[KeyboardSpinDirection["down"] = -1] = "down";
KeyboardSpinDirection[KeyboardSpinDirection["notSpinning"] = 0] = "notSpinning";
KeyboardSpinDirection[KeyboardSpinDirection["up"] = 1] = "up";
})(KeyboardSpinDirection || (exports.KeyboardSpinDirection = KeyboardSpinDirection = {}));
});
//# sourceMappingURL=SpinButton.types.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
export * from './SpinButton';
export * from './SpinButton.types';
+7
View File
@@ -0,0 +1,7 @@
define(["require", "exports", "tslib", "./SpinButton", "./SpinButton.types"], function (require, exports, tslib_1, SpinButton_1, SpinButton_types_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
tslib_1.__exportStar(SpinButton_1, exports);
tslib_1.__exportStar(SpinButton_types_1, exports);
});
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/SpinButton/index.ts"],"names":[],"mappings":";;;IAAA,4CAA6B;IAC7B,kDAAmC","sourcesContent":["export * from './SpinButton';\nexport * from './SpinButton.types';\n"]}