@jupyterlab/ui-components
Version:
JupyterLab - UI components written in React
283 lines • 15.5 kB
JavaScript
/*
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
import { nullTranslator } from '@jupyterlab/translation';
import { JSONExt } from '@lumino/coreutils';
import Form from '@rjsf/core';
import { ADDITIONAL_PROPERTY_FLAG, canExpand, getTemplate } from '@rjsf/utils';
import React from 'react';
import { addIcon, caretDownIcon, caretUpIcon, closeIcon } from '../icon';
/**
* Default `ui:options` for the UiSchema.
*/
export const DEFAULT_UI_OPTIONS = {
/**
* This prevents the submit button from being rendered, by default, as it is
* almost never what is wanted.
*
* Provide any `uiSchema#/ui:options/submitButtonOptions` to override this.
*/
submitButtonOptions: {
norender: true
}
};
/**
* Button to move an item.
*
* @returns - the button as a react element.
*/
export const MoveButton = (props) => {
var _a;
const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
let buttonContent;
/**
* Whether the button is disabled or not.
*/
const disabled = () => {
if (props.direction === 'up') {
return !props.item.hasMoveUp;
}
else {
return !props.item.hasMoveDown;
}
};
if (props.buttonStyle === 'icons') {
const iconProps = {
tag: 'span',
elementSize: 'xlarge',
elementPosition: 'center'
};
buttonContent =
props.direction === 'up' ? (React.createElement(caretUpIcon.react, { ...iconProps })) : (React.createElement(caretDownIcon.react, { ...iconProps }));
}
else {
buttonContent =
props.direction === 'up' ? trans.__('Move up') : trans.__('Move down');
}
const moveTo = props.direction === 'up' ? props.item.index - 1 : props.item.index + 1;
return (React.createElement("button", { className: "jp-mod-styled jp-mod-reject jp-ArrayOperationsButton", onClick: props.item.onReorderClick(props.item.index, moveTo), disabled: disabled() }, buttonContent));
};
/**
* Button to drop an item.
*
* @returns - the button as a react element.
*/
export const DropButton = (props) => {
var _a;
const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
let buttonContent;
if (props.buttonStyle === 'icons') {
buttonContent = (React.createElement(closeIcon.react, { tag: "span", elementSize: "xlarge", elementPosition: "center" }));
}
else {
buttonContent = trans.__('Remove');
}
return (React.createElement("button", { className: "jp-mod-styled jp-mod-warn jp-ArrayOperationsButton", onClick: props.item.onDropIndexClick(props.item.index) }, buttonContent));
};
/**
* Button to add an item.
*
* @returns - the button as a react element.
*/
export const AddButton = (props) => {
var _a;
const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
let buttonContent;
if (props.buttonStyle === 'icons') {
buttonContent = (React.createElement(addIcon.react, { tag: "span", elementSize: "xlarge", elementPosition: "center" }));
}
else {
buttonContent = trans.__('Add');
}
return (React.createElement("button", { className: "jp-mod-styled jp-mod-reject jp-ArrayOperationsButton", onClick: props.onAddClick }, buttonContent));
};
function customizeForLab(options) {
const { component, name, buttonStyle, compact, showModifiedFromDefault, translator } = options;
const isCompact = compact !== null && compact !== void 0 ? compact : false;
const button = buttonStyle !== null && buttonStyle !== void 0 ? buttonStyle : (isCompact ? 'icons' : 'text');
const factory = (props) => component({
...props,
buttonStyle: button,
compact: isCompact,
showModifiedFromDefault: showModifiedFromDefault !== null && showModifiedFromDefault !== void 0 ? showModifiedFromDefault : true,
translator: translator !== null && translator !== void 0 ? translator : nullTranslator
});
if (name) {
factory.displayName = name;
}
return factory;
}
/**
* Fetch field templates from RJSF.
*/
function getTemplates(registry, uiSchema) {
const TitleField = getTemplate('TitleFieldTemplate', registry, uiSchema);
const DescriptionField = getTemplate('DescriptionFieldTemplate', registry, uiSchema);
return { TitleField, DescriptionField };
}
/**
* Template to allow for custom buttons to re-order/remove entries in an array.
* Necessary to create accessible buttons.
*/
const CustomArrayTemplateFactory = (options) => customizeForLab({
...options,
name: 'JupyterLabArrayTemplate',
component: props => {
var _a;
const { schema, registry, uiSchema, required } = props;
const commonProps = { schema, registry, uiSchema, required };
const { TitleField, DescriptionField } = getTemplates(registry, uiSchema);
return (React.createElement("div", { className: props.className },
props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem", id: `${props.idSchema.$id}__title` }, props.title || ''),
React.createElement("div", { className: "jp-FormGroup-description", id: `${props.idSchema.$id}-description` }, props.schema.description || ''))) : (React.createElement(React.Fragment, null,
props.title && (React.createElement(TitleField, { ...commonProps, title: props.title, id: `${props.idSchema.$id}-title` })),
React.createElement(DescriptionField, { ...commonProps, id: `${props.idSchema.$id}-description`, description: (_a = props.schema.description) !== null && _a !== void 0 ? _a : '' }))),
props.items.map(item => {
return (React.createElement("div", { key: item.key, className: item.className },
item.children,
React.createElement("div", { className: "jp-ArrayOperations" },
React.createElement(MoveButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item, direction: "up" }),
React.createElement(MoveButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item, direction: "down" }),
React.createElement(DropButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item }))));
}),
props.canAdd && (React.createElement(AddButton, { onAddClick: props.onAddClick, buttonStyle: props.buttonStyle, translator: props.translator }))));
}
});
/**
* Template with custom add button, necessary for accessibility and internationalization.
*/
const CustomObjectTemplateFactory = (options) => customizeForLab({
...options,
name: 'JupyterLabObjectTemplate',
component: props => {
var _a;
const { schema, registry, uiSchema, required } = props;
const commonProps = { schema, registry, uiSchema, required };
const { TitleField, DescriptionField } = getTemplates(registry, uiSchema);
return (React.createElement("fieldset", { id: props.idSchema.$id },
props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem", id: `${props.idSchema.$id}__title` }, props.title || ''),
React.createElement("div", { className: "jp-FormGroup-description", id: `${props.idSchema.$id}__description` }, props.schema.description || ''))) : (React.createElement(React.Fragment, null,
(props.title ||
(props.uiSchema || JSONExt.emptyObject)['ui:title']) && (React.createElement(TitleField, { ...commonProps, id: `${props.idSchema.$id}__title`, title: props.title ||
`${(props.uiSchema || JSONExt.emptyObject)['ui:title']}` ||
'' })),
React.createElement(DescriptionField, { ...commonProps, id: `${props.idSchema.$id}__description`, description: (_a = props.schema.description) !== null && _a !== void 0 ? _a : '' }))),
props.properties.map(property => property.content),
canExpand(props.schema, props.uiSchema, props.formData) && (React.createElement(AddButton, { onAddClick: props.onAddClick(props.schema), buttonStyle: props.buttonStyle, translator: props.translator }))));
}
});
/**
* Renders the modified indicator and errors
*/
const CustomTemplateFactory = (options) => customizeForLab({
...options,
name: 'JupyterLabFieldTemplate',
component: props => {
var _a;
const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
let isModified = false;
let defaultValue;
const { formData, schema, label, displayLabel, id, formContext, errors, rawErrors, children, onKeyChange, onDropPropertyClick } = props;
const { defaultFormData } = formContext;
const schemaIds = id.split('_');
schemaIds.shift();
const schemaId = schemaIds.join('.');
const isRoot = schemaId === '';
const hasCustomField = schemaId === (props.uiSchema || JSONExt.emptyObject)['ui:field'];
if (props.showModifiedFromDefault) {
/**
* Determine if the field has been modified.
* Schema Id is formatted as 'root_<field name>.<nested field name>'
* This logic parses out the field name to find the default value
* before determining if the field has been modified.
*/
defaultValue = schemaIds.reduce((acc, key) => acc === null || acc === void 0 ? void 0 : acc[key], defaultFormData);
isModified =
!isRoot &&
formData !== undefined &&
defaultValue !== undefined &&
!schema.properties &&
schema.type !== 'array' &&
!JSONExt.deepEqual(formData, defaultValue);
}
const needsDescription = !isRoot &&
schema.type != 'object' &&
id !=
'jp-SettingsEditor-@jupyterlab/shortcuts-extension:shortcuts_shortcuts';
// While we can implement "remove" button for array items in array template,
// object templates do not provide a way to do this instead we need to add
// buttons here (and first check if the field can be removed = is additional).
const isAdditional = schema.hasOwnProperty(ADDITIONAL_PROPERTY_FLAG);
const isItem = !(schema.type === 'object' || schema.type === 'array');
return (React.createElement("div", { className: `form-group ${displayLabel || schema.type === 'boolean' ? 'small-field' : ''}` },
!hasCustomField &&
((rawErrors === null || rawErrors === void 0 ? void 0 : rawErrors.length) ? (
// Shows a red indicator for fields that have validation errors
React.createElement("div", { className: "jp-modifiedIndicator jp-errorIndicator" })) : (
// Only show the modified indicator if there are no errors
isModified && React.createElement("div", { className: "jp-modifiedIndicator" }))),
React.createElement("div", { className: `jp-FormGroup-content ${props.compact
? 'jp-FormGroup-contentCompact'
: 'jp-FormGroup-contentNormal'}` },
isItem && displayLabel && !isRoot && label && !isAdditional ? (props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, label),
isItem && schema.description && needsDescription && (React.createElement("div", { className: "jp-FormGroup-description" }, schema.description)))) : (React.createElement("h3", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, label))) : (React.createElement(React.Fragment, null)),
isAdditional && (React.createElement("input", { className: "jp-FormGroup-contentItem jp-mod-styled", type: "text", onBlur: event => onKeyChange(event.target.value), defaultValue: label })),
React.createElement("div", { className: `${isRoot
? 'jp-root'
: schema.type === 'object'
? 'jp-objectFieldWrapper'
: schema.type === 'array'
? 'jp-arrayFieldWrapper'
: 'jp-inputFieldWrapper jp-FormGroup-contentItem'}` }, children),
isAdditional && (React.createElement("button", { className: "jp-FormGroup-contentItem jp-mod-styled jp-mod-warn jp-FormGroup-removeButton", onClick: onDropPropertyClick(label) }, trans.__('Remove'))),
!props.compact && schema.description && needsDescription && (React.createElement("div", { className: "jp-FormGroup-description" }, schema.description)),
isModified &&
defaultValue !== undefined &&
schema.type !== 'object' && (React.createElement("div", { className: "jp-FormGroup-default" }, trans.__('Default: %1', defaultValue !== null
? defaultValue.toLocaleString()
: 'null'))),
React.createElement("div", { className: "validationErrors" }, errors))));
}
});
/**
* Generic rjsf form component for JupyterLab UI.
*/
export function FormComponent(props) {
const { buttonStyle, compact, showModifiedFromDefault, translator, formContext, ...others } = props;
const uiSchema = { ...(others.uiSchema || JSONExt.emptyObject) };
uiSchema['ui:options'] = { ...DEFAULT_UI_OPTIONS, ...uiSchema['ui:options'] };
others.uiSchema = uiSchema;
const { FieldTemplate, ArrayFieldTemplate, ObjectFieldTemplate } = props.templates || JSONExt.emptyObject;
const customization = {
buttonStyle,
compact,
showModifiedFromDefault,
translator
};
const fieldTemplate = React.useMemo(() => FieldTemplate !== null && FieldTemplate !== void 0 ? FieldTemplate : CustomTemplateFactory(customization), [FieldTemplate, buttonStyle, compact, showModifiedFromDefault, translator]);
const arrayTemplate = React.useMemo(() => ArrayFieldTemplate !== null && ArrayFieldTemplate !== void 0 ? ArrayFieldTemplate : CustomArrayTemplateFactory(customization), [
ArrayFieldTemplate,
buttonStyle,
compact,
showModifiedFromDefault,
translator
]);
const objectTemplate = React.useMemo(() => ObjectFieldTemplate !== null && ObjectFieldTemplate !== void 0 ? ObjectFieldTemplate : CustomObjectTemplateFactory(customization), [
ObjectFieldTemplate,
buttonStyle,
compact,
showModifiedFromDefault,
translator
]);
const templates = {
FieldTemplate: fieldTemplate,
ArrayFieldTemplate: arrayTemplate,
ObjectFieldTemplate: objectTemplate
};
return (React.createElement(Form, { templates: templates, formContext: formContext, ...others }));
}
//# sourceMappingURL=form.js.map