UNPKG

@jupyterlab/ui-components

Version:

JupyterLab - UI components written in React

283 lines 15.5 kB
/* * 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