UNPKG

15.4 kBJavaScriptView Raw
1/*
2 * Copyright (c) Jupyter Development Team.
3 * Distributed under the terms of the Modified BSD License.
4 */
5import { nullTranslator } from '@jupyterlab/translation';
6import { JSONExt } from '@lumino/coreutils';
7import Form from '@rjsf/core';
8import { ADDITIONAL_PROPERTY_FLAG, canExpand, getTemplate } from '@rjsf/utils';
9import React from 'react';
10import { addIcon, caretDownIcon, caretUpIcon, closeIcon } from '../icon';
11/**
12 * Default `ui:options` for the UiSchema.
13 */
14export const DEFAULT_UI_OPTIONS = {
15 /**
16 * This prevents the submit button from being rendered, by default, as it is
17 * almost never what is wanted.
18 *
19 * Provide any `uiSchema#/ui:options/submitButtonOptions` to override this.
20 */
21 submitButtonOptions: {
22 norender: true
23 }
24};
25/**
26 * Button to move an item.
27 *
28 * @returns - the button as a react element.
29 */
30export const MoveButton = (props) => {
31 var _a;
32 const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
33 let buttonContent;
34 /**
35 * Whether the button is disabled or not.
36 */
37 const disabled = () => {
38 if (props.direction === 'up') {
39 return !props.item.hasMoveUp;
40 }
41 else {
42 return !props.item.hasMoveDown;
43 }
44 };
45 if (props.buttonStyle === 'icons') {
46 const iconProps = {
47 tag: 'span',
48 elementSize: 'xlarge',
49 elementPosition: 'center'
50 };
51 buttonContent =
52 props.direction === 'up' ? (React.createElement(caretUpIcon.react, { ...iconProps })) : (React.createElement(caretDownIcon.react, { ...iconProps }));
53 }
54 else {
55 buttonContent =
56 props.direction === 'up' ? trans.__('Move up') : trans.__('Move down');
57 }
58 const moveTo = props.direction === 'up' ? props.item.index - 1 : props.item.index + 1;
59 return (React.createElement("button", { className: "jp-mod-styled jp-mod-reject jp-ArrayOperationsButton", onClick: props.item.onReorderClick(props.item.index, moveTo), disabled: disabled() }, buttonContent));
60};
61/**
62 * Button to drop an item.
63 *
64 * @returns - the button as a react element.
65 */
66export const DropButton = (props) => {
67 var _a;
68 const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
69 let buttonContent;
70 if (props.buttonStyle === 'icons') {
71 buttonContent = (React.createElement(closeIcon.react, { tag: "span", elementSize: "xlarge", elementPosition: "center" }));
72 }
73 else {
74 buttonContent = trans.__('Remove');
75 }
76 return (React.createElement("button", { className: "jp-mod-styled jp-mod-warn jp-ArrayOperationsButton", onClick: props.item.onDropIndexClick(props.item.index) }, buttonContent));
77};
78/**
79 * Button to add an item.
80 *
81 * @returns - the button as a react element.
82 */
83export const AddButton = (props) => {
84 var _a;
85 const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
86 let buttonContent;
87 if (props.buttonStyle === 'icons') {
88 buttonContent = (React.createElement(addIcon.react, { tag: "span", elementSize: "xlarge", elementPosition: "center" }));
89 }
90 else {
91 buttonContent = trans.__('Add');
92 }
93 return (React.createElement("button", { className: "jp-mod-styled jp-mod-reject jp-ArrayOperationsButton", onClick: props.onAddClick }, buttonContent));
94};
95function customizeForLab(options) {
96 const { component, name, buttonStyle, compact, showModifiedFromDefault, translator } = options;
97 const isCompact = compact !== null && compact !== void 0 ? compact : false;
98 const button = buttonStyle !== null && buttonStyle !== void 0 ? buttonStyle : (isCompact ? 'icons' : 'text');
99 const factory = (props) => component({
100 ...props,
101 buttonStyle: button,
102 compact: isCompact,
103 showModifiedFromDefault: showModifiedFromDefault !== null && showModifiedFromDefault !== void 0 ? showModifiedFromDefault : true,
104 translator: translator !== null && translator !== void 0 ? translator : nullTranslator
105 });
106 if (name) {
107 factory.displayName = name;
108 }
109 return factory;
110}
111/**
112 * Fetch field templates from RJSF.
113 */
114function getTemplates(registry, uiSchema) {
115 const TitleField = getTemplate('TitleFieldTemplate', registry, uiSchema);
116 const DescriptionField = getTemplate('DescriptionFieldTemplate', registry, uiSchema);
117 return { TitleField, DescriptionField };
118}
119/**
120 * Template to allow for custom buttons to re-order/remove entries in an array.
121 * Necessary to create accessible buttons.
122 */
123const CustomArrayTemplateFactory = (options) => customizeForLab({
124 ...options,
125 name: 'JupyterLabArrayTemplate',
126 component: props => {
127 var _a;
128 const { schema, registry, uiSchema, required } = props;
129 const commonProps = { schema, registry, uiSchema, required };
130 const { TitleField, DescriptionField } = getTemplates(registry, uiSchema);
131 return (React.createElement("div", { className: props.className },
132 props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
133 React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem", id: `${props.idSchema.$id}__title` }, props.title || ''),
134 React.createElement("div", { className: "jp-FormGroup-description", id: `${props.idSchema.$id}-description` }, props.schema.description || ''))) : (React.createElement(React.Fragment, null,
135 props.title && (React.createElement(TitleField, { ...commonProps, title: props.title, id: `${props.idSchema.$id}-title` })),
136 React.createElement(DescriptionField, { ...commonProps, id: `${props.idSchema.$id}-description`, description: (_a = props.schema.description) !== null && _a !== void 0 ? _a : '' }))),
137 props.items.map(item => {
138 return (React.createElement("div", { key: item.key, className: item.className },
139 item.children,
140 React.createElement("div", { className: "jp-ArrayOperations" },
141 React.createElement(MoveButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item, direction: "up" }),
142 React.createElement(MoveButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item, direction: "down" }),
143 React.createElement(DropButton, { buttonStyle: props.buttonStyle, translator: props.translator, item: item }))));
144 }),
145 props.canAdd && (React.createElement(AddButton, { onAddClick: props.onAddClick, buttonStyle: props.buttonStyle, translator: props.translator }))));
146 }
147});
148/**
149 * Template with custom add button, necessary for accessibility and internationalization.
150 */
151const CustomObjectTemplateFactory = (options) => customizeForLab({
152 ...options,
153 name: 'JupyterLabObjectTemplate',
154 component: props => {
155 var _a;
156 const { schema, registry, uiSchema, required } = props;
157 const commonProps = { schema, registry, uiSchema, required };
158 const { TitleField, DescriptionField } = getTemplates(registry, uiSchema);
159 return (React.createElement("fieldset", { id: props.idSchema.$id },
160 props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
161 React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem", id: `${props.idSchema.$id}__title` }, props.title || ''),
162 React.createElement("div", { className: "jp-FormGroup-description", id: `${props.idSchema.$id}__description` }, props.schema.description || ''))) : (React.createElement(React.Fragment, null,
163 (props.title ||
164 (props.uiSchema || JSONExt.emptyObject)['ui:title']) && (React.createElement(TitleField, { ...commonProps, id: `${props.idSchema.$id}__title`, title: props.title ||
165 `${(props.uiSchema || JSONExt.emptyObject)['ui:title']}` ||
166 '' })),
167 React.createElement(DescriptionField, { ...commonProps, id: `${props.idSchema.$id}__description`, description: (_a = props.schema.description) !== null && _a !== void 0 ? _a : '' }))),
168 props.properties.map(property => property.content),
169 canExpand(props.schema, props.uiSchema, props.formData) && (React.createElement(AddButton, { onAddClick: props.onAddClick(props.schema), buttonStyle: props.buttonStyle, translator: props.translator }))));
170 }
171});
172/**
173 * Renders the modified indicator and errors
174 */
175const CustomTemplateFactory = (options) => customizeForLab({
176 ...options,
177 name: 'JupyterLabFieldTemplate',
178 component: props => {
179 var _a;
180 const trans = ((_a = props.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
181 let isModified = false;
182 let defaultValue;
183 const { formData, schema, label, displayLabel, id, formContext, errors, rawErrors, children, onKeyChange, onDropPropertyClick } = props;
184 const { defaultFormData } = formContext;
185 const schemaIds = id.split('_');
186 schemaIds.shift();
187 const schemaId = schemaIds.join('.');
188 const isRoot = schemaId === '';
189 const hasCustomField = schemaId === (props.uiSchema || JSONExt.emptyObject)['ui:field'];
190 if (props.showModifiedFromDefault) {
191 /**
192 * Determine if the field has been modified.
193 * Schema Id is formatted as 'root_<field name>.<nested field name>'
194 * This logic parses out the field name to find the default value
195 * before determining if the field has been modified.
196 */
197 defaultValue = schemaIds.reduce((acc, key) => acc === null || acc === void 0 ? void 0 : acc[key], defaultFormData);
198 isModified =
199 !isRoot &&
200 formData !== undefined &&
201 defaultValue !== undefined &&
202 !schema.properties &&
203 schema.type !== 'array' &&
204 !JSONExt.deepEqual(formData, defaultValue);
205 }
206 const needsDescription = !isRoot &&
207 schema.type != 'object' &&
208 id !=
209 'jp-SettingsEditor-@jupyterlab/shortcuts-extension:shortcuts_shortcuts';
210 // While we can implement "remove" button for array items in array template,
211 // object templates do not provide a way to do this instead we need to add
212 // buttons here (and first check if the field can be removed = is additional).
213 const isAdditional = schema.hasOwnProperty(ADDITIONAL_PROPERTY_FLAG);
214 const isItem = !(schema.type === 'object' || schema.type === 'array');
215 return (React.createElement("div", { className: `form-group ${displayLabel || schema.type === 'boolean' ? 'small-field' : ''}` },
216 !hasCustomField &&
217 (rawErrors ? (
218 // Shows a red indicator for fields that have validation errors
219 React.createElement("div", { className: "jp-modifiedIndicator jp-errorIndicator" })) : (
220 // Only show the modified indicator if there are no errors
221 isModified && React.createElement("div", { className: "jp-modifiedIndicator" }))),
222 React.createElement("div", { className: `jp-FormGroup-content ${props.compact
223 ? 'jp-FormGroup-contentCompact'
224 : 'jp-FormGroup-contentNormal'}` },
225 isItem && displayLabel && !isRoot && label && !isAdditional ? (props.compact ? (React.createElement("div", { className: "jp-FormGroup-compactTitle" },
226 React.createElement("div", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, label),
227 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)),
228 isAdditional && (React.createElement("input", { className: "jp-FormGroup-contentItem jp-mod-styled", type: "text", onBlur: event => onKeyChange(event.target.value), defaultValue: label })),
229 React.createElement("div", { className: `${isRoot
230 ? 'jp-root'
231 : schema.type === 'object'
232 ? 'jp-objectFieldWrapper'
233 : schema.type === 'array'
234 ? 'jp-arrayFieldWrapper'
235 : 'jp-inputFieldWrapper jp-FormGroup-contentItem'}` }, children),
236 isAdditional && (React.createElement("button", { className: "jp-FormGroup-contentItem jp-mod-styled jp-mod-warn jp-FormGroup-removeButton", onClick: onDropPropertyClick(label) }, trans.__('Remove'))),
237 !props.compact && schema.description && needsDescription && (React.createElement("div", { className: "jp-FormGroup-description" }, schema.description)),
238 isModified && defaultValue !== undefined && (React.createElement("div", { className: "jp-FormGroup-default" }, trans.__('Default: %1', defaultValue !== null ? defaultValue.toLocaleString() : 'null'))),
239 React.createElement("div", { className: "validationErrors" }, errors))));
240 }
241});
242/**
243 * Generic rjsf form component for JupyterLab UI.
244 */
245export function FormComponent(props) {
246 const { buttonStyle, compact, showModifiedFromDefault, translator, formContext, ...others } = props;
247 const uiSchema = { ...(others.uiSchema || JSONExt.emptyObject) };
248 uiSchema['ui:options'] = { ...DEFAULT_UI_OPTIONS, ...uiSchema['ui:options'] };
249 others.uiSchema = uiSchema;
250 const { FieldTemplate, ArrayFieldTemplate, ObjectFieldTemplate } = props.templates || JSONExt.emptyObject;
251 const customization = {
252 buttonStyle,
253 compact,
254 showModifiedFromDefault,
255 translator
256 };
257 const fieldTemplate = React.useMemo(() => FieldTemplate !== null && FieldTemplate !== void 0 ? FieldTemplate : CustomTemplateFactory(customization), [FieldTemplate, buttonStyle, compact, showModifiedFromDefault, translator]);
258 const arrayTemplate = React.useMemo(() => ArrayFieldTemplate !== null && ArrayFieldTemplate !== void 0 ? ArrayFieldTemplate : CustomArrayTemplateFactory(customization), [
259 ArrayFieldTemplate,
260 buttonStyle,
261 compact,
262 showModifiedFromDefault,
263 translator
264 ]);
265 const objectTemplate = React.useMemo(() => ObjectFieldTemplate !== null && ObjectFieldTemplate !== void 0 ? ObjectFieldTemplate : CustomObjectTemplateFactory(customization), [
266 ObjectFieldTemplate,
267 buttonStyle,
268 compact,
269 showModifiedFromDefault,
270 translator
271 ]);
272 const templates = {
273 FieldTemplate: fieldTemplate,
274 ArrayFieldTemplate: arrayTemplate,
275 ObjectFieldTemplate: objectTemplate
276 };
277 return (React.createElement(Form, { templates: templates, formContext: formContext, ...others }));
278}
279//# sourceMappingURL=form.js.map
\No newline at end of file