@byline/admin 4.1.0 → 4.3.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.
Files changed (58) hide show
  1. package/dist/fields/array/array-field.d.ts +12 -2
  2. package/dist/fields/array/array-field.js +46 -20
  3. package/dist/fields/array/array-field.module.js +5 -1
  4. package/dist/fields/array/array-field_module.css +23 -0
  5. package/dist/fields/blocks/blocks-field.js +34 -12
  6. package/dist/fields/blocks/blocks-field.module.js +2 -0
  7. package/dist/fields/blocks/blocks-field_module.css +37 -0
  8. package/dist/fields/code/code-editor.d.ts +20 -0
  9. package/dist/fields/code/code-editor.js +239 -0
  10. package/dist/fields/code/code-field.d.ts +21 -0
  11. package/dist/fields/code/code-field.js +124 -0
  12. package/dist/fields/code/code-field.module.js +7 -0
  13. package/dist/fields/code/code-field_module.css +58 -0
  14. package/dist/fields/draggable-context-menu.js +2 -1
  15. package/dist/fields/field-admin.d.ts +15 -0
  16. package/dist/fields/field-admin.js +11 -0
  17. package/dist/fields/field-admin.test.node.d.ts +8 -0
  18. package/dist/fields/field-helpers.js +1 -0
  19. package/dist/fields/field-renderer.d.ts +11 -2
  20. package/dist/fields/field-renderer.js +21 -4
  21. package/dist/fields/group/group-field.d.ts +23 -2
  22. package/dist/fields/group/group-field.js +7 -3
  23. package/dist/fields/relation/relation-picker.js +8 -2
  24. package/dist/fields/select/select-field.js +4 -2
  25. package/dist/fields/select/select-field.module.js +1 -0
  26. package/dist/fields/select/select-field_module.css +4 -0
  27. package/dist/fields/sortable-item.d.ts +6 -0
  28. package/dist/fields/sortable-item.js +44 -25
  29. package/dist/fields/text/text-field_module.css +1 -0
  30. package/dist/fields/text-area/text-area-field_module.css +1 -0
  31. package/dist/forms/form-context.js +1 -1
  32. package/dist/forms/form-renderer.d.ts +1 -1
  33. package/dist/forms/form-renderer.js +3 -1
  34. package/dist/forms/tree-placement-widget.d.ts +1 -1
  35. package/package.json +23 -10
  36. package/src/fields/array/array-field.module.css +30 -0
  37. package/src/fields/array/array-field.tsx +74 -20
  38. package/src/fields/blocks/blocks-field.module.css +53 -0
  39. package/src/fields/blocks/blocks-field.tsx +38 -1
  40. package/src/fields/code/code-editor.tsx +246 -0
  41. package/src/fields/code/code-field.module.css +84 -0
  42. package/src/fields/code/code-field.tsx +191 -0
  43. package/src/fields/draggable-context-menu.tsx +9 -1
  44. package/src/fields/field-admin.test.node.ts +49 -0
  45. package/src/fields/field-admin.ts +39 -0
  46. package/src/fields/field-helpers.ts +1 -0
  47. package/src/fields/field-renderer.tsx +33 -2
  48. package/src/fields/field-services-types.ts +1 -1
  49. package/src/fields/group/group-field.tsx +29 -2
  50. package/src/fields/relation/relation-picker.tsx +11 -0
  51. package/src/fields/select/select-field.module.css +5 -0
  52. package/src/fields/select/select-field.tsx +9 -2
  53. package/src/fields/sortable-item.tsx +115 -34
  54. package/src/fields/text/text-field.module.css +1 -0
  55. package/src/fields/text-area/text-area-field.module.css +1 -0
  56. package/src/forms/form-context.tsx +9 -1
  57. package/src/forms/form-renderer.tsx +3 -1
  58. package/src/forms/tree-placement-widget.tsx +1 -1
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { ArrayField as ArrayFieldType } from '@byline/core';
9
- export declare const ArrayField: ({ field, defaultValue, path, disableSorting, collectionPath, contentLocale, }: {
8
+ import type { ArrayField as ArrayFieldType, FieldAdminConfig } from '@byline/core';
9
+ export declare const ArrayField: ({ field, defaultValue, path, disableSorting, collectionPath, contentLocale, fieldAdmin, }: {
10
10
  field: ArrayFieldType;
11
11
  defaultValue: any;
12
12
  path: string;
@@ -24,4 +24,14 @@ export declare const ArrayField: ({ field, defaultValue, path, disableSorting, c
24
24
  * can render their locale badge.
25
25
  */
26
26
  contentLocale?: string;
27
+ /**
28
+ * Admin overrides for the array's child fields, keyed by dotted,
29
+ * index-free schema paths relative to this array ('answer',
30
+ * 'filesGroup.publicationFile'). Schema paths address declarations, so
31
+ * one entry applies to that field in every item. Arrives pre-sliced from
32
+ * the enclosing widget (`FieldRenderer` / `GroupField`); exact-name
33
+ * entries apply to the child, deeper entries are re-sliced and threaded
34
+ * on (see `sliceFieldAdmin`).
35
+ */
36
+ fieldAdmin?: Record<string, FieldAdminConfig>;
27
37
  }) => import("react").JSX.Element;
@@ -3,12 +3,13 @@ import { useEffect, useState } from "react";
3
3
  import { useTranslation } from "@byline/i18n/react";
4
4
  import { DraggableSortable, IconButton, PlusIcon, moveItem } from "@byline/ui/react";
5
5
  import classnames from "classnames";
6
+ import { sliceFieldAdmin } from "../field-admin.js";
6
7
  import { defaultScalarForField } from "../field-helpers.js";
7
8
  import { FieldRenderer } from "../field-renderer.js";
8
- import { SortableItem } from "../sortable-item.js";
9
+ import { SortableItem, StaticItem } from "../sortable-item.js";
9
10
  import { useFormContext } from "../../forms/form-context.js";
10
11
  import array_field_module from "./array-field.module.js";
11
- const ArrayField = ({ field, defaultValue, path, disableSorting = false, collectionPath, contentLocale })=>{
12
+ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collectionPath, contentLocale, fieldAdmin })=>{
12
13
  const { appendPatch, getFieldValue, getFieldValues, setFieldStore } = useFormContext();
13
14
  const { t } = useTranslation('byline-admin');
14
15
  const [items, setItems] = useState([]);
@@ -113,6 +114,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
113
114
  const initial = item[childField.name];
114
115
  if ('group' === childField.type && childField.fields && childField.fields.length > 0) {
115
116
  const groupData = initial && 'object' == typeof initial ? initial : {};
117
+ const groupAdmin = sliceFieldAdmin(fieldAdmin, childField.name);
116
118
  return /*#__PURE__*/ jsxs("div", {
117
119
  className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
118
120
  children: [
@@ -126,7 +128,10 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
126
128
  basePath: `${arrayElementPath}.${childField.name}`,
127
129
  disableSorting: true,
128
130
  collectionPath: collectionPath,
129
- contentLocale: contentLocale
131
+ contentLocale: contentLocale,
132
+ components: groupAdmin?.[innerField.name]?.components,
133
+ editor: groupAdmin?.[innerField.name]?.editor,
134
+ fieldAdmin: sliceFieldAdmin(groupAdmin, innerField.name)
130
135
  }, innerField.name))
131
136
  ]
132
137
  }, childField.name);
@@ -137,12 +142,17 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
137
142
  basePath: arrayElementPath,
138
143
  disableSorting: true,
139
144
  collectionPath: collectionPath,
140
- contentLocale: contentLocale
145
+ contentLocale: contentLocale,
146
+ components: fieldAdmin?.[childField.name]?.components,
147
+ editor: fieldAdmin?.[childField.name]?.editor,
148
+ fieldAdmin: sliceFieldAdmin(fieldAdmin, childField.name)
141
149
  }, childField.name);
