json-edit-react 0.9.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.
@@ -0,0 +1,876 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
5
+ var assign = require('object-property-assigner');
6
+ var extract = require('object-property-extractor');
7
+ var clone = require('just-clone');
8
+ var hi = require('react-icons/hi');
9
+ var bi = require('react-icons/bi');
10
+ var md = require('react-icons/md');
11
+ var fi = require('react-icons/fi');
12
+ var ti = require('react-icons/ti');
13
+ var fa = require('react-icons/fa');
14
+
15
+ var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
16
+
17
+ var css = ":root {\n --jer-form-border: 1px solid #ededf0;\n --jer-form-border-focus: 1px solid #e2e2e2;\n --jer-expand-transition-time: 0.2s;\n --jer-highlight-color: #b3d8ff;\n}\n.jer-editor-container {\n font-size: 1.1em;\n /* font-size: 20px; */\n padding: 1em;\n margin-top: 1em;\n margin-bottom: 1em;\n border-radius: 0.5em;\n}\n\n.jer-editor-container textarea {\n /* width: 90%; */\n color: var(--jer-input-color);\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container textarea:focus {\n border: var(--jer-form-border-focus);\n}\n\n.jer-editor-container input {\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container input:focus {\n border: var(--jer-form-border-focus);\n}\n\n/* Input highlighted text */\n.jer-editor-container ::selection {\n background-color: var(--jer-highlight-color);\n}\n\n.jer-editor-container select {\n /* color: red; */\n}\n\n.jer-component {\n}\n\n.fb-collection-component {\n}\n\n.jer-collection-header-row,\n.jer-value-main-row {\n display: flex;\n min-height: 1.7em;\n gap: 0.3em;\n align-items: center;\n}\n\n.jer-collection-header-row {\n display: flex;\n flex-wrap: wrap;\n}\n\n.jer-brackets {\n}\n\n.jer-collection-item-count {\n}\n\n.jer-collection-inner {\n position: relative;\n}\n\n.jer-collection-text-edit {\n display: flex;\n flex-direction: column;\n gap: 0.3em;\n}\n\n.jer-collection-text-area {\n resize: both;\n padding-top: 0.2em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n overflow: hidden;\n max-height: 40em;\n}\n\n.jer-collection-input-button-row {\n width: 90%;\n display: flex;\n justify-content: flex-end;\n font-size: 150%;\n}\n\n.jer-collection-element {\n}\n\n.jer-collection-error-row {\n position: absolute;\n bottom: 0;\n}\n\n.jer-collection-error-row-edit {\n}\n\n.jer-error-slug {\n margin-left: 1em;\n}\n\n.jer-value-component {\n position: relative;\n}\n\n.jer-value-main-row {\n display: flex;\n}\n\n.jer-value-error-row {\n position: absolute;\n}\n\n.jer-value-string {\n line-height: 1.3em;\n}\n\n.jer-input-text {\n resize: none;\n margin: 0;\n height: 1.4em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n min-width: 6em;\n overflow: hidden;\n max-height: 30em;\n}\n\n/* To prevent over-writing by UI libraries */\n.jer-type-select {\n border: revert;\n border-radius: revert;\n padding: revert;\n font-family: revert;\n font-size: revert;\n color: revert;\n}\n\n.jer-value-number {\n}\n\n.jer-value-boolean {\n}\n\n.jer-value-null {\n}\n\n.jer-value-object {\n}\n\n.jer-object-key {\n word-break: break-word;\n line-height: 1.1em;\n}\n\n.jer-value-array {\n}\n\n.jer-value-invalid {\n opacity: 0.5;\n font-style: italic;\n}\n\n/* .jer-input-text, */\n.jer-input-number {\n height: 1.6em;\n}\n\n.jer-input-number {\n min-width: 3em;\n}\n\n.jer-input-component {\n}\n\n.jer-edit-buttons,\n.jer-confirm-buttons {\n display: flex;\n align-items: center;\n cursor: pointer;\n}\n\n.jer-input-buttons {\n gap: 0.4em;\n}\n\n.jer-edit-buttons {\n gap: 0.4em;\n margin-left: 0.5em;\n opacity: 0;\n}\n\n.jer-confirm-buttons {\n gap: 0.2em;\n margin-left: 0.4em;\n}\n\n.jer-edit-buttons:hover {\n opacity: 1;\n position: relative;\n}\n\n.jer-collection-header-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-value-main-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-input-buttons {\n}\n\n.jer-icon-wrapper {\n height: 1.4em;\n opacity: 1;\n transition: 0.2s;\n}\n\n.jer-icon-wrapper:active {\n position: relative;\n top: 0.07em;\n}\n\n.jer-icon-wrapper:hover {\n opacity: 0.85;\n transition: 0.2s;\n transform: scale(1.2);\n}\n\n.jer-copy-pulse {\n position: relative;\n}\n\n.jer-copy-pulse:hover {\n}\n\n.jer-copy-pulse:after {\n content: '';\n display: block;\n position: absolute;\n border-radius: 50%;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n transition: all 0.5s;\n box-shadow: 0 0 15px 5px var(--jer-icon-copy-color);\n}\n\n.jer-copy-pulse:active:after {\n box-shadow: 0 0 0 0 var(--jer-icon-copy-color);\n position: absolute;\n border-radius: 4em;\n left: 0;\n top: 0;\n opacity: 1;\n transition: 0s;\n}\n\n.jer-copy-pulse:active {\n top: 0.07em;\n}\n\n.jer-rotate-90 {\n transform: rotate(-90deg);\n transition: transform var(--jer-expand-transition-time);\n}\n\n.jer-icon {\n /* font-size: '2em'; */\n}\n\n.jer-icon:hover {\n opacity: 0.85;\n transition: 0.3s;\n transform: scale(1.2);\n}\n";
18
+ n(css,{});
19
+
20
+ const defaultTheme$1 = {
21
+ displayName: 'Default',
22
+ fragments: { edit: 'rgb(42, 161, 152)' },
23
+ styles: {
24
+ container: {
25
+ backgroundColor: '#f6f6f6',
26
+ fontFamily: 'monospace',
27
+ },
28
+ property: '#292929',
29
+ bracket: { color: 'rgb(0, 43, 54)', fontWeight: 'bold' },
30
+ itemCount: { color: 'rgba(0, 0, 0, 0.3)', fontStyle: 'italic' },
31
+ string: 'rgb(203, 75, 22)',
32
+ number: 'rgb(38, 139, 210)',
33
+ boolean: 'green',
34
+ null: { color: 'rgb(220, 50, 47)', fontVariant: 'small-caps', fontWeight: 'bold' },
35
+ input: ['#292929', { fontSize: '90%' }],
36
+ inputHighlight: '#b3d8ff',
37
+ error: { fontSize: '0.8em', color: 'red', fontWeight: 'bold' },
38
+ iconCollection: 'rgb(0, 43, 54)',
39
+ iconEdit: 'edit',
40
+ iconDelete: 'rgb(203, 75, 22)',
41
+ iconAdd: 'edit',
42
+ iconCopy: 'rgb(38, 139, 210)',
43
+ iconOk: 'green',
44
+ iconCancel: 'rgb(203, 75, 22)',
45
+ },
46
+ };
47
+ const themes = {
48
+ default: defaultTheme$1,
49
+ githubDark: {
50
+ displayName: 'Github Dark',
51
+ styles: {
52
+ container: {
53
+ backgroundColor: '#0d1117',
54
+ color: 'white',
55
+ },
56
+ property: '#E6EDF3',
57
+ bracket: '#56d364',
58
+ itemCount: '#8B949E',
59
+ string: '#A5D6FF',
60
+ number: '#D2A8FF',
61
+ boolean: { color: '#FF7B72', fontSize: '90%', fontWeight: 'bold' },
62
+ null: 'green',
63
+ iconCollection: '#D2A8FF',
64
+ iconEdit: '#D2A8FF',
65
+ iconDelete: 'rgb(203, 75, 22)',
66
+ iconAdd: 'rgb(203, 75, 22)',
67
+ iconCopy: '#A5D6FF',
68
+ iconOk: '#56d364',
69
+ iconCancel: 'rgb(203, 75, 22)',
70
+ },
71
+ },
72
+ githubLight: {
73
+ displayName: 'Github Light',
74
+ styles: {
75
+ container: 'white',
76
+ property: '#1F2328',
77
+ bracket: '#00802e',
78
+ itemCount: '#8B949E',
79
+ string: '#0A3069',
80
+ number: '#953800',
81
+ boolean: { color: '#CF222E', fontSize: '90%', fontWeight: 'bold' },
82
+ null: '#FF7B72',
83
+ iconCollection: '#8250DF',
84
+ iconEdit: '#8250DF',
85
+ iconDelete: 'rgb(203, 75, 22)',
86
+ iconAdd: '#8250DF',
87
+ iconCopy: '#57606A',
88
+ },
89
+ },
90
+ monoDark: {
91
+ displayName: 'Black & White',
92
+ fragments: {
93
+ lightText: { color: 'white' },
94
+ midGrey: '#5c5c5c',
95
+ },
96
+ styles: {
97
+ container: ['lightText', { backgroundColor: 'black' }],
98
+ property: 'lightText',
99
+ bracket: 'midGrey',
100
+ itemCount: '#4a4a4a',
101
+ string: '#a8a8a8',
102
+ number: '#666666',
103
+ boolean: { color: '#848484', fontStyle: 'italic' },
104
+ null: '#333333',
105
+ iconCollection: 'midGrey',
106
+ iconEdit: 'midGrey',
107
+ iconDelete: 'midGrey',
108
+ iconAdd: 'midGrey',
109
+ iconCopy: 'midGrey',
110
+ iconOk: 'midGrey',
111
+ iconCancel: 'midGrey',
112
+ },
113
+ },
114
+ monoLight: {
115
+ fragments: { midGrey: '#a3a3a3' },
116
+ displayName: 'White & Black',
117
+ styles: {
118
+ container: 'white',
119
+ property: 'black',
120
+ bracket: 'midGrey',
121
+ itemCount: '#b5b5b5',
122
+ string: '#575757',
123
+ number: '#999999',
124
+ boolean: { color: '#7b7b7b', fontStyle: 'italic' },
125
+ null: '#cccccc',
126
+ iconCollection: 'midGrey',
127
+ iconEdit: 'midGrey',
128
+ iconDelete: 'midGrey',
129
+ iconAdd: 'midGrey',
130
+ iconCopy: 'midGrey',
131
+ iconOk: 'midGrey',
132
+ iconCancel: 'midGrey',
133
+ },
134
+ },
135
+ candyWrapper: {
136
+ displayName: 'Candy Wrapper',
137
+ fragments: {
138
+ minty: { backgroundColor: '#F1FAEE' },
139
+ pale: { color: '#A8DADC' },
140
+ mid: { color: '#457B9D' },
141
+ dark: { color: '#1D3557' },
142
+ pop: { color: '#E63946' },
143
+ darkBlue: { color: '#2B2D42' },
144
+ },
145
+ styles: {
146
+ container: 'minty',
147
+ property: 'pop',
148
+ bracket: 'dark',
149
+ itemCount: 'pale',
150
+ string: 'mid',
151
+ number: ['darkBlue', { fontSize: '85%' }],
152
+ boolean: ['mid', { fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' }],
153
+ null: ['#cccccc', { fontWeight: 'bold' }],
154
+ iconCollection: '#1D3557',
155
+ iconEdit: '#457B9D',
156
+ iconDelete: '#E63946',
157
+ iconAdd: '#2B2D42',
158
+ iconCopy: '#1D3557',
159
+ iconCancel: '#E63946',
160
+ },
161
+ },
162
+ psychedelic: {
163
+ displayName: 'Psychedelic',
164
+ fragments: {
165
+ minty: {
166
+ backgroundColor: '#F1FAEE',
167
+ },
168
+ pale: {
169
+ color: '#A8DADC',
170
+ },
171
+ mid: {
172
+ color: '#457B9D',
173
+ },
174
+ dark: {
175
+ color: '#1D3557',
176
+ },
177
+ pop: {
178
+ color: '#E63946',
179
+ },
180
+ fluroYellow: 'rgb(242, 228, 21)',
181
+ fluroGreen: 'rgb(68, 255, 62)',
182
+ hotPink: '#f7379a',
183
+ },
184
+ styles: {
185
+ container: {
186
+ backgroundColor: 'unset',
187
+ background: 'linear-gradient(90deg, hsla(333, 100%, 53%, 1) 0%, hsla(33, 94%, 57%, 1) 100%)',
188
+ color: 'black',
189
+ },
190
+ property: 'black',
191
+ bracket: 'fluroYellow',
192
+ itemCount: [
193
+ 'pale',
194
+ {
195
+ opacity: 0.7,
196
+ },
197
+ ],
198
+ string: 'white',
199
+ number: [
200
+ '#33d9ff',
201
+ {
202
+ fontSize: '90%',
203
+ fontWeight: 'bold',
204
+ },
205
+ ],
206
+ boolean: [
207
+ 'fluroGreen',
208
+ {
209
+ fontWeight: 'bold',
210
+ fontSize: '80%',
211
+ },
212
+ ],
213
+ null: [
214
+ 'black',
215
+ {
216
+ fontWeight: 'bold',
217
+ opacity: 0.3,
218
+ backgroundColor: 'rgb(255, 255, 255, 0.5)',
219
+ padding: '0 0.4em',
220
+ borderRadius: '0.4em',
221
+ },
222
+ ],
223
+ iconCollection: 'fluroYellow',
224
+ iconEdit: ['black'],
225
+ iconDelete: [
226
+ 'white',
227
+ {
228
+ opacity: 0.5,
229
+ },
230
+ ],
231
+ iconAdd: [
232
+ 'white',
233
+ {
234
+ opacity: 0.5,
235
+ },
236
+ ],
237
+ iconCopy: 'rgb(32, 84, 242)',
238
+ iconOk: 'fluroGreen',
239
+ iconCancel: 'hotPink',
240
+ },
241
+ },
242
+ };
243
+ const emptyStyleObject = {
244
+ container: {},
245
+ property: {},
246
+ bracket: {},
247
+ itemCount: {},
248
+ string: {},
249
+ number: {},
250
+ boolean: {},
251
+ null: {},
252
+ input: {},
253
+ inputHighlight: {},
254
+ error: {},
255
+ iconCollection: {},
256
+ iconEdit: {},
257
+ iconDelete: {},
258
+ iconAdd: {},
259
+ iconCopy: {},
260
+ iconOk: {},
261
+ iconCancel: {},
262
+ };
263
+
264
+ const defaultTheme = themes.default;
265
+ const initialContext = {
266
+ styles: emptyStyleObject,
267
+ setTheme: (_) => { },
268
+ icons: {},
269
+ setIcons: () => { },
270
+ };
271
+ const ThemeProviderContext = react.createContext(initialContext);
272
+ const ThemeProvider = ({ children }) => {
273
+ const [styles, setStyles] = react.useState(emptyStyleObject);
274
+ const [icons, setIcons] = react.useState({});
275
+ const setTheme = (theme) => setStyles(compileStyles(theme));
276
+ return (jsxRuntime.jsx(ThemeProviderContext.Provider, { value: { styles, setTheme, icons, setIcons }, children: children }));
277
+ };
278
+ const useTheme = () => react.useContext(ThemeProviderContext);
279
+ const compileStyles = (themeInput) => {
280
+ if (typeof themeInput === 'string')
281
+ return buildStyleObject(themes[themeInput]);
282
+ if (Array.isArray(themeInput)) {
283
+ const [name, overrides] = themeInput;
284
+ return buildStyleObject(themes[name], isStyleObject(overrides) ? { styles: overrides } : overrides);
285
+ }
286
+ return buildStyleObject(defaultTheme, isStyleObject(themeInput) ? { styles: themeInput } : themeInput);
287
+ };
288
+ const buildStyleObject = (baseTheme, overrides = { styles: emptyStyleObject }) => {
289
+ const [defaultStyles, baseStyles, overrideStyles] = [defaultTheme, baseTheme, overrides].map((theme) => {
290
+ const { fragments, styles } = theme;
291
+ const compiledStyles = {};
292
+ Object.entries(styles).forEach(([key, value]) => {
293
+ const elements = Array.isArray(value) ? value : [value];
294
+ const cssStyles = elements.reduce((acc, curr) => {
295
+ if (typeof curr === 'string') {
296
+ const style = fragments?.[curr] ?? curr;
297
+ switch (typeof style) {
298
+ case 'string':
299
+ return { ...acc, [defaultStyleProperties[key]]: style };
300
+ default:
301
+ return { ...acc, ...style };
302
+ }
303
+ }
304
+ else
305
+ return { ...acc, ...curr };
306
+ }, {});
307
+ compiledStyles[key] = cssStyles;
308
+ });
309
+ return compiledStyles;
310
+ });
311
+ const finalStyles = {};
312
+ Object.keys(defaultTheme.styles).forEach((key) => {
313
+ finalStyles[key] = {
314
+ ...defaultStyles[key],
315
+ ...baseStyles[key],
316
+ ...overrideStyles[key],
317
+ };
318
+ });
319
+ if (finalStyles?.inputHighlight?.backgroundColor)
320
+ document.documentElement.style.setProperty('--jer-highlight-color', finalStyles?.inputHighlight?.backgroundColor);
321
+ if (finalStyles?.iconCopy?.color)
322
+ document.documentElement.style.setProperty('--jer-icon-copy-color', finalStyles?.iconCopy?.color);
323
+ return finalStyles;
324
+ };
325
+ const isStyleObject = (overrideObject) => {
326
+ return !('styles' in overrideObject);
327
+ };
328
+ const defaultStyleProperties = {
329
+ container: 'backgroundColor',
330
+ property: 'color',
331
+ bracket: 'color',
332
+ itemCount: 'color',
333
+ string: 'color',
334
+ number: 'color',
335
+ boolean: 'color',
336
+ null: 'color',
337
+ input: 'color',
338
+ inputHighlight: 'backgroundColor',
339
+ error: 'color',
340
+ iconCollection: 'color',
341
+ iconEdit: 'color',
342
+ iconDelete: 'color',
343
+ iconAdd: 'color',
344
+ iconCopy: 'color',
345
+ iconOk: 'color',
346
+ iconCancel: 'color',
347
+ };
348
+
349
+ const AutogrowTextArea = ({ className, name, value, setValue, handleKeyPress, }) => {
350
+ const { styles } = useTheme();
351
+ const dummyValue = value.slice(-1) === '\n' ? value + '.' : value;
352
+ return (jsxRuntime.jsxs("div", { style: { display: 'grid' }, children: [jsxRuntime.jsx("textarea", { style: {
353
+ height: 'auto',
354
+ gridArea: '1 / 1 / 2 / 2',
355
+ overflowY: 'auto',
356
+ ...styles.input,
357
+ }, rows: 1, className: className, name: name, value: value, onChange: (e) => setValue(e.target.value), autoFocus: true, onFocus: (e) => {
358
+ if (value.length < 40)
359
+ e.target.select();
360
+ }, onKeyDown: handleKeyPress }), jsxRuntime.jsx("span", { className: className, style: {
361
+ visibility: 'hidden',
362
+ height: 'auto',
363
+ gridArea: '1 / 1 / 2 / 2',
364
+ color: 'red',
365
+ opacity: 0.9,
366
+ whiteSpace: 'pre-wrap',
367
+ overflow: 'clip',
368
+ border: '1px solid transparent',
369
+ ...styles.input,
370
+ }, children: dummyValue })] }));
371
+ };
372
+
373
+ const truncate = (string, length = 200) => string.length < length ? string : `${string.slice(0, length - 2).trim()}...`;
374
+ const StringValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, stringTruncate, }) => {
375
+ const { styles } = useTheme();
376
+ const handleKeyPress = (e) => {
377
+ if (e.key === 'Enter' && !e.shiftKey)
378
+ handleEdit();
379
+ else if (e.key === 'Escape')
380
+ handleCancel();
381
+ };
382
+ const breakString = (text) => text.split('\n').map((line, index, arr) => (jsxRuntime.jsxs("span", { children: [line, index < arr.length - 1 ? jsxRuntime.jsx("br", {}) : null] }, index)));
383
+ return isEditing ? (jsxRuntime.jsx(AutogrowTextArea, { className: "jer-input-text", name: path.join('.'), value: value, setValue: setValue, isEditing: isEditing, handleKeyPress: handleKeyPress })) : (jsxRuntime.jsxs("div", { onDoubleClick: () => setIsEditing(true), onClick: (e) => {
384
+ if (e.getModifierState('Control') || e.getModifierState('Meta'))
385
+ setIsEditing(true);
386
+ }, className: "jer-value-string", style: styles.string, children: ["\"", breakString(truncate(value, stringTruncate)), "\""] }));
387
+ };
388
+ const NumberValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, }) => {
389
+ const { styles } = useTheme();
390
+ const handleKeyPress = (e) => {
391
+ if (e.key === 'Enter')
392
+ handleEdit();
393
+ else if (e.key === 'Escape')
394
+ handleCancel();
395
+ };
396
+ const validateNumber = (input) => {
397
+ return input.replace(/[^0-9.-]/g, '');
398
+ };
399
+ return isEditing ? (jsxRuntime.jsx("input", { className: "jer-input-number", type: "text", name: path.join('.'), value: value, onChange: (e) => setValue(validateNumber(e.target.value)), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { width: `${String(value).length / 1.5 + 2}em` } })) : (jsxRuntime.jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-number", style: styles.number, children: value }));
400
+ };
401
+ const BooleanValue = ({ value, setValue, isEditing, path, setIsEditing, }) => {
402
+ const { styles } = useTheme();
403
+ return isEditing ? (jsxRuntime.jsx("input", { className: "jer-input-boolean", type: "checkbox", name: path.join('.'), checked: value, onChange: () => setValue(!value) })) : (jsxRuntime.jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-boolean", style: styles.boolean, children: String(value) }));
404
+ };
405
+ const NullValue = ({ value, isEditing, setIsEditing }) => {
406
+ const { styles } = useTheme();
407
+ return isEditing ? (jsxRuntime.jsx("div", { className: "jer-input-null", children: "null" })) : (jsxRuntime.jsx("div", { onDoubleClick: () => setIsEditing(true), className: "jer-value-null", style: styles.null, children: String(value) }));
408
+ };
409
+ const ObjectValue = () => {
410
+ return jsxRuntime.jsx("span", { className: "jer-value-object", children: '{ }' });
411
+ };
412
+ const ArrayValue = ({ value }) => {
413
+ return jsxRuntime.jsx("span", { className: "jer-value-array", children: `[${value === null ? '' : value}]` });
414
+ };
415
+ const InvalidValue = () => {
416
+ return jsxRuntime.jsx("span", { className: "jer-value-invalid", children: "Invalid value" });
417
+ };
418
+
419
+ const Icon = ({ name, rotate }) => {
420
+ const { styles, icons } = useTheme();
421
+ const commonProps = { size: '1.4em', className: 'jer-icon' };
422
+ switch (name) {
423
+ case 'add':
424
+ return icons?.add ?? jsxRuntime.jsx(bi.BiPlusCircle, { ...commonProps, style: styles.iconAdd });
425
+ case 'edit':
426
+ return icons?.edit ?? jsxRuntime.jsx(bi.BiEdit, { ...commonProps, style: styles.iconEdit });
427
+ case 'delete':
428
+ return (icons?.delete ?? jsxRuntime.jsx(md.MdDeleteForever, { ...commonProps, style: styles.iconDelete, size: "1.5em" }));
429
+ case 'copy':
430
+ return icons?.copy ?? jsxRuntime.jsx(hi.HiOutlineClipboardCopy, { ...commonProps, style: styles.iconCopy });
431
+ case 'ok':
432
+ return (icons?.ok ?? (jsxRuntime.jsx(fi.FiCheckCircle, { ...commonProps, style: { fontSize: '90%', ...styles.iconOk } })));
433
+ case 'cancel':
434
+ return (icons?.cancel ?? (jsxRuntime.jsx(ti.TiCancel, { ...commonProps, style: { fontSize: '130%', ...styles.iconCancel } })));
435
+ case 'chevron':
436
+ return (icons?.chevron ?? (jsxRuntime.jsx(fa.FaChevronDown, { className: `${rotate ? ' jer-rotate-90' : ''}`, style: styles.iconCollection })));
437
+ default:
438
+ return jsxRuntime.jsx(jsxRuntime.Fragment, {});
439
+ }
440
+ };
441
+
442
+ const EditButtons = ({ startEdit, handleDelete, handleAdd, enableClipboard, type, data, path, name, translate, }) => {
443
+ const { styles } = useTheme();
444
+ const NEW_KEY_PROMPT = translate('KEY_NEW');
445
+ const [isAdding, setIsAdding] = react.useState(false);
446
+ const [newKey, setNewKey] = react.useState(NEW_KEY_PROMPT);
447
+ react.useEffect(() => {
448
+ if (!isAdding)
449
+ setNewKey(NEW_KEY_PROMPT);
450
+ }, [isAdding]);
451
+ const handleKeyPress = (e) => {
452
+ if (e.key === 'Enter' && handleAdd) {
453
+ setIsAdding(false);
454
+ handleAdd(newKey);
455
+ }
456
+ else if (e.key === 'Escape')
457
+ setIsAdding(false);
458
+ };
459
+ const handleCopy = (e) => {
460
+ let type = 'value';
461
+ let value;
462
+ if (enableClipboard) {
463
+ switch (e.ctrlKey || e.metaKey) {
464
+ case true:
465
+ value = stringifyPath(path);
466
+ type = 'path';
467
+ break;
468
+ case false:
469
+ value = JSON.stringify(data, null, 2);
470
+ }
471
+ navigator.clipboard.writeText(value);
472
+ }
473
+ if (typeof enableClipboard === 'function')
474
+ enableClipboard({ value, path, key: name, type });
475
+ };
476
+ return (jsxRuntime.jsxs("div", { className: "jer-edit-buttons", style: isAdding ? { opacity: 1 } : undefined, children: [enableClipboard && (jsxRuntime.jsx("div", { onClick: handleCopy, className: "jer-copy-pulse", children: jsxRuntime.jsx(Icon, { name: "copy" }) })), startEdit && (jsxRuntime.jsx("div", { onClick: startEdit, children: jsxRuntime.jsx(Icon, { name: "edit" }) })), handleDelete && (jsxRuntime.jsx("div", { onClick: handleDelete, children: jsxRuntime.jsx(Icon, { name: "delete" }) })), handleAdd && (jsxRuntime.jsx("div", { onClick: () => {
477
+ if (type === 'object')
478
+ setIsAdding(true);
479
+ else
480
+ handleAdd('');
481
+ }, children: jsxRuntime.jsx(Icon, { name: "add" }) })), isAdding && handleAdd && type === 'object' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { className: "jer-input-new-key", type: "text", name: "new-object-key", value: newKey, onChange: (e) => setNewKey(e.target.value), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { ...styles.input } }), jsxRuntime.jsx(InputButtons, { onOk: () => {
482
+ if (!!newKey) {
483
+ setIsAdding(false);
484
+ handleAdd(newKey);
485
+ }
486
+ }, onCancel: () => {
487
+ setIsAdding(false);
488
+ } })] }))] }));
489
+ };
490
+ const InputButtons = ({ onOk, onCancel }) => {
491
+ return (jsxRuntime.jsxs("div", { className: "jer-confirm-buttons", children: [jsxRuntime.jsx("div", { onClick: onOk, children: jsxRuntime.jsx(Icon, { name: "ok" }) }), jsxRuntime.jsx("div", { onClick: onCancel, children: jsxRuntime.jsx(Icon, { name: "cancel" }) })] }));
492
+ };
493
+ const stringifyPath = (path) => path.reduce((str, part) => {
494
+ if (typeof part === 'number')
495
+ return `${str}[${part}]`;
496
+ else
497
+ return str === '' ? part : `${str}.${part}`;
498
+ }, '');
499
+
500
+ const ERROR_DISPLAY_TIME = 2500;
501
+ const ValueDataTypes = ['string', 'number', 'boolean', 'null'];
502
+ const CollectionDataTypes = ['object', 'array'];
503
+ const DataTypes = [...ValueDataTypes, ...CollectionDataTypes];
504
+
505
+ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard, restrictEditFilter, restrictDeleteFilter, showArrayIndices, stringTruncate, indent, translate, }) => {
506
+ const { styles } = useTheme();
507
+ const [isEditing, setIsEditing] = react.useState(false);
508
+ const [value, setValue] = react.useState(data);
509
+ const [error, setError] = react.useState(null);
510
+ const [dataType, setDataType] = react.useState(getDataType(data));
511
+ react.useEffect(() => {
512
+ setValue(data);
513
+ setDataType(getDataType(data));
514
+ }, [data, error]);
515
+ const handleChangeDataType = (type) => {
516
+ setValue(convertValue(value, type));
517
+ setDataType(type);
518
+ };
519
+ const logError = (errorString) => {
520
+ setError(errorString);
521
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
522
+ console.log('Error', errorString);
523
+ };
524
+ const handleEdit = () => {
525
+ setIsEditing(false);
526
+ let newValue;
527
+ switch (dataType) {
528
+ case 'object':
529
+ newValue = {};
530
+ break;
531
+ case 'array':
532
+ newValue = value !== null ? [value] : [];
533
+ break;
534
+ case 'number':
535
+ const n = Number(value);
536
+ if (isNaN(n))
537
+ newValue = 0;
538
+ else
539
+ newValue = n;
540
+ break;
541
+ default:
542
+ newValue = value;
543
+ }
544
+ onEdit(newValue, path).then((error) => {
545
+ if (error)
546
+ logError(error);
547
+ });
548
+ };
549
+ const handleCancel = () => {
550
+ setIsEditing(false);
551
+ setValue(data);
552
+ };
553
+ const handleDelete = () => {
554
+ onDelete(value, path).then((error) => {
555
+ if (error)
556
+ logError(error);
557
+ });
558
+ };
559
+ const filterProps = { key: name, path, level: path.length, value: data, size: null };
560
+ const canEdit = react.useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
561
+ const canDelete = react.useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
562
+ const inputProps = {
563
+ value,
564
+ setValue,
565
+ isEditing,
566
+ setIsEditing,
567
+ handleEdit,
568
+ handleCancel,
569
+ path,
570
+ stringTruncate,
571
+ };
572
+ return (jsxRuntime.jsx("div", { className: "jer-component jer-value-component", style: { marginLeft: `${indent / 2}em` }, children: jsxRuntime.jsxs("div", { className: "jer-value-main-row", children: [showArrayIndices && (jsxRuntime.jsxs("label", { htmlFor: path.join('.'), className: "jer-object-key", style: {
573
+ ...styles.property,
574
+ minWidth: `${Math.min(String(name).length + 1, 5)}ch`,
575
+ }, children: [name, ":", ' '] })), jsxRuntime.jsx("div", { className: "jer-input-component", children: getInputComponent(dataType, inputProps) }), isEditing ? (jsxRuntime.jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel })) : (dataType !== 'invalid' &&
576
+ !error && (jsxRuntime.jsx(EditButtons, { startEdit: canEdit ? () => setIsEditing(true) : undefined, handleDelete: canDelete ? handleDelete : undefined, data: data, enableClipboard: enableClipboard, name: name, path: path, translate: translate }))), isEditing && (jsxRuntime.jsx("select", { name: `${name}-type-select`, className: "jer-type-select", onChange: (e) => handleChangeDataType(e.target.value), value: dataType, children: DataTypes.map((type) => (jsxRuntime.jsx("option", { value: type, children: type }, type))) })), !isEditing && error && (jsxRuntime.jsx("span", { className: "jer-error-slug", style: styles.error, children: error }))] }) }));
577
+ };
578
+ const getDataType = (value) => {
579
+ if (typeof value === 'string')
580
+ return 'string';
581
+ if (typeof value === 'number')
582
+ return 'number';
583
+ if (typeof value === 'boolean')
584
+ return 'boolean';
585
+ if (value === null)
586
+ return 'null';
587
+ return 'invalid';
588
+ };
589
+ const getInputComponent = (dataType, inputProps) => {
590
+ const value = inputProps.value;
591
+ switch (dataType) {
592
+ case 'string':
593
+ return jsxRuntime.jsx(StringValue, { ...inputProps, value: value });
594
+ case 'number':
595
+ return jsxRuntime.jsx(NumberValue, { ...inputProps, value: value });
596
+ case 'boolean':
597
+ return jsxRuntime.jsx(BooleanValue, { ...inputProps, value: value });
598
+ case 'null':
599
+ return jsxRuntime.jsx(NullValue, { ...inputProps });
600
+ case 'object':
601
+ return jsxRuntime.jsx(ObjectValue, { ...inputProps });
602
+ case 'array':
603
+ return jsxRuntime.jsx(ArrayValue, { ...inputProps });
604
+ default:
605
+ return jsxRuntime.jsx(InvalidValue, { ...inputProps });
606
+ }
607
+ };
608
+ const convertValue = (value, type) => {
609
+ switch (type) {
610
+ case 'string':
611
+ return String(value);
612
+ case 'number':
613
+ const n = Number(value);
614
+ return isNaN(n) ? 0 : n;
615
+ case 'boolean':
616
+ return !!value;
617
+ case 'null':
618
+ return null;
619
+ case 'object':
620
+ return {};
621
+ case 'array':
622
+ return [value];
623
+ default:
624
+ return String(value);
625
+ }
626
+ };
627
+
628
+ const isCollection = (value) => value !== null && typeof value == 'object';
629
+ const CollectionNode = ({ data, path, name, ...props }) => {
630
+ const { styles } = useTheme();
631
+ const { onEdit, onAdd, onDelete, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, collapseFilter, enableClipboard, indent, keySort, showArrayIndices, defaultValue, translate, } = props;
632
+ const [isEditing, setIsEditing] = react.useState(false);
633
+ const [stringifiedValue, setStringifiedValue] = react.useState(JSON.stringify(data, null, 2));
634
+ const [error, setError] = react.useState(null);
635
+ const collectionSize = Object.keys(data).length;
636
+ const filterProps = { key: name, path, level: path.length, value: data, size: collectionSize };
637
+ const [collapsed, setCollapsed] = react.useState(collapseFilter(filterProps));
638
+ react.useEffect(() => {
639
+ setStringifiedValue(JSON.stringify(data, null, 2));
640
+ }, [data]);
641
+ react.useEffect(() => {
642
+ setCollapsed(collapseFilter(filterProps));
643
+ }, [collapseFilter]);
644
+ const collectionType = Array.isArray(data) ? 'array' : 'object';
645
+ const brackets = collectionType === 'array' ? { open: '[', close: ']' } : { open: '{', close: '}' };
646
+ const transitionTime = getComputedStyle(document.documentElement).getPropertyValue('--jer-expand-transition-time');
647
+ const handleKeyPress = (e) => {
648
+ if (e.key === 'Enter' && (e.metaKey || e.shiftKey || e.ctrlKey))
649
+ handleEdit();
650
+ else if (e.key === 'Escape')
651
+ handleCancel();
652
+ };
653
+ const showError = (errorString) => {
654
+ setError(errorString);
655
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
656
+ console.log('Error', errorString);
657
+ };
658
+ const handleEdit = () => {
659
+ try {
660
+ const value = JSON.parse(stringifiedValue);
661
+ setIsEditing(false);
662
+ setError(null);
663
+ onEdit(value, path).then((error) => {
664
+ if (error)
665
+ showError(error);
666
+ });
667
+ }
668
+ catch {
669
+ setError(translate('ERROR_INVALID_JSON'));
670
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
671
+ console.log('Invalid JSON');
672
+ return;
673
+ }
674
+ };
675
+ const handleAdd = (key) => {
676
+ setCollapsed(false);
677
+ if (collectionType === 'array') {
678
+ onAdd(defaultValue, [...path, data.length]).then((error) => {
679
+ if (error)
680
+ showError(error);
681
+ });
682
+ }
683
+ else if (key in data) {
684
+ showError(translate('ERROR_KEY_EXISTS'));
685
+ return;
686
+ }
687
+ else
688
+ onAdd(defaultValue, [...path, key]).then((error) => {
689
+ if (error)
690
+ showError(error);
691
+ });
692
+ };
693
+ const handleDelete = path.length > 0
694
+ ? () => {
695
+ onDelete(data, path).then((result) => {
696
+ if (result)
697
+ showError(result);
698
+ });
699
+ }
700
+ : undefined;
701
+ const handleCancel = () => {
702
+ setIsEditing(false);
703
+ setError(null);
704
+ setStringifiedValue(JSON.stringify(data, null, 2));
705
+ };
706
+ const canEdit = react.useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
707
+ const canDelete = react.useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
708
+ const canAdd = react.useMemo(() => !restrictAddFilter(filterProps), [filterProps]);
709
+ const showLabel = showArrayIndices || !(typeof path.slice(-1)[0] === 'number');
710
+ const keyValueArray = Object.entries(data).map(([key, value]) => [
711
+ collectionType === 'array' ? Number(key) : key,
712
+ value,
713
+ ]);
714
+ if (keySort && collectionType === 'object')
715
+ keyValueArray.sort(typeof keySort === 'function' ? (a, b) => keySort(a[0], b[0]) : undefined);
716
+ const numOfLines = JSON.stringify(data, null, 2).split('\n').length;
717
+ return (jsxRuntime.jsxs("div", { className: "jer-component fb-collection-component", style: { marginLeft: `${path.length === 0 ? 0 : indent / 2}em` }, children: [jsxRuntime.jsxs("div", { className: "jer-collection-header-row", children: [jsxRuntime.jsx("div", { onClick: () => {
718
+ if (!isEditing)
719
+ setCollapsed(!collapsed);
720
+ }, children: jsxRuntime.jsx(Icon, { name: "chevron", rotate: collapsed }) }), jsxRuntime.jsxs("div", { className: "jer-collection-name", children: [jsxRuntime.jsx("span", { style: styles.property, children: showLabel ? `${name}:` : null }), jsxRuntime.jsx("span", { className: "jer-brackets", style: styles.bracket, children: brackets.open })] }), jsxRuntime.jsx("div", { className: "jer-collection-item-count", style: styles.itemCount, children: collectionSize === 1
721
+ ? translate('ITEM_SINGLE', 1)
722
+ : translate('ITEMS_MULTIPLE', collectionSize) }), collapsed && (jsxRuntime.jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close })), !isEditing && (jsxRuntime.jsx(EditButtons, { startEdit: canEdit
723
+ ? () => {
724
+ setIsEditing(true);
725
+ setCollapsed(false);
726
+ }
727
+ : undefined, handleAdd: canAdd ? handleAdd : undefined, handleDelete: canDelete ? handleDelete : undefined, enableClipboard: enableClipboard, type: collectionType, data: data, name: name, path: path, translate: translate }))] }), jsxRuntime.jsxs("div", { className: 'jer-collection-inner', style: {
728
+ maxHeight: collapsed ? 0 : `${numOfLines * 1.6}em`,
729
+ overflowY: collapsed ? 'hidden' : 'visible',
730
+ transition: `max-height ${transitionTime}`,
731
+ }, children: [isEditing ? (jsxRuntime.jsxs("div", { className: "jer-collection-text-edit", children: [jsxRuntime.jsx(AutogrowTextArea, { className: "jer-collection-text-area", name: path.join('.'), value: stringifiedValue, setValue: setStringifiedValue, isEditing: isEditing, handleKeyPress: handleKeyPress }), jsxRuntime.jsx("div", { className: "jer-collection-input-button-row", children: jsxRuntime.jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel, isCollection: true }) })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: keyValueArray.map(([key, value]) => (jsxRuntime.jsx("div", { className: "jer-collection-element", children: isCollection(value) ? (jsxRuntime.jsx(CollectionNode, { data: value, path: [...path, key], name: key, ...props }, key)) : (jsxRuntime.jsx(ValueNodeWrapper, { data: value, path: [...path, key], name: key, ...props, showArrayIndices: collectionType === 'object' ? true : showArrayIndices }, key)) }, key))) })), jsxRuntime.jsx("div", { className: isEditing ? 'jer-collection-error-row' : 'jer-collection-error-row-edit', children: error && (jsxRuntime.jsx("span", { className: "jer-error-slug", style: styles.error, children: error })) })] }), !collapsed && (jsxRuntime.jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close }))] }));
732
+ };
733
+
734
+ const localisedStrings = {
735
+ ITEM_SINGLE: '{{count}} item',
736
+ ITEMS_MULTIPLE: '{{count}} items',
737
+ KEY_NEW: 'Enter new key',
738
+ ERROR_KEY_EXISTS: 'Key already exists',
739
+ ERROR_INVALID_JSON: 'Invalid JSON',
740
+ ERROR_UPDATE: 'Update unsuccessful',
741
+ ERROR_DELETE: 'Delete unsuccessful',
742
+ ERROR_ADD: 'Adding node unsuccessful',
743
+ };
744
+ const translate = (translations, key, count) => {
745
+ const string = key in translations ? translations[key] : localisedStrings[key];
746
+ return count === undefined ? string : string?.replace('{{count}}', String(count));
747
+ };
748
+ const getTranslateFunction = (translations) => (key, count) => translate(translations, key, count);
749
+
750
+ const Editor = ({ data: srcData, rootName = 'root', onUpdate, onEdit: srcEdit = onUpdate, onDelete: srcDelete = onUpdate, onAdd: srcAdd = onUpdate, enableClipboard = true, theme = 'default', icons, indent = 4, collapse = false, restrictEdit = false, restrictDelete = false, restrictAdd = false, keySort = false, showArrayIndices = true, defaultValue = null, minWidth = 250, maxWidth = 600, stringTruncate = 250, translations = {}, className, }) => {
751
+ const [data, setData] = react.useState(srcData);
752
+ const { styles, setTheme, setIcons } = useTheme();
753
+ const collapseFilter = react.useCallback(getFilterFunction(collapse), [collapse]);
754
+ const translate = react.useCallback(getTranslateFunction(translations), [translations]);
755
+ react.useEffect(() => {
756
+ setData(srcData);
757
+ }, [srcData]);
758
+ react.useEffect(() => {
759
+ if (theme)
760
+ setTheme(theme);
761
+ if (icons)
762
+ setIcons(icons);
763
+ }, [theme, icons]);
764
+ const onEdit = async (value, path) => {
765
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'update');
766
+ if (srcEdit) {
767
+ const result = await srcEdit({
768
+ currentData,
769
+ newData,
770
+ currentValue,
771
+ newValue,
772
+ name: path.slice(-1)[0],
773
+ path,
774
+ });
775
+ if (result === undefined)
776
+ setData(newData);
777
+ if (result === false)
778
+ return translate('ERROR_UPDATE');
779
+ return result;
780
+ }
781
+ else
782
+ setData(newData);
783
+ };
784
+ const onDelete = async (value, path) => {
785
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'delete');
786
+ if (srcDelete) {
787
+ const result = await srcDelete({
788
+ currentData,
789
+ newData,
790
+ currentValue,
791
+ newValue,
792
+ name: path.slice(-1)[0],
793
+ path,
794
+ });
795
+ if (result === undefined)
796
+ setData(newData);
797
+ if (result === false)
798
+ return translate('ERROR_DELETE');
799
+ return result;
800
+ }
801
+ else
802
+ setData(newData);
803
+ };
804
+ const onAdd = async (value, path) => {
805
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'add');
806
+ if (srcAdd) {
807
+ const result = await srcAdd({
808
+ currentData,
809
+ newData,
810
+ currentValue,
811
+ newValue,
812
+ name: path.slice(-1)[0],
813
+ path,
814
+ });
815
+ if (result === undefined)
816
+ setData(newData);
817
+ if (result === false)
818
+ return translate('ERROR_ADD');
819
+ return result;
820
+ }
821
+ else
822
+ setData(newData);
823
+ };
824
+ const restrictEditFilter = getFilterFunction(restrictEdit);
825
+ const restrictDeleteFilter = getFilterFunction(restrictDelete);
826
+ const restrictAddFilter = getFilterFunction(restrictAdd);
827
+ const otherProps = {
828
+ name: rootName,
829
+ onEdit,
830
+ onDelete,
831
+ onAdd,
832
+ collapseFilter,
833
+ restrictEditFilter,
834
+ restrictDeleteFilter,
835
+ restrictAddFilter,
836
+ enableClipboard,
837
+ keySort,
838
+ showArrayIndices,
839
+ indent,
840
+ defaultValue,
841
+ stringTruncate,
842
+ translate,
843
+ };
844
+ if (!styles)
845
+ return null;
846
+ return (jsxRuntime.jsx("div", { className: 'jer-editor-container ' + className, style: { ...styles.container, minWidth, maxWidth }, children: isCollection(data) && jsxRuntime.jsx(CollectionNode, { data: data, path: [], ...otherProps }) }));
847
+ };
848
+ const JsonEditor = (props) => (jsxRuntime.jsx(ThemeProvider, { children: jsxRuntime.jsx(Editor, { ...props }) }));
849
+ const updateDataObject = (data, path, newValue, action) => {
850
+ if (path.length === 0) {
851
+ return {
852
+ currentData: data,
853
+ newData: newValue,
854
+ currentValue: data,
855
+ newValue: newValue,
856
+ };
857
+ }
858
+ const currentValue = action !== 'add' ? extract(data, path) : undefined;
859
+ const newData = assign(clone(data), path, newValue, { remove: action === 'delete' });
860
+ return {
861
+ currentData: data,
862
+ newData,
863
+ currentValue,
864
+ newValue: action === 'update' ? newValue : undefined,
865
+ };
866
+ };
867
+ const getFilterFunction = (propValue) => {
868
+ if (typeof propValue === 'boolean')
869
+ return () => propValue;
870
+ if (typeof propValue === 'number')
871
+ return ({ level }) => level >= propValue;
872
+ return propValue;
873
+ };
874
+
875
+ exports.JsonEditor = JsonEditor;
876
+ exports.themes = themes;