@limetech/lime-elements 39.17.0 → 39.17.2
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/CHANGELOG.md +16 -0
- package/dist/cjs/limel-card.cjs.entry.js +1 -1
- package/dist/cjs/limel-form.cjs.entry.js +128 -1
- package/dist/collection/components/card/card.css +53 -54
- package/dist/collection/components/form/fields/array-field.js +97 -1
- package/dist/collection/components/form/fields/field-helpers.js +15 -0
- package/dist/collection/components/form/fields/schema-field.js +19 -2
- package/dist/collection/components/form/form.test-schemas.js +118 -0
- package/dist/esm/limel-card.entry.js +1 -1
- package/dist/esm/limel-form.entry.js +128 -1
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/p-585b0a3a.entry.js +1 -0
- package/dist/lime-elements/{p-1cb2d781.entry.js → p-e60ffc0a.entry.js} +4 -4
- package/dist/types/components/form/fields/array-field.d.ts +20 -0
- package/dist/types/components/form/fields/field-helpers.d.ts +10 -0
- package/dist/types/components/form/fields/schema-field.d.ts +10 -0
- package/dist/types/components/form/form.test-schemas.d.ts +4 -0
- package/package.json +1 -1
- package/dist/lime-elements/p-6416ca01.entry.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [39.17.2](https://github.com/Lundalogik/lime-elements/compare/v39.17.1...v39.17.2) (2026-04-27)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* **card:** don't show hover effects when card is neither clickable nor has 3D effect ([20cf51b](https://github.com/Lundalogik/lime-elements/commit/20cf51b5a425a05eb259d39f80c24ca9bf130fe7))
|
|
7
|
+
|
|
8
|
+
## [39.17.1](https://github.com/Lundalogik/lime-elements/compare/v39.17.0...v39.17.1) (2026-04-27)
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
* **form:** allow clearing custom components nested inside array items ([02910c1](https://github.com/Lundalogik/lime-elements/commit/02910c1b10fca4e5fbea564555dc4db75be82c99))
|
|
14
|
+
* **form:** preserve nested-object leaf data inside array items ([8e29997](https://github.com/Lundalogik/lime-elements/commit/8e299974ed9b3b14dd9eda4313ded2106716225d))
|
|
15
|
+
* **form:** reset obsolete dependent fields inside array items on trigger change ([7e1458e](https://github.com/Lundalogik/lime-elements/commit/7e1458e54467db9f2019a1160a52dad94b7e7a14))
|
|
16
|
+
|
|
1
17
|
## [39.17.0](https://github.com/Lundalogik/lime-elements/compare/v39.16.4...v39.17.0) (2026-04-27)
|
|
2
18
|
|
|
3
19
|
### Features
|
|
@@ -5,7 +5,7 @@ var isItem = require('./is-item-DMELyaao.js');
|
|
|
5
5
|
var getIconProps = require('./get-icon-props-CwpDdQDI.js');
|
|
6
6
|
var _3dTiltHoverEffect = require('./3d-tilt-hover-effect-DI8kt0Y0.js');
|
|
7
7
|
|
|
8
|
-
const cardCss = () => `@charset "UTF-8";*{box-sizing:border-box;min-width:0;min-height:0}:host(limel-card){display:flex;border-radius:var(--card-border-radius, 0.95rem)}section{box-sizing:border-box;display:flex;gap:0.5rem;flex-direction:column}:host(limel-card[orientation=landscape]) section{flex-direction:row}section{width:100%;border-radius:var(--card-border-radius, 0.95rem);border:1px solid rgb(var(--contrast-500));padding:0.25rem;background-color:var(--card-background-color, rgb(var(--contrast-300)))}section:hover{border-color:transparent;background-color:var(--card-background-color--hovered, var(--card-background-color, rgb(var(--contrast-200))))}.body{flex-grow:1;display:flex;flex-direction:column;gap:0.5rem}.image-wrapper{flex-shrink:0;display:flex;align-items:center;justify-content:center}:host(limel-card[orientation=portrait]) .image-wrapper{width:100%}:host(limel-card[orientation=landscape]) .image-wrapper{flex-shrink:0;max-width:40%;height:100%}:host(limel-card[orientation=landscape]) .image-wrapper img{height:100%}img{transition:filter 0.6s ease;object-fit:cover;border-radius:calc(var(--card-border-radius, 0.95rem) / 1.4);min-width:0.5rem;min-height:0.5rem;max-width:100%;max-height:100%}section:hover img
|
|
8
|
+
const cardCss = () => `@charset "UTF-8";*{box-sizing:border-box;min-width:0;min-height:0}:host(limel-card){display:flex;border-radius:var(--card-border-radius, 0.95rem)}section{box-sizing:border-box;display:flex;gap:0.5rem;flex-direction:column}:host(limel-card[orientation=landscape]) section{flex-direction:row}section{width:100%;border-radius:var(--card-border-radius, 0.95rem);border:1px solid rgb(var(--contrast-500));padding:0.25rem;background-color:var(--card-background-color, rgb(var(--contrast-300)))}:host(limel-card[clickable]) section,:host(limel-card[show-3d-effect]) section{position:relative;transition-duration:0.8s;transition-property:transform, box-shadow, background-color;transition-timing-function:ease-out;transform:scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg)}:host(limel-card[clickable]) section:focus,:host(limel-card[show-3d-effect]) section:focus{outline:none}:host(limel-card[clickable]) section:hover,:host(limel-card[clickable]) section:focus,:host(limel-card[clickable]) section:focus-visible,:host(limel-card[clickable]) section:focus-within,:host(limel-card[show-3d-effect]) section:hover,:host(limel-card[show-3d-effect]) section:focus,:host(limel-card[show-3d-effect]) section:focus-visible,:host(limel-card[show-3d-effect]) section:focus-within{will-change:background-color, box-shadow, transform}:host(limel-card[clickable]) section:hover,:host(limel-card[clickable]) section:focus,:host(limel-card[clickable]) section:focus-visible,:host(limel-card[clickable]) section:active,:host(limel-card[show-3d-effect]) section:hover,:host(limel-card[show-3d-effect]) section:focus,:host(limel-card[show-3d-effect]) section:focus-visible,:host(limel-card[show-3d-effect]) section:active{transition-duration:0.2s}:host(limel-card[clickable]) section:hover,:host(limel-card[clickable]) section:focus-visible,:host(limel-card[show-3d-effect]) section:hover,:host(limel-card[show-3d-effect]) section:focus-visible{box-shadow:var(--button-shadow-hovered), var(--shadow-depth-16)}:host(limel-card[clickable]) section:hover,:host(limel-card[show-3d-effect]) section:hover{transform:scale3d(1.01, 1.01, 1.01) rotate3d(var(--limel-3d-hover-effect-rotate3d))}:host(limel-card[clickable]) section:focus-visible,:host(limel-card[show-3d-effect]) section:focus-visible{outline:none;transform:scale3d(1.01, 1.01, 1.01)}:host(limel-card[clickable]) section:hover limel-3d-hover-effect-glow,:host(limel-card[show-3d-effect]) section:hover limel-3d-hover-effect-glow{--limel-3d-hover-effect-glow-opacity:0.5}@media (prefers-reduced-motion){:host(limel-card[clickable]) section:hover limel-3d-hover-effect-glow,:host(limel-card[show-3d-effect]) section:hover limel-3d-hover-effect-glow{--limel-3d-hover-effect-glow-opacity:0.2}}:host(limel-card[clickable]) section{cursor:pointer;box-shadow:var(--button-shadow-normal)}:host(limel-card[clickable]) section:hover,:host(limel-card[clickable]) section:focus-visible{box-shadow:var(--button-shadow-hovered), var(--shadow-depth-16)}:host(limel-card[clickable]) section:active{transform:scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg);box-shadow:var(--button-shadow-pressed)}:host(limel-card[clickable]) section:focus-visible{box-shadow:var(--shadow-depth-8-focused), var(--button-shadow-hovered)}:host(limel-card[clickable]) section:focus-visible:active{box-shadow:var(--shadow-depth-8-focused), var(--button-shadow-pressed)}:host(limel-card[clickable]) section:hover{border-color:transparent;background-color:var(--card-background-color--hovered, var(--card-background-color, rgb(var(--contrast-200))))}.body{flex-grow:1;display:flex;flex-direction:column;gap:0.5rem}.image-wrapper{flex-shrink:0;display:flex;align-items:center;justify-content:center}:host(limel-card[orientation=portrait]) .image-wrapper{width:100%}:host(limel-card[orientation=landscape]) .image-wrapper{flex-shrink:0;max-width:40%;height:100%}:host(limel-card[orientation=landscape]) .image-wrapper img{height:100%}img{transition:filter 0.6s ease;object-fit:cover;border-radius:calc(var(--card-border-radius, 0.95rem) / 1.4);min-width:0.5rem;min-height:0.5rem;max-width:100%;max-height:100%}:host(limel-card[show-3d-effect]) section:hover img,:host(limel-card[show-3d-effect]) section:focus-visible img{transition-duration:0.2s;filter:saturate(1.3)}.markdown-wrapper{position:relative;flex-grow:1;display:flex;flex-direction:column}.markdown-wrapper limel-markdown{overflow-y:auto;flex-grow:1;padding:0.5rem 0.75rem}.markdown-wrapper::before{top:0;background:radial-gradient(farthest-side at 50% 0%, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0))}.markdown-wrapper::after{bottom:0;background:radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0))}.markdown-wrapper::before,.markdown-wrapper::after{content:"";pointer-events:none;position:absolute;right:0;left:0;height:0.75rem;opacity:0;transition:opacity 0.6s ease;background-repeat:no-repeat}.markdown-wrapper.can-scroll-up::before,.markdown-wrapper.can-scroll-down::after{opacity:1}header{flex-shrink:0;display:flex;justify-content:center;gap:0.5rem;padding:0.25rem 0.75rem}:host(limel-card[orientation=landscape]) header{padding-top:0.5rem}header:has(limel-icon){padding-left:0.25rem}header .headings{flex-grow:1;display:flex;flex-direction:column;gap:0.125rem}header .title{padding-right:1.25rem}header limel-icon{flex-shrink:0;width:2rem}header h1{font-size:1.125rem;font-weight:500;color:var(--card-heading-color, rgb(var(--contrast-1100)));letter-spacing:-0.03125rem}header h2{font-size:var(--limel-theme-default-font-size);font-weight:400;color:var(--card-subheading-color, rgb(var(--contrast-1000)))}header h1,header h2{word-break:break-word;hyphens:auto;-webkit-hyphens:auto;margin:0}limel-action-bar{flex-shrink:0;--action-bar-background-color:transparent;margin-left:auto}limel-3d-hover-effect-glow{border-radius:var(--card-border-radius, 0.95rem)}:host(limel-card[show-3d-effect]){isolation:isolate;transform-style:preserve-3d;perspective:1000px}@media (prefers-reduced-motion){:host(limel-card[show-3d-effect]){perspective:2000px}}:host(limel-card[selected]) section,:host(limel-card[selected]) section:hover,:host(limel-card[selected]) section:focus-visible,:host(limel-card[selected]) section:active{box-shadow:var(--shadow-focused-state)}`;
|
|
9
9
|
|
|
10
10
|
const Card = class {
|
|
11
11
|
constructor(hostRef) {
|
|
@@ -39813,6 +39813,21 @@ function isCustomObjectSchema(schema) {
|
|
|
39813
39813
|
function isAdditionalProperty(schema) {
|
|
39814
39814
|
return schema[ADDITIONAL_PROPERTY_FLAG] === true;
|
|
39815
39815
|
}
|
|
39816
|
+
/**
|
|
39817
|
+
* Check whether a schema permits `null` as a valid value. Used to decide
|
|
39818
|
+
* whether a cleared field value should be preserved as `null` or converted
|
|
39819
|
+
* to `undefined` so the property can be removed from the form data.
|
|
39820
|
+
*
|
|
39821
|
+
* @param schema - the schema to check
|
|
39822
|
+
* @returns true if the schema's type is `'null'` or includes `'null'`
|
|
39823
|
+
*/
|
|
39824
|
+
const schemaAllowsNull = (schema) => {
|
|
39825
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
39826
|
+
if (!type) {
|
|
39827
|
+
return false;
|
|
39828
|
+
}
|
|
39829
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
39830
|
+
};
|
|
39816
39831
|
|
|
39817
39832
|
/**
|
|
39818
39833
|
* A widget is a concept in react-jsonschema-form (rjsf).
|
|
@@ -40007,7 +40022,7 @@ const BaseSchemaField = defaultFields$2.SchemaField;
|
|
|
40007
40022
|
* @returns whether or not null should be changed to undefined
|
|
40008
40023
|
*/
|
|
40009
40024
|
const shouldChangeToUndefined = (value, schema) => {
|
|
40010
|
-
return value === null && !schema
|
|
40025
|
+
return value === null && !schemaAllowsNull(schema);
|
|
40011
40026
|
};
|
|
40012
40027
|
const hasCustomComponent = (schema) => {
|
|
40013
40028
|
var _a, _b;
|
|
@@ -40094,9 +40109,26 @@ class SchemaField extends React.Component {
|
|
|
40094
40109
|
const { formData, schema } = this.props;
|
|
40095
40110
|
const { rootSchema } = this.props.registry;
|
|
40096
40111
|
this.setState({ modified: true });
|
|
40112
|
+
if (this.isDeepLeafChange(path)) {
|
|
40113
|
+
this.props.onChange(data, path);
|
|
40114
|
+
return;
|
|
40115
|
+
}
|
|
40097
40116
|
const newData = resetDependentFields(formData, data, schema, rootSchema);
|
|
40098
40117
|
this.props.onChange(newData, path);
|
|
40099
40118
|
}
|
|
40119
|
+
/**
|
|
40120
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40121
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40122
|
+
* through this SchemaField with a path that is deeper than its own.
|
|
40123
|
+
* The enclosing ArrayField rebuilds the affected item and runs
|
|
40124
|
+
* `resetDependentFields` at the item level (see array-field.ts), so we
|
|
40125
|
+
* must pass the partial leaf data through untouched here.
|
|
40126
|
+
* @param path
|
|
40127
|
+
*/
|
|
40128
|
+
isDeepLeafChange(path) {
|
|
40129
|
+
const { fieldPathId } = this.props;
|
|
40130
|
+
return !!fieldPathId && path.length > fieldPathId.path.length;
|
|
40131
|
+
}
|
|
40100
40132
|
buildCustomComponentProps() {
|
|
40101
40133
|
const { disabled, readonly, name, registry, schema: rawSchema, errorSchema, fieldPathId, } = this.props;
|
|
40102
40134
|
const schema = rawSchema;
|
|
@@ -40208,6 +40240,10 @@ class ArrayField extends React.Component {
|
|
|
40208
40240
|
handleChange(newData, path) {
|
|
40209
40241
|
const { formData: oldData, schema } = this.props;
|
|
40210
40242
|
const { rootSchema } = this.props.registry;
|
|
40243
|
+
if (this.isDeepLeafChange(path)) {
|
|
40244
|
+
this.handleDeepLeafChange(newData, path);
|
|
40245
|
+
return;
|
|
40246
|
+
}
|
|
40211
40247
|
// This case handles when the first list item is added. When there are no
|
|
40212
40248
|
// items we get undefined instead of []
|
|
40213
40249
|
if (!oldData) {
|
|
@@ -40237,11 +40273,102 @@ class ArrayField extends React.Component {
|
|
|
40237
40273
|
}
|
|
40238
40274
|
this.props.onChange(newData, path);
|
|
40239
40275
|
}
|
|
40276
|
+
/**
|
|
40277
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40278
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40279
|
+
* through here with the leaf value (not the full array) and a path that
|
|
40280
|
+
* is deeper than this field's own path.
|
|
40281
|
+
* @param path
|
|
40282
|
+
*/
|
|
40283
|
+
isDeepLeafChange(path) {
|
|
40284
|
+
const { formData, fieldPathId } = this.props;
|
|
40285
|
+
return (Array.isArray(formData) &&
|
|
40286
|
+
!!fieldPathId &&
|
|
40287
|
+
path.length > fieldPathId.path.length);
|
|
40288
|
+
}
|
|
40289
|
+
/**
|
|
40290
|
+
* Rebuild the affected item locally so we can (a) restore `undefined`
|
|
40291
|
+
* when the leaf schema does not allow `null` — the built-in handler
|
|
40292
|
+
* coerces `undefined` to `null`, which breaks field clearing for custom
|
|
40293
|
+
* components — and (b) still run `resetDependentFields` at the item
|
|
40294
|
+
* level so sibling fields that became obsolete for the new data are
|
|
40295
|
+
* cleared.
|
|
40296
|
+
* @param newData
|
|
40297
|
+
* @param path
|
|
40298
|
+
*/
|
|
40299
|
+
handleDeepLeafChange(newData, path) {
|
|
40300
|
+
const { formData: oldData, fieldPathId } = this.props;
|
|
40301
|
+
const [indexSegment, ...pathInItem] = path.slice(fieldPathId.path.length);
|
|
40302
|
+
const index = Number(indexSegment);
|
|
40303
|
+
const newArray = [...oldData];
|
|
40304
|
+
newArray[index] = this.buildResetItem(newData, index, pathInItem);
|
|
40305
|
+
this.props.onChange(newArray, fieldPathId.path);
|
|
40306
|
+
}
|
|
40307
|
+
buildResetItem(newData, index, pathInItem) {
|
|
40308
|
+
const { formData: oldData, schema } = this.props;
|
|
40309
|
+
const { rootSchema } = this.props.registry;
|
|
40310
|
+
const itemSchema = (Array.isArray(schema.items) ? schema.items[index] : schema.items);
|
|
40311
|
+
const leafSchema = getSchemaAtPath(itemSchema, pathInItem);
|
|
40312
|
+
const value = newData === null && !schemaAllowsNull(leafSchema)
|
|
40313
|
+
? undefined
|
|
40314
|
+
: newData;
|
|
40315
|
+
const oldItem = oldData[index];
|
|
40316
|
+
const newItem = setValueAtPath(oldItem, pathInItem, value);
|
|
40317
|
+
return resetDependentFields(oldItem, newItem, itemSchema, rootSchema);
|
|
40318
|
+
}
|
|
40240
40319
|
render() {
|
|
40241
40320
|
const arrayProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange });
|
|
40242
40321
|
return React.createElement('div', { ref: this.setWrapper }, React.createElement(BaseArrayField, arrayProps));
|
|
40243
40322
|
}
|
|
40244
40323
|
}
|
|
40324
|
+
function getSchemaAtPath(schema, pathSegments) {
|
|
40325
|
+
let current = schema;
|
|
40326
|
+
for (const segment of pathSegments) {
|
|
40327
|
+
if (!current) {
|
|
40328
|
+
return;
|
|
40329
|
+
}
|
|
40330
|
+
current = resolveSchemaSegment(current, segment);
|
|
40331
|
+
}
|
|
40332
|
+
return current;
|
|
40333
|
+
}
|
|
40334
|
+
function resolveSchemaSegment(schema, segment) {
|
|
40335
|
+
var _a;
|
|
40336
|
+
if (typeof segment === 'number') {
|
|
40337
|
+
const items = schema.items;
|
|
40338
|
+
return Array.isArray(items) ? items[segment] : items;
|
|
40339
|
+
}
|
|
40340
|
+
return (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[segment];
|
|
40341
|
+
}
|
|
40342
|
+
/**
|
|
40343
|
+
* Return a copy of `target` with `value` written at the location described
|
|
40344
|
+
* by `pathSegments`, creating intermediate containers as needed. Numeric
|
|
40345
|
+
* segments address arrays; string segments address object keys. `target`
|
|
40346
|
+
* is not mutated.
|
|
40347
|
+
*
|
|
40348
|
+
* @param target
|
|
40349
|
+
* @param pathSegments
|
|
40350
|
+
* @param value
|
|
40351
|
+
* @example
|
|
40352
|
+
* setValueAtPath({ a: { b: 1 } }, ['a', 'b'], 2); // → { a: { b: 2 } }
|
|
40353
|
+
* setValueAtPath([{ x: 1 }], [0, 'x'], 9); // → [{ x: 9 }]
|
|
40354
|
+
*/
|
|
40355
|
+
function setValueAtPath(target, pathSegments, value) {
|
|
40356
|
+
if (pathSegments.length === 0) {
|
|
40357
|
+
return value;
|
|
40358
|
+
}
|
|
40359
|
+
const clone = cloneContainer(target, pathSegments[0]);
|
|
40360
|
+
set(clone, pathSegments, value);
|
|
40361
|
+
return clone;
|
|
40362
|
+
}
|
|
40363
|
+
function cloneContainer(target, firstSegment) {
|
|
40364
|
+
if (Array.isArray(target) || isPlainObject(target)) {
|
|
40365
|
+
return cloneDeep.cloneDeep(target);
|
|
40366
|
+
}
|
|
40367
|
+
if (typeof firstSegment === 'number') {
|
|
40368
|
+
return [];
|
|
40369
|
+
}
|
|
40370
|
+
return {};
|
|
40371
|
+
}
|
|
40245
40372
|
|
|
40246
40373
|
class CodeEditor extends React.Component {
|
|
40247
40374
|
constructor(props) {
|
|
@@ -131,7 +131,58 @@ section {
|
|
|
131
131
|
padding: 0.25rem;
|
|
132
132
|
background-color: var(--card-background-color, rgb(var(--contrast-300)));
|
|
133
133
|
}
|
|
134
|
-
section:
|
|
134
|
+
:host(limel-card[clickable]) section, :host(limel-card[show-3d-effect]) section {
|
|
135
|
+
position: relative;
|
|
136
|
+
transition-duration: 0.8s;
|
|
137
|
+
transition-property: transform, box-shadow, background-color;
|
|
138
|
+
transition-timing-function: ease-out;
|
|
139
|
+
transform: scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg);
|
|
140
|
+
}
|
|
141
|
+
:host(limel-card[clickable]) section:focus, :host(limel-card[show-3d-effect]) section:focus {
|
|
142
|
+
outline: none;
|
|
143
|
+
}
|
|
144
|
+
:host(limel-card[clickable]) section:hover, :host(limel-card[clickable]) section:focus, :host(limel-card[clickable]) section:focus-visible, :host(limel-card[clickable]) section:focus-within, :host(limel-card[show-3d-effect]) section:hover, :host(limel-card[show-3d-effect]) section:focus, :host(limel-card[show-3d-effect]) section:focus-visible, :host(limel-card[show-3d-effect]) section:focus-within {
|
|
145
|
+
will-change: background-color, box-shadow, transform;
|
|
146
|
+
}
|
|
147
|
+
:host(limel-card[clickable]) section:hover, :host(limel-card[clickable]) section:focus, :host(limel-card[clickable]) section:focus-visible, :host(limel-card[clickable]) section:active, :host(limel-card[show-3d-effect]) section:hover, :host(limel-card[show-3d-effect]) section:focus, :host(limel-card[show-3d-effect]) section:focus-visible, :host(limel-card[show-3d-effect]) section:active {
|
|
148
|
+
transition-duration: 0.2s;
|
|
149
|
+
}
|
|
150
|
+
:host(limel-card[clickable]) section:hover, :host(limel-card[clickable]) section:focus-visible, :host(limel-card[show-3d-effect]) section:hover, :host(limel-card[show-3d-effect]) section:focus-visible {
|
|
151
|
+
box-shadow: var(--button-shadow-hovered), var(--shadow-depth-16);
|
|
152
|
+
}
|
|
153
|
+
:host(limel-card[clickable]) section:hover, :host(limel-card[show-3d-effect]) section:hover {
|
|
154
|
+
transform: scale3d(1.01, 1.01, 1.01) rotate3d(var(--limel-3d-hover-effect-rotate3d));
|
|
155
|
+
}
|
|
156
|
+
:host(limel-card[clickable]) section:focus-visible, :host(limel-card[show-3d-effect]) section:focus-visible {
|
|
157
|
+
outline: none;
|
|
158
|
+
transform: scale3d(1.01, 1.01, 1.01);
|
|
159
|
+
}
|
|
160
|
+
:host(limel-card[clickable]) section:hover limel-3d-hover-effect-glow, :host(limel-card[show-3d-effect]) section:hover limel-3d-hover-effect-glow {
|
|
161
|
+
--limel-3d-hover-effect-glow-opacity: 0.5;
|
|
162
|
+
}
|
|
163
|
+
@media (prefers-reduced-motion) {
|
|
164
|
+
:host(limel-card[clickable]) section:hover limel-3d-hover-effect-glow, :host(limel-card[show-3d-effect]) section:hover limel-3d-hover-effect-glow {
|
|
165
|
+
--limel-3d-hover-effect-glow-opacity: 0.2;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
:host(limel-card[clickable]) section {
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
box-shadow: var(--button-shadow-normal);
|
|
171
|
+
}
|
|
172
|
+
:host(limel-card[clickable]) section:hover, :host(limel-card[clickable]) section:focus-visible {
|
|
173
|
+
box-shadow: var(--button-shadow-hovered), var(--shadow-depth-16);
|
|
174
|
+
}
|
|
175
|
+
:host(limel-card[clickable]) section:active {
|
|
176
|
+
transform: scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg);
|
|
177
|
+
box-shadow: var(--button-shadow-pressed);
|
|
178
|
+
}
|
|
179
|
+
:host(limel-card[clickable]) section:focus-visible {
|
|
180
|
+
box-shadow: var(--shadow-depth-8-focused), var(--button-shadow-hovered);
|
|
181
|
+
}
|
|
182
|
+
:host(limel-card[clickable]) section:focus-visible:active {
|
|
183
|
+
box-shadow: var(--shadow-depth-8-focused), var(--button-shadow-pressed);
|
|
184
|
+
}
|
|
185
|
+
:host(limel-card[clickable]) section:hover {
|
|
135
186
|
border-color: transparent;
|
|
136
187
|
background-color: var(--card-background-color--hovered, var(--card-background-color, rgb(var(--contrast-200))));
|
|
137
188
|
}
|
|
@@ -170,7 +221,7 @@ img {
|
|
|
170
221
|
max-width: 100%;
|
|
171
222
|
max-height: 100%;
|
|
172
223
|
}
|
|
173
|
-
section:hover img, section:focus-visible img {
|
|
224
|
+
:host(limel-card[show-3d-effect]) section:hover img, :host(limel-card[show-3d-effect]) section:focus-visible img {
|
|
174
225
|
transition-duration: 0.2s;
|
|
175
226
|
filter: saturate(1.3);
|
|
176
227
|
}
|
|
@@ -275,58 +326,6 @@ limel-3d-hover-effect-glow {
|
|
|
275
326
|
}
|
|
276
327
|
}
|
|
277
328
|
|
|
278
|
-
section {
|
|
279
|
-
position: relative;
|
|
280
|
-
transition-duration: 0.8s;
|
|
281
|
-
transition-property: transform, box-shadow, background-color;
|
|
282
|
-
transition-timing-function: ease-out;
|
|
283
|
-
transform: scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg);
|
|
284
|
-
}
|
|
285
|
-
section:focus {
|
|
286
|
-
outline: none;
|
|
287
|
-
}
|
|
288
|
-
section:hover, section:focus, section:focus-visible, section:focus-within {
|
|
289
|
-
will-change: background-color, box-shadow, transform;
|
|
290
|
-
}
|
|
291
|
-
section:hover, section:focus, section:focus-visible, section:active {
|
|
292
|
-
transition-duration: 0.2s;
|
|
293
|
-
}
|
|
294
|
-
section:hover, section:focus-visible {
|
|
295
|
-
box-shadow: var(--button-shadow-hovered), var(--shadow-depth-16);
|
|
296
|
-
}
|
|
297
|
-
section:hover {
|
|
298
|
-
transform: scale3d(1.01, 1.01, 1.01) rotate3d(var(--limel-3d-hover-effect-rotate3d));
|
|
299
|
-
}
|
|
300
|
-
section:focus-visible {
|
|
301
|
-
outline: none;
|
|
302
|
-
transform: scale3d(1.01, 1.01, 1.01);
|
|
303
|
-
}
|
|
304
|
-
section:hover limel-3d-hover-effect-glow {
|
|
305
|
-
--limel-3d-hover-effect-glow-opacity: 0.5;
|
|
306
|
-
}
|
|
307
|
-
@media (prefers-reduced-motion) {
|
|
308
|
-
section:hover limel-3d-hover-effect-glow {
|
|
309
|
-
--limel-3d-hover-effect-glow-opacity: 0.2;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
:host(limel-card[clickable]) section {
|
|
313
|
-
cursor: pointer;
|
|
314
|
-
box-shadow: var(--button-shadow-normal);
|
|
315
|
-
}
|
|
316
|
-
:host(limel-card[clickable]) section:hover, :host(limel-card[clickable]) section:focus-visible {
|
|
317
|
-
box-shadow: var(--button-shadow-hovered), var(--shadow-depth-16);
|
|
318
|
-
}
|
|
319
|
-
:host(limel-card[clickable]) section:active {
|
|
320
|
-
transform: scale3d(1, 1, 1) rotate3d(0, 0, 0, 0deg);
|
|
321
|
-
box-shadow: var(--button-shadow-pressed);
|
|
322
|
-
}
|
|
323
|
-
:host(limel-card[clickable]) section:focus-visible {
|
|
324
|
-
box-shadow: var(--shadow-depth-8-focused), var(--button-shadow-hovered);
|
|
325
|
-
}
|
|
326
|
-
:host(limel-card[clickable]) section:focus-visible:active {
|
|
327
|
-
box-shadow: var(--shadow-depth-8-focused), var(--button-shadow-pressed);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
329
|
:host(limel-card[selected]) section, :host(limel-card[selected]) section:hover, :host(limel-card[selected]) section:focus-visible, :host(limel-card[selected]) section:active {
|
|
331
330
|
box-shadow: var(--shadow-focused-state);
|
|
332
331
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { getDefaultRegistry } from "@rjsf/core";
|
|
3
|
-
import {
|
|
3
|
+
import { cloneDeep, isPlainObject, set } from "lodash-es";
|
|
4
|
+
import { resetDependentFields, schemaAllowsNull } from "./field-helpers";
|
|
4
5
|
import { ARRAY_REORDER_EVENT } from "../templates/array-field";
|
|
5
6
|
const { fields: defaultFields } = getDefaultRegistry();
|
|
6
7
|
const BaseArrayField = defaultFields.ArrayField;
|
|
@@ -69,6 +70,10 @@ export class ArrayField extends React.Component {
|
|
|
69
70
|
handleChange(newData, path) {
|
|
70
71
|
const { formData: oldData, schema } = this.props;
|
|
71
72
|
const { rootSchema } = this.props.registry;
|
|
73
|
+
if (this.isDeepLeafChange(path)) {
|
|
74
|
+
this.handleDeepLeafChange(newData, path);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
72
77
|
// This case handles when the first list item is added. When there are no
|
|
73
78
|
// items we get undefined instead of []
|
|
74
79
|
if (!oldData) {
|
|
@@ -98,8 +103,99 @@ export class ArrayField extends React.Component {
|
|
|
98
103
|
}
|
|
99
104
|
this.props.onChange(newData, path);
|
|
100
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
108
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
109
|
+
* through here with the leaf value (not the full array) and a path that
|
|
110
|
+
* is deeper than this field's own path.
|
|
111
|
+
* @param path
|
|
112
|
+
*/
|
|
113
|
+
isDeepLeafChange(path) {
|
|
114
|
+
const { formData, fieldPathId } = this.props;
|
|
115
|
+
return (Array.isArray(formData) &&
|
|
116
|
+
!!fieldPathId &&
|
|
117
|
+
path.length > fieldPathId.path.length);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Rebuild the affected item locally so we can (a) restore `undefined`
|
|
121
|
+
* when the leaf schema does not allow `null` — the built-in handler
|
|
122
|
+
* coerces `undefined` to `null`, which breaks field clearing for custom
|
|
123
|
+
* components — and (b) still run `resetDependentFields` at the item
|
|
124
|
+
* level so sibling fields that became obsolete for the new data are
|
|
125
|
+
* cleared.
|
|
126
|
+
* @param newData
|
|
127
|
+
* @param path
|
|
128
|
+
*/
|
|
129
|
+
handleDeepLeafChange(newData, path) {
|
|
130
|
+
const { formData: oldData, fieldPathId } = this.props;
|
|
131
|
+
const [indexSegment, ...pathInItem] = path.slice(fieldPathId.path.length);
|
|
132
|
+
const index = Number(indexSegment);
|
|
133
|
+
const newArray = [...oldData];
|
|
134
|
+
newArray[index] = this.buildResetItem(newData, index, pathInItem);
|
|
135
|
+
this.props.onChange(newArray, fieldPathId.path);
|
|
136
|
+
}
|
|
137
|
+
buildResetItem(newData, index, pathInItem) {
|
|
138
|
+
const { formData: oldData, schema } = this.props;
|
|
139
|
+
const { rootSchema } = this.props.registry;
|
|
140
|
+
const itemSchema = (Array.isArray(schema.items) ? schema.items[index] : schema.items);
|
|
141
|
+
const leafSchema = getSchemaAtPath(itemSchema, pathInItem);
|
|
142
|
+
const value = newData === null && !schemaAllowsNull(leafSchema)
|
|
143
|
+
? undefined
|
|
144
|
+
: newData;
|
|
145
|
+
const oldItem = oldData[index];
|
|
146
|
+
const newItem = setValueAtPath(oldItem, pathInItem, value);
|
|
147
|
+
return resetDependentFields(oldItem, newItem, itemSchema, rootSchema);
|
|
148
|
+
}
|
|
101
149
|
render() {
|
|
102
150
|
const arrayProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange });
|
|
103
151
|
return React.createElement('div', { ref: this.setWrapper }, React.createElement(BaseArrayField, arrayProps));
|
|
104
152
|
}
|
|
105
153
|
}
|
|
154
|
+
function getSchemaAtPath(schema, pathSegments) {
|
|
155
|
+
let current = schema;
|
|
156
|
+
for (const segment of pathSegments) {
|
|
157
|
+
if (!current) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
current = resolveSchemaSegment(current, segment);
|
|
161
|
+
}
|
|
162
|
+
return current;
|
|
163
|
+
}
|
|
164
|
+
function resolveSchemaSegment(schema, segment) {
|
|
165
|
+
var _a;
|
|
166
|
+
if (typeof segment === 'number') {
|
|
167
|
+
const items = schema.items;
|
|
168
|
+
return Array.isArray(items) ? items[segment] : items;
|
|
169
|
+
}
|
|
170
|
+
return (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[segment];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Return a copy of `target` with `value` written at the location described
|
|
174
|
+
* by `pathSegments`, creating intermediate containers as needed. Numeric
|
|
175
|
+
* segments address arrays; string segments address object keys. `target`
|
|
176
|
+
* is not mutated.
|
|
177
|
+
*
|
|
178
|
+
* @param target
|
|
179
|
+
* @param pathSegments
|
|
180
|
+
* @param value
|
|
181
|
+
* @example
|
|
182
|
+
* setValueAtPath({ a: { b: 1 } }, ['a', 'b'], 2); // → { a: { b: 2 } }
|
|
183
|
+
* setValueAtPath([{ x: 1 }], [0, 'x'], 9); // → [{ x: 9 }]
|
|
184
|
+
*/
|
|
185
|
+
function setValueAtPath(target, pathSegments, value) {
|
|
186
|
+
if (pathSegments.length === 0) {
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
const clone = cloneContainer(target, pathSegments[0]);
|
|
190
|
+
set(clone, pathSegments, value);
|
|
191
|
+
return clone;
|
|
192
|
+
}
|
|
193
|
+
function cloneContainer(target, firstSegment) {
|
|
194
|
+
if (Array.isArray(target) || isPlainObject(target)) {
|
|
195
|
+
return cloneDeep(target);
|
|
196
|
+
}
|
|
197
|
+
if (typeof firstSegment === 'number') {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
@@ -69,3 +69,18 @@ export function isCustomObjectSchema(schema) {
|
|
|
69
69
|
function isAdditionalProperty(schema) {
|
|
70
70
|
return schema[ADDITIONAL_PROPERTY_FLAG] === true;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Check whether a schema permits `null` as a valid value. Used to decide
|
|
74
|
+
* whether a cleared field value should be preserved as `null` or converted
|
|
75
|
+
* to `undefined` so the property can be removed from the form data.
|
|
76
|
+
*
|
|
77
|
+
* @param schema - the schema to check
|
|
78
|
+
* @returns true if the schema's type is `'null'` or includes `'null'`
|
|
79
|
+
*/
|
|
80
|
+
export const schemaAllowsNull = (schema) => {
|
|
81
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
82
|
+
if (!type) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
86
|
+
};
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { getDefaultRegistry } from "@rjsf/core";
|
|
3
3
|
import { isEmpty } from "lodash-es";
|
|
4
4
|
import { hasValue, isFieldInvalid, isFieldRequired, getErrorText, } from "../validation-display";
|
|
5
|
-
import { resetDependentFields } from "./field-helpers";
|
|
5
|
+
import { resetDependentFields, schemaAllowsNull } from "./field-helpers";
|
|
6
6
|
import { FieldTemplate } from "../templates";
|
|
7
7
|
import { getHelpComponent } from "../help";
|
|
8
8
|
import { TimePicker } from "../widgets/time-picker";
|
|
@@ -34,7 +34,7 @@ const BaseSchemaField = defaultFields.SchemaField;
|
|
|
34
34
|
* @returns whether or not null should be changed to undefined
|
|
35
35
|
*/
|
|
36
36
|
const shouldChangeToUndefined = (value, schema) => {
|
|
37
|
-
return value === null && !schema
|
|
37
|
+
return value === null && !schemaAllowsNull(schema);
|
|
38
38
|
};
|
|
39
39
|
const hasCustomComponent = (schema) => {
|
|
40
40
|
var _a, _b;
|
|
@@ -121,9 +121,26 @@ export class SchemaField extends React.Component {
|
|
|
121
121
|
const { formData, schema } = this.props;
|
|
122
122
|
const { rootSchema } = this.props.registry;
|
|
123
123
|
this.setState({ modified: true });
|
|
124
|
+
if (this.isDeepLeafChange(path)) {
|
|
125
|
+
this.props.onChange(data, path);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
124
128
|
const newData = resetDependentFields(formData, data, schema, rootSchema);
|
|
125
129
|
this.props.onChange(newData, path);
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
133
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
134
|
+
* through this SchemaField with a path that is deeper than its own.
|
|
135
|
+
* The enclosing ArrayField rebuilds the affected item and runs
|
|
136
|
+
* `resetDependentFields` at the item level (see array-field.ts), so we
|
|
137
|
+
* must pass the partial leaf data through untouched here.
|
|
138
|
+
* @param path
|
|
139
|
+
*/
|
|
140
|
+
isDeepLeafChange(path) {
|
|
141
|
+
const { fieldPathId } = this.props;
|
|
142
|
+
return !!fieldPathId && path.length > fieldPathId.path.length;
|
|
143
|
+
}
|
|
127
144
|
buildCustomComponentProps() {
|
|
128
145
|
const { disabled, readonly, name, registry, schema: rawSchema, errorSchema, fieldPathId, } = this.props;
|
|
129
146
|
const schema = rawSchema;
|
|
@@ -306,3 +306,121 @@ export const nestedArrayObjectSchema = {
|
|
|
306
306
|
},
|
|
307
307
|
},
|
|
308
308
|
};
|
|
309
|
+
export const topLevelStringCustomComponentSchema = {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
icon: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
title: 'Icon',
|
|
315
|
+
lime: { component: { name: 'limel-input-field' } },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
export const arrayItemWithDependenciesSchema = {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
entries: {
|
|
323
|
+
type: 'array',
|
|
324
|
+
title: 'Entries',
|
|
325
|
+
items: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
useA: {
|
|
329
|
+
type: 'boolean',
|
|
330
|
+
title: 'Use A',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ['useA'],
|
|
334
|
+
dependencies: {
|
|
335
|
+
useA: {
|
|
336
|
+
oneOf: [
|
|
337
|
+
{
|
|
338
|
+
properties: {
|
|
339
|
+
useA: { const: true },
|
|
340
|
+
valueA: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
title: 'Value A',
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
properties: {
|
|
348
|
+
useA: { const: false },
|
|
349
|
+
valueB: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
title: 'Value B',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
export const arrayItemWithDependenciesAndCustomConfigSchema = {
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {
|
|
365
|
+
entries: {
|
|
366
|
+
type: 'array',
|
|
367
|
+
title: 'Entries',
|
|
368
|
+
items: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
useA: {
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
title: 'Use A',
|
|
374
|
+
},
|
|
375
|
+
config: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
title: 'Config',
|
|
378
|
+
additionalProperties: { type: 'string' },
|
|
379
|
+
lime: {
|
|
380
|
+
component: { name: 'limel-input-field' },
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
required: ['useA'],
|
|
385
|
+
dependencies: {
|
|
386
|
+
useA: {
|
|
387
|
+
oneOf: [
|
|
388
|
+
{
|
|
389
|
+
properties: {
|
|
390
|
+
useA: { const: true },
|
|
391
|
+
valueA: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
title: 'Value A',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
properties: { useA: { const: false } },
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
additionalProperties: true,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
export const nestedStringCustomComponentSchema = {
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
views: {
|
|
412
|
+
type: 'array',
|
|
413
|
+
title: 'Views',
|
|
414
|
+
items: {
|
|
415
|
+
type: 'object',
|
|
416
|
+
properties: {
|
|
417
|
+
icon: {
|
|
418
|
+
type: 'string',
|
|
419
|
+
title: 'Icon',
|
|
420
|
+
lime: { component: { name: 'limel-input-field' } },
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
};
|