142
150
  });
143
- const label = field.label ?? field.name;
144
- if (disableSorting) return /*#__PURE__*/ jsx("div", {
145
- className: classnames('byline-field-array-card', array_field_module.card),
151
+ const itemLabel = `${field.label ?? field.name} ${index + 1}`;
152
+ if (disableSorting) return /*#__PURE__*/ jsx(StaticItem, {
153
+ label: itemLabel,
154
+ onAddBelow: ()=>handleInsertBelow(index),
155
+ onRemove: ()=>handleRemoveItem(index),
146
156
  children: /*#__PURE__*/ jsx("div", {
147
157
  className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
148
158
  children: innerBody
@@ -150,7 +160,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
150
160
  }, itemWrapper.id);
151
161
  return /*#__PURE__*/ jsx(SortableItem, {
152
162
  id: itemWrapper.id,
153
- label: label,
163
+ label: itemLabel,
154
164
  onAddBelow: ()=>handleInsertBelow(index),
155
165
  onRemove: ()=>handleRemoveItem(index),
156
166
  children: /*#__PURE__*/ jsx("div", {
@@ -159,31 +169,47 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
159
169
  })
160
170
  }, itemWrapper.id);
161
171
  };
172
+ const addRow = /*#__PURE__*/ jsxs("div", {
173
+ className: classnames('byline-field-array-add-row', array_field_module["add-row"]),
174
+ children: [
175
+ /*#__PURE__*/ jsx(IconButton, {
176
+ onClick: ()=>{
177
+ handleAddItem();
178
+ },
179
+ "aria-label": t('fields.array.addItemAriaLabel'),
180
+ children: /*#__PURE__*/ jsx(PlusIcon, {})
181
+ }),
182
+ /*#__PURE__*/ jsx("button", {
183
+ type: "button",
184
+ tabIndex: -1,
185
+ onClick: ()=>{
186
+ handleAddItem();
187
+ },
188
+ className: classnames('byline-field-array-add-label', array_field_module["add-label"]),
189
+ children: t('fields.array.addItem')
190
+ })
191
+ ]
192
+ });
162
193
  return /*#__PURE__*/ jsxs("div", {
163
194
  className: `byline-field-array ${field.name}`,
164
195
  children: [
165
- !disableSorting && field.label && /*#__PURE__*/ jsx("h3", {
196
+ field.label && /*#__PURE__*/ jsx("h3", {
166
197
  className: classnames('byline-field-array-title', array_field_module.title),
167
198
  children: field.label
168
199
  }),
169
- disableSorting ? /*#__PURE__*/ jsx("div", {
200
+ disableSorting ? /*#__PURE__*/ jsxs("div", {
170
201
  className: classnames('byline-field-array-stack', array_field_module.stack),
171
- children: items.map((item, index)=>renderItem(item, index))
202
+ children: [
203
+ items.map((item, index)=>renderItem(item, index)),
204
+ addRow
205
+ ]
172
206
  }) : /*#__PURE__*/ jsxs(DraggableSortable, {
173
207
  ids: items.map((i)=>i.id),
174
208
  onDragEnd: handleDragEnd,
175
209
  className: classnames('byline-field-array-stack', array_field_module.stack),
176
210
  children: [
177
211
  items.map((item, index)=>renderItem(item, index)),
178
- /*#__PURE__*/ jsx("span", {
179
- children: /*#__PURE__*/ jsx(IconButton, {
180
- onClick: ()=>{
181
- handleAddItem();
182
- },
183
- "aria-label": t('fields.array.addItemAriaLabel'),
184
- children: /*#__PURE__*/ jsx(PlusIcon, {})
185
- })
186
- })
212
+ addRow
187
213
  ]
188
214
  })
189
215
  ]
@@ -6,6 +6,10 @@ const array_field_module = {
6
6
  "group-header": "group-header-KJxpIV",
7
7
  groupHeader: "group-header-KJxpIV",
8
8
  "group-fields": "group-fields-Bmi_hA",
9
- groupFields: "group-fields-Bmi_hA"
9
+ groupFields: "group-fields-Bmi_hA",
10
+ "add-row": "add-row-bCHtVe",
11
+ addRow: "add-row-bCHtVe",
12
+ "add-label": "add-label-KdsV2D",
13
+ addLabel: "add-label-KdsV2D"
10
14
  };
11
15
  export default array_field_module;
@@ -30,3 +30,26 @@
30
30
  display: flex;
31
31
  }
32
32
 
33
+ :is(.add-row-bCHtVe, .byline-field-array-add-row) {
34
+ color: var(--muted);
35
+ align-items: center;
36
+ gap: var(--spacing-8);
37
+ font-size: .9rem;
38
+ display: flex;
39
+ }
40
+
41
+ :is(.add-label-KdsV2D, .byline-field-array-add-label) {
42
+ appearance: none;
43
+ font: inherit;
44
+ color: inherit;
45
+ cursor: pointer;
46
+ background: none;
47
+ border: none;
48
+ padding: 0;
49
+ transition: color .12s;
50
+ }
51
+
52
+ :is(.add-row-bCHtVe:hover .add-label-KdsV2D, .byline-field-array-add-row:hover .byline-field-array-add-label) {
53
+ color: var(--text);
54
+ }
55
+
@@ -1,5 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
+ import { getClientConfig } from "@byline/core";
3
4
  import { useTranslation } from "@byline/i18n/react";
4
5
  import { Card, CloseIcon, DraggableSortable, IconButton, Modal, PlusIcon, moveItem } from "@byline/ui/react";
5
6
  import classnames from "classnames";
@@ -17,6 +18,11 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
17
18
  const availableBlocks = useMemo(()=>field.blocks ?? [], [
18
19
  field.blocks
19
20
  ]);
21
+ const blockAdminByType = useMemo(()=>{
22
+ const map = new Map();
23
+ for (const entry of getClientConfig().blockAdmin ?? [])map.set(entry.blockType, entry);
24
+ return map;
25
+ }, []);
20
26
  const [selectedBlockName, setSelectedBlockName] = useState(()=>availableBlocks[0]?.blockType ?? '');
21
27
  useEffect(()=>{
22
28
  if (null == selectedBlockName || !availableBlocks.some((b)=>b.blockType === selectedBlockName)) setSelectedBlockName(availableBlocks[0]?.blockType ?? '');
@@ -130,7 +136,9 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
130
136
  },
131
137
  defaultValue: fieldData,
132
138
  path: arrayElementPath,
133
- contentLocale: contentLocale
139
+ disableSorting: false,
140
+ contentLocale: contentLocale,
141
+ fieldAdmin: blockAdminByType.get(item._type)?.fields
134
142
  }, subField.blockType);
135
143
  return /*#__PURE__*/ jsx(SortableItem, {
136
144
  id: itemWrapper.id,
@@ -153,17 +161,31 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
153
161
  className: classnames('byline-field-blocks-stack', blocks_field_module.stack),
154
162
  children: [
155
163
  items.map((item, index)=>renderItem(item, index)),
156
- /*#__PURE__*/ jsx("div", {
164
+ /*#__PURE__*/ jsxs("div", {
157
165
  className: classnames('byline-field-blocks-add-row', blocks_field_module["add-row"]),
158
- children: /*#__PURE__*/ jsx(IconButton, {
159
- onClick: ()=>{
160
- setPendingInsertIndex(null);
161
- setShowAddBlockModal(true);
162
- },
163
- disabled: !selectedBlockName,
164
- "aria-label": t('fields.blocks.addBlockAriaLabel'),
165
- children: /*#__PURE__*/ jsx(PlusIcon, {})
166
- })
166
+ children: [
167
+ /*#__PURE__*/ jsx(IconButton, {
168
+ onClick: ()=>{
169
+ setPendingInsertIndex(null);
170
+ setShowAddBlockModal(true);
171
+ },
172
+ disabled: !selectedBlockName,
173
+ "aria-label": t('fields.blocks.addBlockAriaLabel'),
174
+ variant: "outlined",
175
+ children: /*#__PURE__*/ jsx(PlusIcon, {})
176
+ }),
177
+ /*#__PURE__*/ jsx("button", {
178
+ type: "button",
179
+ tabIndex: -1,
180
+ disabled: !selectedBlockName,
181
+ onClick: ()=>{
182
+ setPendingInsertIndex(null);
183
+ setShowAddBlockModal(true);
184
+ },
185
+ className: classnames('byline-field-blocks-add-label', blocks_field_module["add-label"]),
186
+ children: t('fields.blocks.addBlock')
187
+ })
188
+ ]
167
189
  })
168
190
  ]
169
191
  }),
@@ -176,7 +198,7 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
176
198
  },
177
199
  children: /*#__PURE__*/ jsxs(Modal.Container, {
178
200
  style: {
179
- maxWidth: '600px'
201
+ maxWidth: '700px'
180
202
  },
181
203
  children: [
182
204
  /*#__PURE__*/ jsxs(Modal.Header, {
@@ -4,6 +4,8 @@ const blocks_field_module = {
4
4
  stack: "stack-Xxa89J",
5
5
  "add-row": "add-row-gEqjM_",
6
6
  addRow: "add-row-gEqjM_",
7
+ "add-label": "add-label-jiOURu",
8
+ addLabel: "add-label-jiOURu",
7
9
  "modal-head": "modal-head-BTxR9C",
8
10
  modalHead: "modal-head-BTxR9C",
9
11
  "modal-title": "modal-title-jwOfU_",
@@ -11,11 +11,32 @@
11
11
  }
12
12
 
13
13
  :is(.add-row-gEqjM_, .byline-field-blocks-add-row) {
14
+ color: var(--muted);
14
15
  align-items: center;
15
16
  gap: var(--spacing-8);
17
+ font-size: .9rem;
16
18
  display: flex;
17
19
  }
18
20
 
21
+ :is(.add-label-jiOURu, .byline-field-blocks-add-label) {
22
+ appearance: none;
23
+ font: inherit;
24
+ color: inherit;
25
+ cursor: pointer;
26
+ background: none;
27
+ border: none;
28
+ padding: 0;
29
+ transition: color .12s;
30
+ }
31
+
32
+ :is(.add-label-jiOURu:disabled, .byline-field-blocks-add-label:disabled) {
33
+ cursor: default;
34
+ }
35
+
36
+ :is(.add-row-gEqjM_:hover .add-label-jiOURu:not(:disabled), .byline-field-blocks-add-row:hover .byline-field-blocks-add-label:not(:disabled)) {
37
+ color: var(--text);
38
+ }
39
+
19
40
  :is(.modal-head-BTxR9C, .byline-field-blocks-modal-head) {
20
41
  margin-bottom: var(--spacing-8);
21
42
  padding-top: 1rem;
@@ -28,6 +49,22 @@
28
49
 
29
50
  :is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
30
51
  cursor: pointer;
52
+ overscroll-behavior: contain;
53
+ min-height: 0;
54
+ max-height: 80dvh;
55
+ overflow-y: auto;
56
+ }
57
+
58
+ @media (min-width: 40rem) {
59
+ :is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
60
+ max-height: 70dvh;
61
+ }
62
+ }
63
+
64
+ @media (min-width: 64rem) {
65
+ :is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
66
+ max-height: 60dvh;
67
+ }
31
68
  }
32
69
 
33
70
  :is(.grid-gALL6j, .byline-field-blocks-grid) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export interface CodeEditorProps {
9
+ id?: string;
10
+ /** Initial document. External updates sync only while the editor is unfocused. */
11
+ value: string;
12
+ /** Highlight language (canonical name or alias). Unknown → plain text. */
13
+ language?: string;
14
+ readOnly?: boolean;
15
+ onChange?: (value: string) => void;
16
+ ariaInvalid?: boolean;
17
+ ariaDescribedBy?: string;
18
+ }
19
+ declare const CodeEditor: ({ id, value, language, readOnly, onChange, ariaInvalid, ariaDescribedBy, }: CodeEditorProps) => import("react").JSX.Element;
20
+ export default CodeEditor;
@@ -0,0 +1,239 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { defaultKeymap, history as commands_history, historyKeymap, indentWithTab } from "@codemirror/commands";
4
+ import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
5
+ import { Compartment, EditorState } from "@codemirror/state";
6
+ import { EditorView, keymap, lineNumbers } from "@codemirror/view";
7
+ import { tags } from "@lezer/highlight";
8
+ const LANGUAGE_LOADERS = {
9
+ javascript: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript()),
10
+ jsx: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
11
+ jsx: true
12
+ })),
13
+ typescript: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
14
+ typescript: true
15
+ })),
16
+ tsx: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
17
+ jsx: true,
18
+ typescript: true
19
+ })),
20
+ json: ()=>import("@codemirror/lang-json").then((m)=>m.json()),
21
+ html: ()=>import("@codemirror/lang-html").then((m)=>m.html()),
22
+ css: ()=>import("@codemirror/lang-css").then((m)=>m.css()),
23
+ markdown: ()=>import("@codemirror/lang-markdown").then((m)=>m.markdown()),
24
+ python: ()=>import("@codemirror/lang-python").then((m)=>m.python()),
25
+ sql: ()=>import("@codemirror/lang-sql").then((m)=>m.sql()),
26
+ yaml: ()=>import("@codemirror/lang-yaml").then((m)=>m.yaml())
27
+ };
28
+ const LANGUAGE_ALIASES = {
29
+ js: "javascript",
30
+ ts: "typescript",
31
+ md: 'markdown',
32
+ py: 'python',
33
+ yml: 'yaml'
34
+ };
35
+ const resolveLanguageLoader = (language)=>{
36
+ if (!language) return null;
37
+ const key = LANGUAGE_ALIASES[language] ?? language;
38
+ return LANGUAGE_LOADERS[key] ?? null;
39
+ };
40
+ const bylineCodeTheme = EditorView.theme({
41
+ '&': {
42
+ backgroundColor: 'var(--byline-code-bg)',
43
+ color: 'var(--byline-code-fg)',
44
+ fontSize: 'var(--byline-code-font-size, 13px)',
45
+ border: '1px solid var(--byline-code-border)',
46
+ borderRadius: 'var(--byline-code-radius, 4px)'
47
+ },
48
+ '&.cm-focused': {
49
+ outline: '2px solid var(--byline-code-focus-ring)',
50
+ outlineOffset: '-1px'
51
+ },
52
+ '.cm-content': {
53
+ fontFamily: 'var(--byline-code-font, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)',
54
+ caretColor: 'var(--byline-code-caret)',
55
+ minHeight: 'var(--byline-code-min-height, 6rem)'
56
+ },
57
+ '.cm-scroller': {
58
+ fontFamily: 'inherit',
59
+ maxHeight: 'var(--byline-code-max-height, 32rem)'
60
+ },
61
+ '.cm-gutters': {
62
+ backgroundColor: 'var(--byline-code-gutter-bg)',
63
+ color: 'var(--byline-code-gutter-fg)',
64
+ border: 'none',
65
+ borderRight: '1px solid var(--byline-code-border)'
66
+ },
67
+ '.cm-activeLine': {
68
+ backgroundColor: 'var(--byline-code-active-line)'
69
+ },
70
+ '.cm-activeLineGutter': {
71
+ backgroundColor: 'var(--byline-code-active-line)'
72
+ },
73
+ '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': {
74
+ backgroundColor: 'var(--byline-code-selection) !important'
75
+ },
76
+ '.cm-cursor': {
77
+ borderLeftColor: 'var(--byline-code-caret)'
78
+ }
79
+ });
80
+ const bylineHighlightStyle = HighlightStyle.define([
81
+ {
82
+ tag: tags.keyword,
83
+ color: 'var(--byline-code-keyword)'
84
+ },
85
+ {
86
+ tag: [
87
+ tags.string,
88
+ tags.special(tags.string)
89
+ ],
90
+ color: 'var(--byline-code-string)'
91
+ },
92
+ {
93
+ tag: tags.comment,
94
+ color: 'var(--byline-code-comment)',
95
+ fontStyle: 'italic'
96
+ },
97
+ {
98
+ tag: [
99
+ tags.number,
100
+ tags.bool,
101
+ tags["null"]
102
+ ],
103
+ color: 'var(--byline-code-number)'
104
+ },
105
+ {
106
+ tag: [
107
+ tags["function"](tags.variableName),
108
+ tags["function"](tags.propertyName)
109
+ ],
110
+ color: 'var(--byline-code-function)'
111
+ },
112
+ {
113
+ tag: [
114
+ tags.typeName,
115
+ tags.className,
116
+ tags.tagName
117
+ ],
118
+ color: 'var(--byline-code-type)'
119
+ },
120
+ {
121
+ tag: [
122
+ tags.propertyName,
123
+ tags.attributeName
124
+ ],
125
+ color: 'var(--byline-code-property)'
126
+ },
127
+ {
128
+ tag: [
129
+ tags.operator,
130
+ tags.definitionKeyword
131
+ ],
132
+ color: 'var(--byline-code-operator)'
133
+ }
134
+ ]);
135
+ const CodeEditor = ({ id, value, language, readOnly, onChange, ariaInvalid, ariaDescribedBy })=>{
136
+ const containerRef = useRef(null);
137
+ const viewRef = useRef(null);
138
+ const onChangeRef = useRef(onChange);
139
+ onChangeRef.current = onChange;
140
+ const languageCompartment = useRef(new Compartment());
141
+ const readOnlyCompartment = useRef(new Compartment());
142
+ const ariaCompartment = useRef(new Compartment());
143
+ const initialValueRef = useRef(value);
144
+ useEffect(()=>{
145
+ if (null == containerRef.current) return;
146
+ const state = EditorState.create({
147
+ doc: initialValueRef.current,
148
+ extensions: [
149
+ lineNumbers(),
150
+ commands_history(),
151
+ bracketMatching(),
152
+ keymap.of([
153
+ ...defaultKeymap,
154
+ ...historyKeymap,
155
+ indentWithTab
156
+ ]),
157
+ EditorView.lineWrapping,
158
+ bylineCodeTheme,
159
+ syntaxHighlighting(bylineHighlightStyle),
160
+ languageCompartment.current.of([]),
161
+ readOnlyCompartment.current.of([]),
162
+ ariaCompartment.current.of([]),
163
+ EditorView.updateListener.of((update)=>{
164
+ if (update.docChanged) onChangeRef.current?.(update.state.doc.toString());
165
+ })
166
+ ]
167
+ });
168
+ const view = new EditorView({
169
+ state,
170
+ parent: containerRef.current
171
+ });
172
+ viewRef.current = view;
173
+ return ()=>{
174
+ view.destroy();
175
+ viewRef.current = null;
176
+ };
177
+ }, []);
178
+ useEffect(()=>{
179
+ const view = viewRef.current;
180
+ if (null == view || view.hasFocus) return;
181
+ const current = view.state.doc.toString();
182
+ if (current !== value) view.dispatch({
183
+ changes: {
184
+ from: 0,
185
+ to: current.length,
186
+ insert: value
187
+ }
188
+ });
189
+ }, [
190
+ value
191
+ ]);
192
+ useEffect(()=>{
193
+ let cancelled = false;
194
+ const view = viewRef.current;
195
+ if (null == view) return;
196
+ const loader = resolveLanguageLoader(language);
197
+ if (null == loader) return void view.dispatch({
198
+ effects: languageCompartment.current.reconfigure([])
199
+ });
200
+ loader().then((extension)=>{
201
+ if (!cancelled && null != viewRef.current) viewRef.current.dispatch({
202
+ effects: languageCompartment.current.reconfigure(extension)
203
+ });
204
+ }).catch(()=>{});
205
+ return ()=>{
206
+ cancelled = true;
207
+ };
208
+ }, [
209
+ language
210
+ ]);
211
+ useEffect(()=>{
212
+ viewRef.current?.dispatch({
213
+ effects: readOnlyCompartment.current.reconfigure([
214
+ EditorState.readOnly.of(true === readOnly),
215
+ EditorView.editable.of(true !== readOnly)
216
+ ])
217
+ });
218
+ }, [
219
+ readOnly
220
+ ]);
221
+ useEffect(()=>{
222
+ const attributes = {};
223
+ if (ariaInvalid) attributes['aria-invalid'] = 'true';
224
+ if (ariaDescribedBy) attributes['aria-describedby'] = ariaDescribedBy;
225
+ viewRef.current?.dispatch({
226
+ effects: ariaCompartment.current.reconfigure(EditorView.contentAttributes.of(attributes))
227
+ });
228
+ }, [
229
+ ariaInvalid,
230
+ ariaDescribedBy
231
+ ]);
232
+ return /*#__PURE__*/ jsx("div", {
233
+ ref: containerRef,
234
+ id: id,
235
+ className: "byline-code-editor"
236
+ });
237
+ };
238
+ const code_editor = CodeEditor;
239
+ export default code_editor;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import React from 'react';
9
+ import type { FieldComponentSlots, CodeField as FieldType } from '@byline/core';
10
+ export declare const CodeField: ({ field, value, defaultValue, onChange, id, path, locale, components, }: {
11
+ field: FieldType;
12
+ value?: string;
13
+ defaultValue?: string;
14
+ onChange?: (value: string) => void;
15
+ id?: string;
16
+ path?: string;
17
+ /** When provided, renders a LocaleBadge next to the field label. */
18
+ locale?: string;
19
+ /** Optional UI component slot overrides from the admin config. */
20
+ components?: FieldComponentSlots;
21
+ }) => React.JSX.Element;