@vention/machine-ui 6.7.2 → 6.8.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.
- package/README.md +4 -1
- package/index.esm.js +237 -24
- package/package.json +1 -1
- package/src/lib/components/vention-tabs/vention-tabs.d.ts +4 -1
package/README.md
CHANGED
|
@@ -111,7 +111,10 @@ Components built on an MUI component (noted as "built on MUI `X`") accept that c
|
|
|
111
111
|
|
|
112
112
|
### Navigation and layout
|
|
113
113
|
|
|
114
|
-
- `VentionTabs`: a row of tabs. `value` is the index of the selected tab, and `onChange(event, index)` receives the new index.
|
|
114
|
+
- `VentionTabs`: a row of tabs. `value` is the index of the selected tab, and `onChange(event, index)` receives the new index. The tabs are controlled, so the app updates `tabOptions` and `value` itself.
|
|
115
|
+
- With `onRename(index, name)`, clicking the selected tab opens an input over its label. Enter or leaving the input saves, Esc cancels. The name is trimmed, and `onRename` isn't called when it's empty or unchanged.
|
|
116
|
+
- With `onRemove(index)`, each tab gets a remove button. It shows while the pointer is over the tab and while the tab has keyboard focus. Touch screens have no hover, so there it shows on the selected tab. It isn't rendered when only one tab is left. Removing doesn't change `value`, so pick the new selected tab in `onRemove`. Clicking it moves focus to that tab's label, or to the previous tab's label for the last tab, so once the parent drops the tab, focus is on the tab that took its place.
|
|
117
|
+
- With `onAdd()`, an add button follows the tabs. Append the new tab to `tabOptions` in `onAdd`.
|
|
115
118
|
- `VentionSteps`: a vertical list of steps for a guided procedure. Use `size="xx-large"` on touch HMIs; `"small"` always shows dots.
|
|
116
119
|
- `VentionSidebarItem`: a profile entry in a sidebar.
|
|
117
120
|
- `VentionTreeItem`: one row of a tree, with nested `VentionTreeItem` children.
|
package/index.esm.js
CHANGED
|
@@ -10976,57 +10976,270 @@ const useStyles$m = tss.withNestedSelectors().create(({
|
|
|
10976
10976
|
};
|
|
10977
10977
|
});
|
|
10978
10978
|
|
|
10979
|
+
const ICON_BUTTON_WIDTH = 32;
|
|
10980
|
+
const HOVER_MEDIA_QUERY = "@media (hover: hover) and (pointer: fine)";
|
|
10981
|
+
const TOUCH_MEDIA_QUERY = "@media not all and (hover: hover) and (pointer: fine)";
|
|
10979
10982
|
const VentionTabs = ({
|
|
10980
10983
|
tabOptions,
|
|
10981
10984
|
onChange,
|
|
10985
|
+
onRename,
|
|
10986
|
+
onRemove,
|
|
10987
|
+
onAdd,
|
|
10982
10988
|
value
|
|
10983
10989
|
}) => {
|
|
10990
|
+
const theme = useTheme();
|
|
10984
10991
|
const {
|
|
10985
10992
|
cx,
|
|
10986
10993
|
classes
|
|
10987
10994
|
} = useStyles$l();
|
|
10988
|
-
|
|
10995
|
+
const canRemove = onRemove !== undefined && tabOptions.length > 1;
|
|
10996
|
+
const [editingIndex, setEditingIndex] = useState(null);
|
|
10997
|
+
const [draft, setDraft] = useState("");
|
|
10998
|
+
const isCancellingRef = useRef(false);
|
|
10999
|
+
const refocusIndexRef = useRef(null);
|
|
11000
|
+
const labelButtonsRef = useRef([]);
|
|
11001
|
+
// The edit is tied to a tab position, so it closes when the parent changes the selection or the tab count.
|
|
11002
|
+
useEffect(() => {
|
|
11003
|
+
setEditingIndex(null);
|
|
11004
|
+
refocusIndexRef.current = null;
|
|
11005
|
+
}, [value, tabOptions.length]);
|
|
11006
|
+
const focusInputOnMount = useCallback(input => {
|
|
11007
|
+
if (input) {
|
|
11008
|
+
input.focus();
|
|
11009
|
+
input.select();
|
|
11010
|
+
}
|
|
11011
|
+
}, []);
|
|
11012
|
+
const handleTabClick = (event, index) => {
|
|
11013
|
+
if (onRename && index === value) {
|
|
11014
|
+
setDraft(tabOptions[index]);
|
|
11015
|
+
setEditingIndex(index);
|
|
11016
|
+
return;
|
|
11017
|
+
}
|
|
11018
|
+
onChange(event, index);
|
|
11019
|
+
};
|
|
11020
|
+
// Enter and Esc both blur the input, so blur is the only place the edit closes.
|
|
11021
|
+
const handleInputKeyDown = (event, index) => {
|
|
11022
|
+
// Safari ends the composition before the Enter that confirms it, and only flags that Enter with keyCode 229.
|
|
11023
|
+
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
|
11024
|
+
return;
|
|
11025
|
+
}
|
|
11026
|
+
if (event.key === "Enter" || event.key === "Escape") {
|
|
11027
|
+
event.preventDefault();
|
|
11028
|
+
event.stopPropagation();
|
|
11029
|
+
isCancellingRef.current = event.key === "Escape";
|
|
11030
|
+
refocusIndexRef.current = index;
|
|
11031
|
+
event.currentTarget.blur();
|
|
11032
|
+
}
|
|
11033
|
+
};
|
|
11034
|
+
// The label is hidden while the input is open, so it can only take focus back once it renders visible again.
|
|
11035
|
+
const attachLabelButton = (labelButton, index) => {
|
|
11036
|
+
labelButtonsRef.current[index] = labelButton;
|
|
11037
|
+
if (labelButton && refocusIndexRef.current === index) {
|
|
11038
|
+
refocusIndexRef.current = null;
|
|
11039
|
+
labelButton.focus();
|
|
11040
|
+
}
|
|
11041
|
+
};
|
|
11042
|
+
const handleInputBlur = index => {
|
|
11043
|
+
const name = draft.trim();
|
|
11044
|
+
if (!isCancellingRef.current && name.length > 0 && name !== tabOptions[index]) {
|
|
11045
|
+
onRename === null || onRename === void 0 ? void 0 : onRename(index, name);
|
|
11046
|
+
}
|
|
11047
|
+
isCancellingRef.current = false;
|
|
11048
|
+
setEditingIndex(null);
|
|
11049
|
+
};
|
|
11050
|
+
// Tabs are keyed by index, so once the parent drops this tab, the label at this index shows the tab that took its place.
|
|
11051
|
+
const handleRemoveClick = index => {
|
|
11052
|
+
var _a;
|
|
11053
|
+
const isLastTab = index === tabOptions.length - 1;
|
|
11054
|
+
(_a = labelButtonsRef.current[isLastTab ? index - 1 : index]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
11055
|
+
onRemove === null || onRemove === void 0 ? void 0 : onRemove(index);
|
|
11056
|
+
};
|
|
11057
|
+
return jsxs(Box$1, {
|
|
10989
11058
|
className: classes.root,
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
children:
|
|
11059
|
+
role: "tablist",
|
|
11060
|
+
children: [tabOptions.map((tabLabel, index) => {
|
|
11061
|
+
const isSelected = index === value;
|
|
11062
|
+
const isEditing = index === editingIndex;
|
|
11063
|
+
return jsxs("div", {
|
|
11064
|
+
className: cx(classes.tab, isSelected && classes.selectedTab),
|
|
11065
|
+
children: [jsx("button", {
|
|
11066
|
+
ref: labelButton => attachLabelButton(labelButton, index),
|
|
11067
|
+
className: cx(classes.label, isEditing && classes.hiddenLabel),
|
|
11068
|
+
type: "button",
|
|
11069
|
+
role: "tab",
|
|
11070
|
+
"aria-selected": isSelected,
|
|
11071
|
+
onClick: event => handleTabClick(event, index),
|
|
11072
|
+
children: jsx(Typography, {
|
|
11073
|
+
variant: "heading18SemiBold",
|
|
11074
|
+
noWrap: true,
|
|
11075
|
+
children: tabLabel
|
|
11076
|
+
})
|
|
11077
|
+
}), isEditing && jsx("input", {
|
|
11078
|
+
ref: focusInputOnMount,
|
|
11079
|
+
className: classes.input,
|
|
11080
|
+
type: "text",
|
|
11081
|
+
"aria-label": "Tab name",
|
|
11082
|
+
value: draft,
|
|
11083
|
+
onChange: event => setDraft(event.target.value),
|
|
11084
|
+
onKeyDown: event => handleInputKeyDown(event, index),
|
|
11085
|
+
onBlur: () => handleInputBlur(index)
|
|
11086
|
+
}), canRemove && !isEditing && jsx("span", {
|
|
11087
|
+
className: classes.removeControls,
|
|
11088
|
+
children: jsx("button", {
|
|
11089
|
+
className: classes.removeButton,
|
|
11090
|
+
type: "button",
|
|
11091
|
+
"aria-label": `Remove tab ${tabLabel}`,
|
|
11092
|
+
onClick: () => handleRemoveClick(index),
|
|
11093
|
+
children: jsx(VentionIcon, {
|
|
11094
|
+
type: "close",
|
|
11095
|
+
size: 16,
|
|
11096
|
+
color: theme.palette.icon.secondary
|
|
11097
|
+
})
|
|
11098
|
+
})
|
|
11099
|
+
})]
|
|
11100
|
+
}, index);
|
|
11101
|
+
}), onAdd && jsx("button", {
|
|
11102
|
+
className: classes.addButton,
|
|
11103
|
+
type: "button",
|
|
11104
|
+
"aria-label": "Add tab",
|
|
11105
|
+
onClick: () => onAdd(),
|
|
11106
|
+
children: jsx(VentionIcon, {
|
|
11107
|
+
type: "plus",
|
|
11108
|
+
size: 16,
|
|
11109
|
+
color: theme.palette.icon.secondary
|
|
10997
11110
|
})
|
|
10998
|
-
}
|
|
11111
|
+
})]
|
|
10999
11112
|
});
|
|
11000
11113
|
};
|
|
11001
|
-
const useStyles$l = tss.create(({
|
|
11002
|
-
theme
|
|
11114
|
+
const useStyles$l = tss.withNestedSelectors().create(({
|
|
11115
|
+
theme,
|
|
11116
|
+
classes
|
|
11003
11117
|
}) => {
|
|
11118
|
+
const iconButton = {
|
|
11119
|
+
flexShrink: 0,
|
|
11120
|
+
display: "inline-flex",
|
|
11121
|
+
alignItems: "center",
|
|
11122
|
+
justifyContent: "center",
|
|
11123
|
+
width: `${ICON_BUTTON_WIDTH}px`,
|
|
11124
|
+
padding: 0,
|
|
11125
|
+
border: "none",
|
|
11126
|
+
backgroundColor: "transparent",
|
|
11127
|
+
cursor: "pointer",
|
|
11128
|
+
transition: "background-color 120ms",
|
|
11129
|
+
"&:focus-visible": {
|
|
11130
|
+
outline: `2px solid ${theme.palette.border.blue}`,
|
|
11131
|
+
outlineOffset: "-2px"
|
|
11132
|
+
}
|
|
11133
|
+
};
|
|
11134
|
+
const shownRemoveControls = {
|
|
11135
|
+
opacity: 1,
|
|
11136
|
+
pointerEvents: "auto"
|
|
11137
|
+
};
|
|
11004
11138
|
return {
|
|
11005
11139
|
root: {
|
|
11140
|
+
display: "inline-flex",
|
|
11141
|
+
flexWrap: "wrap",
|
|
11142
|
+
alignItems: "center",
|
|
11143
|
+
gap: "1px",
|
|
11144
|
+
padding: "1px",
|
|
11006
11145
|
borderRadius: theme.spacing(1),
|
|
11007
11146
|
borderWidth: "2px",
|
|
11008
11147
|
borderColor: theme.palette.misc.stepper.border,
|
|
11009
11148
|
borderStyle: "solid",
|
|
11010
11149
|
backgroundColor: theme.palette.background.surface2
|
|
11011
11150
|
},
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11151
|
+
tab: {
|
|
11152
|
+
position: "relative",
|
|
11153
|
+
display: "inline-flex",
|
|
11154
|
+
alignItems: "center",
|
|
11155
|
+
height: "36px",
|
|
11156
|
+
minWidth: "150px",
|
|
11015
11157
|
margin: "1px",
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
"
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11158
|
+
paddingLeft: theme.spacing(1),
|
|
11159
|
+
borderRadius: "2px",
|
|
11160
|
+
transition: "background-color 120ms",
|
|
11161
|
+
[`&:has(:focus-visible) .${classes.removeControls}`]: shownRemoveControls,
|
|
11162
|
+
[HOVER_MEDIA_QUERY]: {
|
|
11163
|
+
"&:hover": {
|
|
11164
|
+
backgroundColor: alpha(theme.palette.common.white, 0.6)
|
|
11165
|
+
},
|
|
11166
|
+
[`&:hover .${classes.removeControls}`]: shownRemoveControls
|
|
11167
|
+
}
|
|
11168
|
+
},
|
|
11169
|
+
selectedTab: {
|
|
11170
|
+
backgroundColor: theme.palette.background.default,
|
|
11171
|
+
boxShadow: theme.shadows[1],
|
|
11172
|
+
[HOVER_MEDIA_QUERY]: {
|
|
11022
11173
|
"&:hover": {
|
|
11023
|
-
backgroundColor:
|
|
11174
|
+
backgroundColor: theme.palette.background.default
|
|
11024
11175
|
}
|
|
11176
|
+
},
|
|
11177
|
+
[TOUCH_MEDIA_QUERY]: {
|
|
11178
|
+
[`& .${classes.removeControls}`]: shownRemoveControls
|
|
11025
11179
|
}
|
|
11026
11180
|
},
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11181
|
+
label: {
|
|
11182
|
+
flexGrow: 1,
|
|
11183
|
+
minWidth: 0,
|
|
11184
|
+
height: "100%",
|
|
11185
|
+
padding: theme.spacing(0, 2),
|
|
11186
|
+
border: "none",
|
|
11187
|
+
background: "transparent",
|
|
11188
|
+
color: theme.palette.text.primary,
|
|
11189
|
+
cursor: "pointer",
|
|
11190
|
+
textAlign: "left",
|
|
11191
|
+
"&:focus-visible": {
|
|
11192
|
+
outline: `2px solid ${theme.palette.border.blue}`,
|
|
11193
|
+
outlineOffset: "-2px",
|
|
11194
|
+
borderRadius: "2px"
|
|
11195
|
+
}
|
|
11196
|
+
},
|
|
11197
|
+
hiddenLabel: {
|
|
11198
|
+
visibility: "hidden"
|
|
11199
|
+
},
|
|
11200
|
+
input: Object.assign(Object.assign({}, theme.typography.heading18SemiBold), {
|
|
11201
|
+
position: "absolute",
|
|
11202
|
+
top: theme.spacing(1),
|
|
11203
|
+
bottom: theme.spacing(1),
|
|
11204
|
+
left: "6px",
|
|
11205
|
+
right: theme.spacing(1),
|
|
11206
|
+
padding: "0 5px",
|
|
11207
|
+
border: `1px solid ${theme.palette.border.blue}`,
|
|
11208
|
+
borderRadius: "3px",
|
|
11209
|
+
backgroundColor: theme.palette.background.default,
|
|
11210
|
+
color: theme.palette.text.primary,
|
|
11211
|
+
outline: "none",
|
|
11212
|
+
boxShadow: `0 0 0 3px ${alpha(theme.palette.border.blue, 0.18)}`
|
|
11213
|
+
}),
|
|
11214
|
+
removeControls: {
|
|
11215
|
+
flexShrink: 0,
|
|
11216
|
+
alignSelf: "stretch",
|
|
11217
|
+
display: "inline-flex",
|
|
11218
|
+
opacity: 0,
|
|
11219
|
+
pointerEvents: "none",
|
|
11220
|
+
transition: "opacity 120ms"
|
|
11221
|
+
},
|
|
11222
|
+
removeButton: Object.assign(Object.assign({}, iconButton), {
|
|
11223
|
+
borderRadius: "0 2px 2px 0",
|
|
11224
|
+
[HOVER_MEDIA_QUERY]: {
|
|
11225
|
+
"&:hover": {
|
|
11226
|
+
backgroundColor: theme.palette.background.surface3
|
|
11227
|
+
}
|
|
11228
|
+
},
|
|
11229
|
+
"&:active": {
|
|
11230
|
+
backgroundColor: theme.palette.background.surface4
|
|
11231
|
+
}
|
|
11232
|
+
}),
|
|
11233
|
+
addButton: Object.assign(Object.assign({}, iconButton), {
|
|
11234
|
+
height: "36px",
|
|
11235
|
+
margin: "1px",
|
|
11236
|
+
borderRadius: "2px",
|
|
11237
|
+
[HOVER_MEDIA_QUERY]: {
|
|
11238
|
+
"&:hover": {
|
|
11239
|
+
backgroundColor: alpha(theme.palette.common.white, 0.6)
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
})
|
|
11030
11243
|
};
|
|
11031
11244
|
});
|
|
11032
11245
|
|
package/package.json
CHANGED
|
@@ -3,6 +3,9 @@ type VentionTabsProps = {
|
|
|
3
3
|
tabOptions: string[];
|
|
4
4
|
value: number;
|
|
5
5
|
onChange: (event: React.MouseEvent<HTMLElement>, arg: number) => void;
|
|
6
|
+
onRename?: (index: number, name: string) => void;
|
|
7
|
+
onRemove?: (index: number) => void;
|
|
8
|
+
onAdd?: () => void;
|
|
6
9
|
};
|
|
7
|
-
export declare const VentionTabs: ({ tabOptions, onChange, value }: VentionTabsProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare const VentionTabs: ({ tabOptions, onChange, onRename, onRemove, onAdd, value }: VentionTabsProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
11
|
export {};
|