@gooddata/sdk-ui-kit 11.52.0 → 11.53.0-alpha.1

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 (67) hide show
  1. package/esm/@ui/UiConfigEditor/ConfigEditorToolbar.d.ts +26 -0
  2. package/esm/@ui/UiConfigEditor/ConfigEditorToolbar.d.ts.map +1 -0
  3. package/esm/@ui/UiConfigEditor/ConfigEditorToolbar.js +38 -0
  4. package/esm/@ui/UiConfigEditor/UiConfigEditor.d.ts +20 -0
  5. package/esm/@ui/UiConfigEditor/UiConfigEditor.d.ts.map +1 -0
  6. package/esm/@ui/UiConfigEditor/UiConfigEditor.js +229 -0
  7. package/esm/@ui/UiConfigEditor/configEditorGrammars.d.ts +15 -0
  8. package/esm/@ui/UiConfigEditor/configEditorGrammars.d.ts.map +1 -0
  9. package/esm/@ui/UiConfigEditor/configEditorGrammars.js +50 -0
  10. package/esm/@ui/UiConfigEditor/configEditorHighlighting.d.ts +6 -0
  11. package/esm/@ui/UiConfigEditor/configEditorHighlighting.d.ts.map +1 -0
  12. package/esm/@ui/UiConfigEditor/configEditorHighlighting.js +31 -0
  13. package/esm/@ui/UiConfigEditor/configEditorLanguage.d.ts +43 -0
  14. package/esm/@ui/UiConfigEditor/configEditorLanguage.d.ts.map +1 -0
  15. package/esm/@ui/UiConfigEditor/configEditorLanguage.js +179 -0
  16. package/esm/@ui/UiConfigEditor/types.d.ts +135 -0
  17. package/esm/@ui/UiConfigEditor/types.d.ts.map +1 -0
  18. package/esm/@ui/UiConfigEditor/types.js +2 -0
  19. package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuHeader.d.ts.map +1 -1
  20. package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuHeader.js +5 -2
  21. package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.d.ts +2 -2
  22. package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.d.ts.map +1 -1
  23. package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.js +1 -0
  24. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.d.ts +13 -0
  25. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.d.ts.map +1 -1
  26. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.js +19 -4
  27. package/esm/List/DateDatasetsListItem.d.ts.map +1 -1
  28. package/esm/List/DateDatasetsListItem.js +1 -2
  29. package/esm/index.d.ts +5 -1
  30. package/esm/index.d.ts.map +1 -1
  31. package/esm/index.js +2 -1
  32. package/esm/sdk-ui-kit.d.ts +204 -36
  33. package/esm/syntaxHighlightingInput/SyntaxHighlightingInput.d.ts +7 -1
  34. package/esm/syntaxHighlightingInput/SyntaxHighlightingInput.d.ts.map +1 -1
  35. package/esm/syntaxHighlightingInput/SyntaxHighlightingInput.js +2 -1
  36. package/esm/syntaxHighlightingInput/hooks/useChangeHandler.d.ts.map +1 -1
  37. package/esm/syntaxHighlightingInput/hooks/useChangeHandler.js +9 -2
  38. package/esm/syntaxHighlightingInput/hooks/useCodemirror.d.ts +4 -1
  39. package/esm/syntaxHighlightingInput/hooks/useCodemirror.d.ts.map +1 -1
  40. package/esm/syntaxHighlightingInput/hooks/useCodemirror.js +2 -2
  41. package/esm/syntaxHighlightingInput/hooks/useCodemirrorChange.d.ts +12 -1
  42. package/esm/syntaxHighlightingInput/hooks/useCodemirrorChange.d.ts.map +1 -1
  43. package/esm/syntaxHighlightingInput/hooks/useCodemirrorChange.js +10 -8
  44. package/esm/syntaxHighlightingInput/hooks/useCodemirrorEditable.d.ts.map +1 -1
  45. package/esm/syntaxHighlightingInput/hooks/useCodemirrorEditable.js +11 -4
  46. package/esm/syntaxHighlightingInput/hooks/useCodemirrorKeymap.d.ts +1 -1
  47. package/esm/syntaxHighlightingInput/hooks/useCodemirrorKeymap.d.ts.map +1 -1
  48. package/esm/syntaxHighlightingInput/hooks/useCodemirrorKeymap.js +26 -22
  49. package/esm/syntaxHighlightingInput/syntaxErrorLinter.d.ts +24 -0
  50. package/esm/syntaxHighlightingInput/syntaxErrorLinter.d.ts.map +1 -0
  51. package/esm/syntaxHighlightingInput/syntaxErrorLinter.js +49 -0
  52. package/package.json +13 -11
  53. package/src/@ui/UiConfigEditor/UiConfigEditor.scss +169 -0
  54. package/src/@ui/UiMenu/UiMenu.scss +6 -0
  55. package/src/@ui/UiSubmenuHeader/UiSubmenuHeader.scss +15 -0
  56. package/src/@ui/index.scss +1 -0
  57. package/styles/css/main.css +132 -0
  58. package/styles/css/main.css.map +1 -1
  59. package/styles/css/stylingEditorDialog.css +5 -0
  60. package/styles/css/stylingEditorDialog.css.map +1 -1
  61. package/styles/scss/stylingEditorDialog.scss +8 -0
  62. package/esm/syntaxHighlightingInput/YamlEditor.d.ts +0 -35
  63. package/esm/syntaxHighlightingInput/YamlEditor.d.ts.map +0 -1
  64. package/esm/syntaxHighlightingInput/YamlEditor.js +0 -32
  65. package/esm/syntaxHighlightingInput/yamlSyntaxLinter.d.ts +0 -12
  66. package/esm/syntaxHighlightingInput/yamlSyntaxLinter.d.ts.map +0 -1
  67. package/esm/syntaxHighlightingInput/yamlSyntaxLinter.js +0 -32
@@ -1,7 +1,7 @@
1
- // (C) 2025 GoodData Corporation
1
+ // (C) 2025-2026 GoodData Corporation
2
2
  import { useEffect } from "react";
3
3
  import { EditorSelection, Transaction } from "@codemirror/state";
4
- export function useCodemirrorChange(viewRef, value) {
4
+ export function useCodemirrorChange(viewRef, value, externalChangeSelection = "clamp") {
5
5
  // Handle external value changes
6
6
  useEffect(() => {
7
7
  const view = viewRef.current;
@@ -13,11 +13,13 @@ export function useCodemirrorChange(viewRef, value) {
13
13
  const hasFocus = view.hasFocus;
14
14
  const newLength = value.length;
15
15
  // Adjust selection to stay within bounds of new content
16
- const adjustedSelection = EditorSelection.create(selection.ranges.map((range) => {
17
- const from = Math.min(range.from, newLength);
18
- const to = Math.min(range.to, newLength);
19
- return EditorSelection.range(from, to);
20
- }), selection.mainIndex);
16
+ const adjustedSelection = externalChangeSelection === "end"
17
+ ? EditorSelection.single(newLength)
18
+ : EditorSelection.create(selection.ranges.map((range) => {
19
+ const from = Math.min(range.from, newLength);
20
+ const to = Math.min(range.to, newLength);
21
+ return EditorSelection.range(from, to);
22
+ }), selection.mainIndex);
21
23
  view.dispatch({
22
24
  changes: { from: 0, to: currentValue.length, insert: value },
23
25
  selection: adjustedSelection,
@@ -30,5 +32,5 @@ export function useCodemirrorChange(viewRef, value) {
30
32
  view.focus();
31
33
  }
32
34
  }
33
- }, [value, viewRef]);
35
+ }, [value, viewRef, externalChangeSelection]);
34
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useCodemirrorEditable.d.ts","sourceRoot":"","sources":["../../../src/syntaxHighlightingInput/hooks/useCodemirrorEditable.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,OAAO;;EAqB9F"}
1
+ {"version":3,"file":"useCodemirrorEditable.d.ts","sourceRoot":"","sources":["../../../src/syntaxHighlightingInput/hooks/useCodemirrorEditable.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAU9C,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,OAAO;;EAqB9F"}
@@ -1,11 +1,18 @@
1
- // (C) 2025 GoodData Corporation
1
+ // (C) 2025-2026 GoodData Corporation
2
2
  import { useEffect, useRef } from "react";
3
- import { Compartment } from "@codemirror/state";
3
+ import { Compartment, EditorState } from "@codemirror/state";
4
4
  import { EditorView } from "@codemirror/view";
5
+ // `EditorView.editable` only governs the DOM's contenteditable, which blocks typing; keymap
6
+ // commands (Enter, deletions, undo) consult `EditorState.readOnly` instead, so both must flip
7
+ // together or a "read-only" editor still mutates from the keyboard.
8
+ const editableExtensions = (disabled) => [
9
+ EditorView.editable.of(!disabled),
10
+ EditorState.readOnly.of(disabled),
11
+ ];
5
12
  export function useCodemirrorEditable(viewRef, disabled) {
6
13
  // Editable compartment
7
14
  const editableCompartmentRef = useRef(new Compartment());
8
- const editableCompartmentExtension = editableCompartmentRef.current.of(EditorView.editable.of(!disabled));
15
+ const editableCompartmentExtension = editableCompartmentRef.current.of(editableExtensions(!!disabled));
9
16
  // Handle disabled state changes
10
17
  useEffect(() => {
11
18
  const view = viewRef.current;
@@ -14,7 +21,7 @@ export function useCodemirrorEditable(viewRef, disabled) {
14
21
  }
15
22
  // Update the editable compartment based on the disabled prop
16
23
  view.dispatch({
17
- effects: editableCompartmentRef.current.reconfigure(EditorView.editable.of(!disabled)),
24
+ effects: editableCompartmentRef.current.reconfigure(editableExtensions(!!disabled)),
18
25
  });
19
26
  }, [disabled, viewRef]);
20
27
  return {
@@ -3,6 +3,6 @@ import { type EditorView } from "@codemirror/view";
3
3
  export declare function useCodemirrorKeymap({ handleKeyDown }: {
4
4
  handleKeyDown: RefObject<((event: KeyboardEvent, view: EditorView) => boolean) | undefined>;
5
5
  }): {
6
- keymapExtension: import("@codemirror/state").Extension;
6
+ keymapExtension: import("@codemirror/state").Extension[];
7
7
  };
8
8
  //# sourceMappingURL=useCodemirrorKeymap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCodemirrorKeymap.d.ts","sourceRoot":"","sources":["../../../src/syntaxHighlightingInput/hooks/useCodemirrorKeymap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAIhD,OAAO,EAAE,KAAK,UAAU,EAAU,MAAM,kBAAkB,CAAC;AAE3D,wBAAgB,mBAAmB,CAAC,EAChC,aAAa,EAChB,EAAE;IACC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;CAC/F;;EA0BA"}
1
+ {"version":3,"file":"useCodemirrorKeymap.d.ts","sourceRoot":"","sources":["../../../src/syntaxHighlightingInput/hooks/useCodemirrorKeymap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAIhD,OAAO,EAAE,KAAK,UAAU,EAAU,MAAM,kBAAkB,CAAC;AAE3D,wBAAgB,mBAAmB,CAAC,EAChC,aAAa,EAChB,EAAE;IACC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;CAC/F;;EA8BA"}
@@ -1,31 +1,35 @@
1
- // (C) 2025 GoodData Corporation
1
+ // (C) 2025-2026 GoodData Corporation
2
2
  import { useMemo } from "react";
3
3
  import { startCompletion } from "@codemirror/autocomplete";
4
- import { defaultKeymap, historyKeymap } from "@codemirror/commands";
4
+ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
5
5
  import { keymap } from "@codemirror/view";
6
6
  export function useCodemirrorKeymap({ handleKeyDown, }) {
7
- // Keymap extension
7
+ // Keymap extension. The history state extension comes with it: `historyKeymap` binds the
8
+ // undo/redo commands, but without `history()` installed they have nothing to operate on.
8
9
  const keymapExtension = useMemo(() => {
9
- return keymap.of([
10
- // Handle custom keydown events
11
- {
12
- any(view, event) {
13
- return handleKeyDown.current?.(event, view) ?? false;
10
+ return [
11
+ history(),
12
+ keymap.of([
13
+ // Handle custom keydown events
14
+ {
15
+ any(view, event) {
16
+ return handleKeyDown.current?.(event, view) ?? false;
17
+ },
14
18
  },
15
- },
16
- // "Mod" is Ctrl on Windows/Linux, Cmd on macOS
17
- {
18
- key: "Mod-i",
19
- run: startCompletion,
20
- },
21
- // Ctrl on Windows
22
- {
23
- key: "Ctrl-i",
24
- run: startCompletion,
25
- },
26
- ...defaultKeymap,
27
- ...historyKeymap,
28
- ]);
19
+ // "Mod" is Ctrl on Windows/Linux, Cmd on macOS
20
+ {
21
+ key: "Mod-i",
22
+ run: startCompletion,
23
+ },
24
+ // Ctrl on Windows
25
+ {
26
+ key: "Ctrl-i",
27
+ run: startCompletion,
28
+ },
29
+ ...defaultKeymap,
30
+ ...historyKeymap,
31
+ ]),
32
+ ];
29
33
  }, [handleKeyDown]);
30
34
  return { keymapExtension };
31
35
  }
@@ -0,0 +1,24 @@
1
+ import { type Diagnostic } from "@codemirror/lint";
2
+ import { type EditorState } from "@codemirror/state";
3
+ /**
4
+ * Collects syntax-error diagnostics by walking the parse tree for error nodes.
5
+ *
6
+ * A blank document reports nothing: grammars differ on whether emptiness parses (JSON requires a
7
+ * value, YAML does not), but an editor the user has not typed into yet is not in error — that is
8
+ * what the caller's own "required" validation says, not the gutter.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function getSyntaxErrorDiagnostics(state: EditorState, message: string): Diagnostic[];
13
+ /**
14
+ * Creates a CodeMirror linter extension that reports syntax errors by walking the parse tree for
15
+ * error nodes.
16
+ *
17
+ * Language-agnostic: it reads the tree the language extension already built for highlighting, so it
18
+ * works for any grammar and costs no extra parse and no extra dependency.
19
+ *
20
+ * @param message - localized message shown on each syntax-error diagnostic
21
+ * @internal
22
+ */
23
+ export declare function createSyntaxErrorLinter(message: string): import("@codemirror/state").Extension;
24
+ //# sourceMappingURL=syntaxErrorLinter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntaxErrorLinter.d.ts","sourceRoot":"","sources":["../../src/syntaxHighlightingInput/syntaxErrorLinter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAU,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CA0B3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,yCAEtD"}
@@ -0,0 +1,49 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { syntaxTree } from "@codemirror/language";
3
+ import { linter } from "@codemirror/lint";
4
+ /**
5
+ * Collects syntax-error diagnostics by walking the parse tree for error nodes.
6
+ *
7
+ * A blank document reports nothing: grammars differ on whether emptiness parses (JSON requires a
8
+ * value, YAML does not), but an editor the user has not typed into yet is not in error — that is
9
+ * what the caller's own "required" validation says, not the gutter.
10
+ *
11
+ * @internal
12
+ */
13
+ export function getSyntaxErrorDiagnostics(state, message) {
14
+ if (state.doc.toString().trim() === "") {
15
+ return [];
16
+ }
17
+ const diagnostics = [];
18
+ const tree = syntaxTree(state);
19
+ tree.iterate({
20
+ enter(node) {
21
+ if (node.type.isError) {
22
+ diagnostics.push({
23
+ from: node.from,
24
+ // Zero-width error nodes are widened so the marker has something to underline —
25
+ // but never past the end of the document, where an incomplete document's EOF
26
+ // error sits: a diagnostic outside the document is out of contract for the lint
27
+ // state and can throw once decorations are built from it.
28
+ to: Math.min(Math.max(node.to, node.from + 1), state.doc.length),
29
+ severity: "error",
30
+ message,
31
+ });
32
+ }
33
+ },
34
+ });
35
+ return diagnostics;
36
+ }
37
+ /**
38
+ * Creates a CodeMirror linter extension that reports syntax errors by walking the parse tree for
39
+ * error nodes.
40
+ *
41
+ * Language-agnostic: it reads the tree the language extension already built for highlighting, so it
42
+ * works for any grammar and costs no extra parse and no extra dependency.
43
+ *
44
+ * @param message - localized message shown on each syntax-error diagnostic
45
+ * @internal
46
+ */
47
+ export function createSyntaxErrorLinter(message) {
48
+ return linter((view) => getSyntaxErrorDiagnostics(view.state, message));
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-kit",
3
- "version": "11.52.0",
3
+ "version": "11.53.0-alpha.1",
4
4
  "description": "GoodData SDK - UI Building Components",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -38,6 +38,7 @@
38
38
  "@aaronhayes/react-use-hubspot-form": "2.1.2",
39
39
  "@codemirror/autocomplete": "6.20.0",
40
40
  "@codemirror/commands": "6.10.1",
41
+ "@codemirror/lang-json": "6.0.2",
41
42
  "@codemirror/lang-yaml": "6.1.3",
42
43
  "@codemirror/language": "6.12.1",
43
44
  "@codemirror/lint": "6.9.2",
@@ -76,11 +77,12 @@
76
77
  "tslib": "2.8.1",
77
78
  "unified": "^11.0.5",
78
79
  "uuid": "11.1.1",
79
- "@gooddata/sdk-model": "11.52.0",
80
- "@gooddata/sdk-backend-spi": "11.52.0",
81
- "@gooddata/sdk-ui": "11.52.0",
82
- "@gooddata/sdk-ui-theme-provider": "11.52.0",
83
- "@gooddata/util": "11.52.0"
80
+ "yaml": "2.8.3",
81
+ "@gooddata/sdk-backend-spi": "11.53.0-alpha.1",
82
+ "@gooddata/sdk-model": "11.53.0-alpha.1",
83
+ "@gooddata/sdk-ui": "11.53.0-alpha.1",
84
+ "@gooddata/util": "11.53.0-alpha.1",
85
+ "@gooddata/sdk-ui-theme-provider": "11.53.0-alpha.1"
84
86
  },
85
87
  "devDependencies": {
86
88
  "@microsoft/api-documenter": "^7.17.0",
@@ -129,11 +131,11 @@
129
131
  "typescript": "5.9.3",
130
132
  "vitest": "4.1.8",
131
133
  "vitest-dom": "0.1.1",
132
- "@gooddata/oxlint-config": "11.52.0",
133
- "@gooddata/reference-workspace": "11.52.0",
134
- "@gooddata/eslint-config": "11.52.0",
135
- "@gooddata/sdk-backend-mockingbird": "11.52.0",
136
- "@gooddata/stylelint-config": "11.52.0"
134
+ "@gooddata/eslint-config": "11.53.0-alpha.1",
135
+ "@gooddata/oxlint-config": "11.53.0-alpha.1",
136
+ "@gooddata/reference-workspace": "11.53.0-alpha.1",
137
+ "@gooddata/sdk-backend-mockingbird": "11.53.0-alpha.1",
138
+ "@gooddata/stylelint-config": "11.53.0-alpha.1"
137
139
  },
138
140
  "peerDependencies": {
139
141
  "react": "^18.0.0 || ^19.0.0",
@@ -0,0 +1,169 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ @use "sass:color";
4
+
5
+ @use "../../../styles/scss/variables";
6
+
7
+ .gd-ui-kit-config-editor {
8
+ // The single source for the row-height maths: the fixed editor height below and the pinned
9
+ // content line-height must agree, or `rows` stops meaning whole lines.
10
+ --config-editor-line-height: 1.5;
11
+
12
+ display: flex;
13
+ flex: 1;
14
+ flex-direction: column;
15
+ min-height: 0;
16
+ // The frame belongs to the whole control, so the toolbar reads as part of the field rather than
17
+ // as separate chrome floating above it. `overflow: hidden` is what keeps the editor's square
18
+ // corners from poking through the rounded ones.
19
+ border: 1px solid var(--gd-palette-complementary-4);
20
+ border-radius: 3px;
21
+ overflow: hidden;
22
+
23
+ &__toolbar {
24
+ display: flex;
25
+ flex: 0 0 auto;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ gap: var(--gd-spacing-10px);
29
+ // A fixed height, so the toolbar reads the same whichever controls happen to be in it —
30
+ // radios, the menu button, or just the Auto-format link. 36px of content plus the divider,
31
+ // sized explicitly so a host's global box-sizing reset cannot change the outcome.
32
+ box-sizing: border-box;
33
+ height: 37px;
34
+ padding-left: var(--gd-spacing-10px);
35
+ border-bottom: 1px solid var(--gd-palette-complementary-4);
36
+ }
37
+
38
+ &__languages {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: var(--gd-spacing-10px);
42
+ }
43
+
44
+ &__actions {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: var(--gd-spacing-10px);
48
+ // Keeps the actions pinned to the right edge even when a single-language editor renders no
49
+ // switcher on the left, where `space-between` alone would let them drift to the start.
50
+ margin-left: auto;
51
+ // Mirrors the switcher's inset on the other side.
52
+ padding-right: var(--gd-spacing-10px);
53
+ }
54
+
55
+ // With the menu button present the design sits it flush against the frame — the button's own
56
+ // padding is what keeps the ellipsis off the edge, so any padding here would double it.
57
+ &__actions--withMenu {
58
+ padding-right: 0;
59
+ }
60
+
61
+ &__editor {
62
+ display: flex;
63
+ flex: 1;
64
+ flex-direction: column;
65
+ min-height: 0;
66
+ }
67
+
68
+ /**
69
+ * The shared syntax-highlighting input renders its own wrapper between this element and
70
+ * `.cm-editor`, and that wrapper is a plain block. Without making it part of the flex column the
71
+ * `flex` on `.cm-editor` below is inert, so a long document grows the editor without bound and
72
+ * the surrounding dialog scrolls instead of the editor scrolling inside itself.
73
+ */
74
+ &__editor > * {
75
+ display: flex;
76
+ flex: 1;
77
+ flex-direction: column;
78
+ min-height: 0;
79
+ }
80
+
81
+ /**
82
+ * The shared syntax-highlighting input ships a fixed `height: 100px` on `.cm-editor`, scoped
83
+ * under its own `.gd-input-syntax-highlighting-input` class. That stylesheet is bundled after
84
+ * this one, so at equal specificity it would win — hence the repeated block class, which buys a
85
+ * third class without changing what is matched.
86
+ *
87
+ * Height comes from `rows`, while still growing to fill a taller parent, so the editor works
88
+ * both standalone and as a field in a fixed-height dialog.
89
+ *
90
+ * `--config-editor-rows` is deliberately outside the `--gd-` namespace: that prefix is reserved
91
+ * for theme variables, each of which needs a specification in sdk-ui-theme-provider. This one
92
+ * only carries the `rows` prop into the stylesheet and is not something a theme should set.
93
+ */
94
+ &.gd-ui-kit-config-editor &__editor .cm-editor {
95
+ // A definite height rather than a floor, so the scroller inside always has something to
96
+ // scroll against: with `min-height` alone a long document grows the editor without bound in
97
+ // any parent that is not itself height-constrained, and the page scrolls instead.
98
+ //
99
+ // `rows` whole lines, plus the 8px of padding CodeMirror puts around its content and the 1px
100
+ // border on each side (the box is border-box). Sizing to whole lines keeps a line from being
101
+ // clipped through the middle, which looks unfinished and makes a screenshot of it turn on how
102
+ // half a glyph happens to be antialiased.
103
+ height: calc(var(--config-editor-rows, 12) * var(--config-editor-line-height) * 1em + 8px);
104
+ // Still grows to take the space when a parent does constrain and offer it, which is what a
105
+ // fixed-height dialog does.
106
+ flex: 1 1 auto;
107
+ min-height: 0;
108
+ // The frame and its corners now belong to the wrapper, so the shared input's own border and
109
+ // radius would draw a second box inside it.
110
+ border: none;
111
+ border-radius: 0;
112
+ // The standard input inner shadow, falling from the toolbar onto the code area. It replaces
113
+ // the shared input's own (which doubles as its focus styling — here focus is shown on the
114
+ // whole field instead), with the design's slightly softer blur.
115
+ box-shadow: inset 0 1px 2px 0 color.adjust(variables.$gd-input-text-box-shadow-color, $alpha: -0.85);
116
+ }
117
+
118
+ // Focus is shown on the whole field instead, so the toolbar is visibly part of what is focused.
119
+ &.gd-ui-kit-config-editor:has(.cm-editor.cm-focused) {
120
+ border-color: var(--gd-palette-primary-base);
121
+ }
122
+
123
+ // Pinned rather than left as `normal`, so the height above is exactly `rows` lines.
124
+ &__editor .cm-content {
125
+ line-height: var(--config-editor-line-height);
126
+ }
127
+
128
+ // Scrolling past the end of a long config should not start scrolling the dialog behind it.
129
+ &__editor .cm-scroller {
130
+ overflow: auto;
131
+ overscroll-behavior: contain;
132
+ }
133
+
134
+ /**
135
+ * The design draws no gutter chrome at all: the line numbers sit on the editor's own surface,
136
+ * with no fill and no divider. Making it transparent (rather than a colour) also keeps it
137
+ * correct on a dark theme, where CodeMirror's built-in light-variant gutter would otherwise
138
+ * paint a pale strip — whatever themed background the editor has simply shows through.
139
+ *
140
+ * The repeated block class is for specificity: CodeMirror's own rule is `.cm-editor.cm-light
141
+ * .cm-gutters`, which would otherwise win.
142
+ */
143
+ &.gd-ui-kit-config-editor &__editor .cm-gutters {
144
+ border-right: none;
145
+ background: transparent;
146
+ color: var(--gd-palette-complementary-6);
147
+ }
148
+
149
+ &.gd-ui-kit-config-editor &__editor .cm-activeLineGutter {
150
+ background: transparent;
151
+ color: var(--gd-palette-complementary-7);
152
+ }
153
+
154
+ // Muted surface for a value that cannot be edited. Applied to the whole field, toolbar included,
155
+ // now that they share one frame — tinting only the code area would leave a lighter strip on top.
156
+ // The gutter is transparent, so it takes the same muted surface by itself.
157
+ &--disabled,
158
+ &--readOnly {
159
+ background: var(--gd-palette-complementary-1);
160
+
161
+ .cm-editor {
162
+ background: transparent;
163
+ }
164
+ }
165
+
166
+ &--disabled {
167
+ opacity: 0.7;
168
+ }
169
+ }
@@ -192,6 +192,12 @@
192
192
  > :not(#{$root}__item-title) {
193
193
  flex: none;
194
194
  }
195
+
196
+ // The submenu indicator is an affordance, not content, so it takes the muted colour rather
197
+ // than inheriting the label's.
198
+ .gd-icon-navigateright {
199
+ color: var(--gd-palette-complementary-5);
200
+ }
195
201
  }
196
202
 
197
203
  &__item-title {
@@ -6,6 +6,10 @@
6
6
  align-items: center;
7
7
  gap: var(--gd-spacing-5px);
8
8
  width: 100%;
9
+ // The horizontal padding must come out of the 100% width, not be added on top of it: without
10
+ // this the header renders wider than the menu that contains it and juts out of the panel in any
11
+ // app that does not carry a global border-box reset.
12
+ box-sizing: border-box;
9
13
  color: var(--gd-palette-complementary-6, #94a1ad);
10
14
  background: var(--gd-palette-complementary-2, #ebeff4);
11
15
 
@@ -23,6 +27,17 @@
23
27
  text-transform: uppercase;
24
28
  }
25
29
 
30
+ &--small {
31
+ padding: 0 10px;
32
+ height: 35px;
33
+ min-height: 35px;
34
+
35
+ h3.gd-ui-kit-submenu-header__title-text {
36
+ font-size: 11px;
37
+ line-height: normal;
38
+ }
39
+ }
40
+
26
41
  &--medium {
27
42
  padding: 0 10px;
28
43
  height: 36px;
@@ -51,6 +51,7 @@
51
51
  @use "./UiPermissionMenu/UiPermissionMenu.scss";
52
52
  @use "./UiGranteeRowControls/UiGranteeRowControls.scss";
53
53
  @use "./UiToast/UiToast.scss";
54
+ @use "./UiConfigEditor/UiConfigEditor.scss";
54
55
 
55
56
  // `--gd-font-family` is a `:root` variable only; there is no global `body` font
56
57
  // declaration. Apply it once to every kit root so standalone components don't
@@ -1,3 +1,4 @@
1
+ @charset "UTF-8";
1
2
  :root {
2
3
  --gd-palette-error-base: #e54d42;
3
4
  --gd-palette-info-base: var(--gd-palette-primary-base, #14b2e2);
@@ -2000,6 +2001,9 @@
2000
2001
  .gd-ui-kit-menu__item > :not(.gd-ui-kit-menu__item-title) {
2001
2002
  flex: none;
2002
2003
  }
2004
+ .gd-ui-kit-menu__item .gd-icon-navigateright {
2005
+ color: var(--gd-palette-complementary-5);
2006
+ }
2003
2007
  .gd-ui-kit-menu__item-title {
2004
2008
  width: 100%;
2005
2009
  min-width: 0;
@@ -3363,6 +3367,7 @@
3363
3367
  align-items: center;
3364
3368
  gap: var(--gd-spacing-5px);
3365
3369
  width: 100%;
3370
+ box-sizing: border-box;
3366
3371
  color: var(--gd-palette-complementary-6, #94a1ad);
3367
3372
  background: var(--gd-palette-complementary-2, #ebeff4);
3368
3373
  }
@@ -3378,6 +3383,15 @@
3378
3383
  font-family: var(--gd-font-family, gdcustomfont, Avenir, "Helvetica Neue", arial, sans-serif);
3379
3384
  text-transform: uppercase;
3380
3385
  }
3386
+ .gd-ui-kit-submenu-header--small {
3387
+ padding: 0 10px;
3388
+ height: 35px;
3389
+ min-height: 35px;
3390
+ }
3391
+ .gd-ui-kit-submenu-header--small h3.gd-ui-kit-submenu-header__title-text {
3392
+ font-size: 11px;
3393
+ line-height: normal;
3394
+ }
3381
3395
  .gd-ui-kit-submenu-header--medium {
3382
3396
  padding: 0 10px;
3383
3397
  height: 36px;
@@ -4223,6 +4237,119 @@
4223
4237
  outline-offset: 2px;
4224
4238
  }
4225
4239
 
4240
+ .gd-ui-kit-config-editor {
4241
+ --config-editor-line-height: 1.5;
4242
+ display: flex;
4243
+ flex: 1;
4244
+ flex-direction: column;
4245
+ min-height: 0;
4246
+ border: 1px solid var(--gd-palette-complementary-4);
4247
+ border-radius: 3px;
4248
+ overflow: hidden;
4249
+ /**
4250
+ * The shared syntax-highlighting input renders its own wrapper between this element and
4251
+ * `.cm-editor`, and that wrapper is a plain block. Without making it part of the flex column the
4252
+ * `flex` on `.cm-editor` below is inert, so a long document grows the editor without bound and
4253
+ * the surrounding dialog scrolls instead of the editor scrolling inside itself.
4254
+ */
4255
+ /**
4256
+ * The shared syntax-highlighting input ships a fixed `height: 100px` on `.cm-editor`, scoped
4257
+ * under its own `.gd-input-syntax-highlighting-input` class. That stylesheet is bundled after
4258
+ * this one, so at equal specificity it would win — hence the repeated block class, which buys a
4259
+ * third class without changing what is matched.
4260
+ *
4261
+ * Height comes from `rows`, while still growing to fill a taller parent, so the editor works
4262
+ * both standalone and as a field in a fixed-height dialog.
4263
+ *
4264
+ * `--config-editor-rows` is deliberately outside the `--gd-` namespace: that prefix is reserved
4265
+ * for theme variables, each of which needs a specification in sdk-ui-theme-provider. This one
4266
+ * only carries the `rows` prop into the stylesheet and is not something a theme should set.
4267
+ */
4268
+ /**
4269
+ * The design draws no gutter chrome at all: the line numbers sit on the editor's own surface,
4270
+ * with no fill and no divider. Making it transparent (rather than a colour) also keeps it
4271
+ * correct on a dark theme, where CodeMirror's built-in light-variant gutter would otherwise
4272
+ * paint a pale strip — whatever themed background the editor has simply shows through.
4273
+ *
4274
+ * The repeated block class is for specificity: CodeMirror's own rule is `.cm-editor.cm-light
4275
+ * .cm-gutters`, which would otherwise win.
4276
+ */
4277
+ }
4278
+ .gd-ui-kit-config-editor__toolbar {
4279
+ display: flex;
4280
+ flex: 0 0 auto;
4281
+ align-items: center;
4282
+ justify-content: space-between;
4283
+ gap: var(--gd-spacing-10px);
4284
+ box-sizing: border-box;
4285
+ height: 37px;
4286
+ padding-left: var(--gd-spacing-10px);
4287
+ border-bottom: 1px solid var(--gd-palette-complementary-4);
4288
+ }
4289
+ .gd-ui-kit-config-editor__languages {
4290
+ display: flex;
4291
+ align-items: center;
4292
+ gap: var(--gd-spacing-10px);
4293
+ }
4294
+ .gd-ui-kit-config-editor__actions {
4295
+ display: flex;
4296
+ align-items: center;
4297
+ gap: var(--gd-spacing-10px);
4298
+ margin-left: auto;
4299
+ padding-right: var(--gd-spacing-10px);
4300
+ }
4301
+ .gd-ui-kit-config-editor__actions--withMenu {
4302
+ padding-right: 0;
4303
+ }
4304
+ .gd-ui-kit-config-editor__editor {
4305
+ display: flex;
4306
+ flex: 1;
4307
+ flex-direction: column;
4308
+ min-height: 0;
4309
+ }
4310
+ .gd-ui-kit-config-editor__editor > * {
4311
+ display: flex;
4312
+ flex: 1;
4313
+ flex-direction: column;
4314
+ min-height: 0;
4315
+ }
4316
+ .gd-ui-kit-config-editor.gd-ui-kit-config-editor .gd-ui-kit-config-editor__editor .cm-editor {
4317
+ height: calc(var(--config-editor-rows, 12) * var(--config-editor-line-height) * 1em + 8px);
4318
+ flex: 1 1 auto;
4319
+ min-height: 0;
4320
+ border: none;
4321
+ border-radius: 0;
4322
+ box-shadow: inset 0 1px 2px 0 rgba(31, 53, 74, 0.15);
4323
+ }
4324
+ .gd-ui-kit-config-editor.gd-ui-kit-config-editor:has(.cm-editor.cm-focused) {
4325
+ border-color: var(--gd-palette-primary-base);
4326
+ }
4327
+ .gd-ui-kit-config-editor__editor .cm-content {
4328
+ line-height: var(--config-editor-line-height);
4329
+ }
4330
+ .gd-ui-kit-config-editor__editor .cm-scroller {
4331
+ overflow: auto;
4332
+ overscroll-behavior: contain;
4333
+ }
4334
+ .gd-ui-kit-config-editor.gd-ui-kit-config-editor .gd-ui-kit-config-editor__editor .cm-gutters {
4335
+ border-right: none;
4336
+ background: transparent;
4337
+ color: var(--gd-palette-complementary-6);
4338
+ }
4339
+ .gd-ui-kit-config-editor.gd-ui-kit-config-editor .gd-ui-kit-config-editor__editor .cm-activeLineGutter {
4340
+ background: transparent;
4341
+ color: var(--gd-palette-complementary-7);
4342
+ }
4343
+ .gd-ui-kit-config-editor--disabled, .gd-ui-kit-config-editor--readOnly {
4344
+ background: var(--gd-palette-complementary-1);
4345
+ }
4346
+ .gd-ui-kit-config-editor--disabled .cm-editor, .gd-ui-kit-config-editor--readOnly .cm-editor {
4347
+ background: transparent;
4348
+ }
4349
+ .gd-ui-kit-config-editor--disabled {
4350
+ opacity: 0.7;
4351
+ }
4352
+
4226
4353
  [class^=gd-ui-kit-],
4227
4354
  [class*=" gd-ui-kit-"] {
4228
4355
  font-family: var(--gd-font-family);
@@ -12847,6 +12974,11 @@ button.gd-button-primary.gd-upsell-button:active {
12847
12974
  color: var(--gd-palette-complementary-7, #6d7680);
12848
12975
  margin-top: 20px;
12849
12976
  }
12977
+ .gd-styling-editor-dialog-content-form-textarea > div {
12978
+ flex: 1;
12979
+ min-height: 0;
12980
+ margin-bottom: 20px;
12981
+ }
12850
12982
  .gd-styling-editor-dialog-content-form-textarea textarea {
12851
12983
  resize: none;
12852
12984
  flex: 1;