@kubex/zinc 1.1.94 → 1.1.96

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 (39) hide show
  1. package/dist/custom-elements.json +653 -89
  2. package/dist/vscode.html-custom-data.json +73 -18
  3. package/dist/web-types.json +145 -35
  4. package/dist/zn.d.ts +204 -39
  5. package/dist/zn.min.css +1 -1
  6. package/dist/zn.min.js +374 -308
  7. package/docs/pages/components/flow-builder.md +17 -1
  8. package/docs/pages/components/page-builder.md +31 -0
  9. package/docs/pages/components/slash-menu.md +132 -6
  10. package/docs/pages/components/textarea.md +16 -0
  11. package/package.json +1 -1
  12. package/scss/_root.scss +7 -1
  13. package/src/components/button/button.scss +5 -2
  14. package/src/components/flow-builder/flow-builder.component.ts +16 -1
  15. package/src/components/flow-builder/flow-builder.test.ts +206 -1
  16. package/src/components/flow-builder/flow-geometry.test.ts +102 -0
  17. package/src/components/flow-builder/flow.types.ts +82 -15
  18. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +163 -108
  19. package/src/components/flow-builder/modules/flow-step/flow-step.component.ts +2 -0
  20. package/src/components/inline-edit/inline-edit.component.ts +6 -1
  21. package/src/components/input/input.component.ts +12 -2
  22. package/src/components/page/page.scss +7 -2
  23. package/src/components/page-builder/modules/page-section-card/page-section-card.component.ts +16 -9
  24. package/src/components/page-builder/modules/page-section-card/page-section-card.scss +13 -0
  25. package/src/components/page-builder/modules/page-section-card/page-section-card.test.ts +9 -0
  26. package/src/components/page-builder/page-builder.component.ts +62 -4
  27. package/src/components/page-builder/page-builder.test.ts +94 -0
  28. package/src/components/remarkd-editor/remarkd-editor.component.ts +227 -12
  29. package/src/components/remarkd-editor/remarkd-editor.scss +81 -0
  30. package/src/components/remarkd-editor/remarkd-editor.test.ts +258 -0
  31. package/src/components/settings-container/settings-container.scss +2 -1
  32. package/src/components/slash-item/slash-item.component.ts +1 -1
  33. package/src/components/slash-menu/slash-menu-items.ts +48 -0
  34. package/src/components/slash-menu/slash-menu.component.ts +134 -27
  35. package/src/components/slash-menu/slash-menu.scss +90 -12
  36. package/src/components/slash-menu/slash-menu.test.ts +107 -0
  37. package/src/components/textarea/textarea.component.ts +12 -2
  38. package/src/components/textarea/textarea.test.ts +2 -2
  39. package/src/components/translations/translations.component.ts +5 -1
@@ -4,12 +4,10 @@ import {defaultValue} from "../../internal/default-value";
4
4
  import {FormControlController} from "../../internal/form";
5
5
  import {property, query, state} from 'lit/decorators.js';
6
6
  import {parse as remarkdParse} from "remarkd-js";
7
- import {SlashMenuController} from "../slash-menu/slash-menu-controller";
8
7
  import {unsafeHTML} from "lit/directives/unsafe-html.js";
9
8
  import {watch} from "../../internal/watch";
10
9
  import ZincElement from '../../internal/zinc-element';
11
- import ZnSlashMenu from "../slash-menu";
12
- import type {SlashMenuItem} from "../slash-menu/slash-menu-items";
10
+ import ZnSlashMenu, {SlashMenuController, type SlashMenuItem} from "../slash-menu";
13
11
  import type {ZincFormControl} from '../../internal/zinc-element';
14
12
  import type ZnFile from "../file";
15
13
 
@@ -28,6 +26,7 @@ interface BlockType {
28
26
  /** Where the caret lands within `prefix`. Defaults to the end. */
29
27
  caretOffset?: number;
30
28
  image?: boolean;
29
+ include?: boolean;
31
30
  }
32
31
 
33
32
  interface ImageBlockData {
@@ -39,6 +38,21 @@ interface ImageBlockData {
39
38
  height: string;
40
39
  }
41
40
 
41
+ interface IncludeBlockData {
42
+ target: string;
43
+ label: string;
44
+ }
45
+
46
+ interface IncludeOption {
47
+ id: string;
48
+ title: string;
49
+ description?: string;
50
+ scope?: string;
51
+ keywords?: string[];
52
+ languages?: string;
53
+ url?: string;
54
+ }
55
+
42
56
  const BLOCK_TYPES: BlockType[] = [
43
57
  {label: 'Text', icon: 'text@lu', prefix: ''},
44
58
  {label: 'Heading 1', icon: 'heading-1@lu', prefix: '# '},
@@ -49,11 +63,25 @@ const BLOCK_TYPES: BlockType[] = [
49
63
  {label: 'Warning', icon: 'triangle-alert@lu', prefix: 'WARNING: '},
50
64
  {label: 'Code', icon: 'code@lu', prefix: '```\n\n```', caretOffset: 4},
51
65
  {label: 'Image', icon: 'image@lu', image: true},
66
+ {label: 'Include', icon: 'blocks@lu', include: true},
52
67
  ];
53
68
 
69
+ /** remarkd's include directive on a line of its own: `include::<target>[<label>]`. */
70
+ const INCLUDE_LINE = /^include::([^[]+)\[(.*)]\s*$/;
71
+
72
+ /**
73
+ * The marker a document body carries for an embed. Mirrors app-kb's
74
+ * `model.IncludeEmbedMarker`: brackets and newlines in the title would break the
75
+ * directive, so they are replaced rather than escaped.
76
+ */
77
+ function includeMarker(id: string, title: string): string {
78
+ const label = title.replace(/[\r\n]+/g, ' ').replace(/\[/g, '(').replace(/]/g, ')').trim();
79
+ return `include::${id}[${label}]`;
80
+ }
81
+
54
82
  /** The block types as slash menu insertions. Image opens the picker, so it inserts nothing. */
55
- const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image
56
- ? {label: item.label, icon: item.icon, action: 'image'}
83
+ const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image || item.include
84
+ ? {label: item.label, icon: item.icon, action: item.image ? 'image' : 'include'}
57
85
  : {label: item.label, icon: item.icon, value: item.prefix ?? '', caretOffset: item.caretOffset});
58
86
 
59
87
  /**
@@ -80,6 +108,8 @@ const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image
80
108
  * @csspart raw - The full-document textarea shown in raw source mode.
81
109
  * @csspart slash-menu - The `zn-slash-menu` opened by typing "/" in an empty block.
82
110
  * @csspart image-controls - The caption / alignment / size panel shown when an image block is clicked.
111
+ * @csspart include - The chip rendered in place of an `include::` directive.
112
+ * @csspart include-picker - The inline Include picker opened from the toolbar or "/include".
83
113
  */
84
114
  export default class ZnRemarkdEditor extends ZincElement implements ZincFormControl {
85
115
  static styles: CSSResultGroup = unsafeCSS(styles);
@@ -95,11 +125,14 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
95
125
  menu: () => this.mountSlashMenu(),
96
126
  // The menu only belongs in a block that is nothing but the slash command — a
97
127
  // block prefix like "## " is not valid remarkd part-way through a line.
98
- items: () => this.isSlashBlock() ? SLASH_ITEMS : [],
128
+ items: () => this.isSlashBlock()
129
+ ? SLASH_ITEMS.filter(item => item.action !== 'include' || !!this.includeUrl)
130
+ : [],
99
131
  onSelect: item => this.handleSlashSelect(item)
100
132
  });
101
133
 
102
134
  private editingDraft = '';
135
+ private includeRequest: Promise<IncludeOption[]> | null = null;
103
136
  private rawEntryValue = '';
104
137
  private suppressValueSync = false;
105
138
  private suppressBlurCommit = false;
@@ -109,6 +142,12 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
109
142
 
110
143
  @state() private blocks: string[] = [];
111
144
  @state() private editingIndex: number | null = null;
145
+ /**
146
+ * Lists the blocks most recently inserted here above the rest of the slash menu, remembered in
147
+ * `localStorage` under this key. Leave unset to offer no recently used section.
148
+ */
149
+ @property({attribute: 'slash-recent-key'}) slashRecentKey = '';
150
+
112
151
  /** Renders the slash menu only once it has been needed. */
113
152
  @state() private hasSlashMenu = false;
114
153
  @state() private imagePickerIndex: number | null = null;
@@ -117,6 +156,10 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
117
156
  @state() private dragIndex: number | null = null;
118
157
  @state() private editShell = '';
119
158
  @state() private rawMode = false;
159
+ @state() private includeOptions: IncludeOption[] | null = null;
160
+ @state() private includeLoadFailed = false;
161
+ @state() private includePickerIndex: number | null = null;
162
+ @state() private includeQuery = '';
120
163
 
121
164
  private pendingDragHandle: HTMLElement | null = null;
122
165
  private dragStartX = 0;
@@ -142,6 +185,13 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
142
185
  */
143
186
  @property({attribute: 'attachment-url'}) attachmentUrl = '';
144
187
 
188
+ /**
189
+ * Endpoint listing the Includes this document may embed, as
190
+ * `{"items":[{id,title,description,scope,keywords,languages,url}]}`. Labels the
191
+ * chips rendered for `include::` directives and feeds the include picker.
192
+ */
193
+ @property({attribute: 'include-url'}) includeUrl = '';
194
+
145
195
  /** Adds a toolbar toggle that swaps the block view for the full remarkd source. */
146
196
  @property({type: Boolean, attribute: 'allow-raw', reflect: true}) allowRaw = false;
147
197
 
@@ -201,6 +251,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
201
251
  protected firstUpdated(_changedProperties: PropertyValues) {
202
252
  super.firstUpdated(_changedProperties);
203
253
  this.formControlController.updateValidity();
254
+ if (this.hasIncludeBlock()) void this.loadIncludeOptions();
204
255
  }
205
256
 
206
257
  disconnectedCallback() {
@@ -215,6 +266,15 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
215
266
  return;
216
267
  }
217
268
  this.blocks = this.splitBlocks(this.value || '');
269
+ if (this.hasUpdated && this.hasIncludeBlock()) void this.loadIncludeOptions();
270
+ }
271
+
272
+ @watch('includeUrl', {waitUntilFirstUpdate: true})
273
+ handleIncludeUrlChange() {
274
+ this.includeRequest = null;
275
+ this.includeOptions = null;
276
+ this.includeLoadFailed = false;
277
+ if (this.hasIncludeBlock()) void this.loadIncludeOptions();
218
278
  }
219
279
 
220
280
  /**
@@ -246,6 +306,12 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
246
306
  fence = marker;
247
307
  continue;
248
308
  }
309
+ if (INCLUDE_LINE.test(trimmed)) {
310
+ push();
311
+ current.push(line);
312
+ push();
313
+ continue;
314
+ }
249
315
  if (trimmed === '') {
250
316
  push();
251
317
  continue;
@@ -337,6 +403,13 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
337
403
  return found ? data : null;
338
404
  }
339
405
 
406
+ /** Parses a block that is nothing but an include directive. */
407
+ private parseIncludeBlock(block: string): IncludeBlockData | null {
408
+ const match = INCLUDE_LINE.exec((block ?? '').trim());
409
+ if (!match) return null;
410
+ return {target: match[1].trim(), label: match[2].trim()};
411
+ }
412
+
340
413
  private serializeImageBlock(data: ImageBlockData): string {
341
414
  const lines: string[] = [];
342
415
  if (data.caption.trim()) lines.push(`.${data.caption.trim()}`);
@@ -549,14 +622,18 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
549
622
 
550
623
  /** Returns false for items the controller should not insert text for. */
551
624
  private handleSlashSelect(item: SlashMenuItem): boolean {
552
- if (item.action !== 'image') return true;
625
+ if (item.action !== 'image' && item.action !== 'include') return true;
553
626
 
554
627
  const index = this.editingIndex ?? this.blocks.length;
555
628
  // Let the controller strip the "/…" text first, then drop the empty draft
556
- // block and run the image flow in its place.
629
+ // block and run the picker in its place.
557
630
  void this.updateComplete.then(() => {
558
631
  this.blur();
559
- this.pickImage(index);
632
+ if (item.action === 'image') {
633
+ this.pickImage(index);
634
+ } else {
635
+ this.pickInclude(index);
636
+ }
560
637
  });
561
638
  return false;
562
639
  }
@@ -682,9 +759,28 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
682
759
 
683
760
  private pickImage(index: number) {
684
761
  if (this.disabled || this.readonly) return;
762
+ this.includePickerIndex = null;
685
763
  this.imagePickerIndex = index;
686
764
  }
687
765
 
766
+ private pickInclude(index: number) {
767
+ if (this.disabled || this.readonly || !this.includeUrl) return;
768
+ this.imagePickerIndex = null;
769
+ this.includeQuery = '';
770
+ this.includePickerIndex = index;
771
+ void this.loadIncludeOptions();
772
+ }
773
+
774
+ private closeIncludePicker = () => {
775
+ this.includePickerIndex = null;
776
+ };
777
+
778
+ private insertInclude(item: IncludeOption) {
779
+ const index = this.includePickerIndex ?? this.blocks.length;
780
+ this.includePickerIndex = null;
781
+ this.addBlockAt(index, includeMarker(item.id, item.title));
782
+ }
783
+
688
784
  private closeImagePicker = () => {
689
785
  this.imagePickerIndex = null;
690
786
  };
@@ -731,6 +827,53 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
731
827
  return data.uploadPath;
732
828
  }
733
829
 
830
+ /**
831
+ * Resolves an app-relative path the way the console's pagelet handler does:
832
+ * an app fragment's URLs sit under the app base, and the console puts the app's
833
+ * `gaid` on the host element. Links rendered here live in the shadow root,
834
+ * where a click retargets to the host, so that handler never sees them — the
835
+ * href has to carry the base itself.
836
+ */
837
+ private appPath(path: string): string {
838
+ const gaid = this.getAttribute('gaid') ?? '';
839
+ const trimmed = path.replace(/^\/+/, '');
840
+ if (!gaid || trimmed.startsWith(gaid)) return path;
841
+ return `/${gaid}/${trimmed}`;
842
+ }
843
+
844
+ private hasIncludeBlock(): boolean {
845
+ return this.blocks.some(block => this.parseIncludeBlock(block) !== null);
846
+ }
847
+
848
+ /** Fetches the include list once; every later caller shares the same promise. */
849
+ private loadIncludeOptions(): Promise<IncludeOption[]> {
850
+ if (!this.includeUrl) return Promise.resolve([]);
851
+ if (!this.includeRequest) {
852
+ this.includeRequest = fetch(this.includeUrl, {headers: {Accept: 'application/json'}})
853
+ .then(res => {
854
+ if (!res.ok) throw new Error(`include list request failed: ${res.status}`);
855
+ return res.json() as Promise<{items?: IncludeOption[]}>;
856
+ })
857
+ .then(data => {
858
+ const items = data.items ?? [];
859
+ this.includeOptions = items;
860
+ this.includeLoadFailed = false;
861
+ return items;
862
+ })
863
+ .catch(error => {
864
+ // A list that never arrived is not evidence an embed is broken: leave
865
+ // the options unresolved so chips keep their marker labels, and let
866
+ // the next picker open try again.
867
+ console.error('[zn-remarkd-editor] include list failed', error);
868
+ this.includeOptions = null;
869
+ this.includeLoadFailed = true;
870
+ this.includeRequest = null;
871
+ return [] as IncludeOption[];
872
+ });
873
+ }
874
+ return this.includeRequest;
875
+ }
876
+
734
877
  private autosize(input: HTMLTextAreaElement) {
735
878
  input.style.height = 'auto';
736
879
  input.style.height = `${input.scrollHeight}px`;
@@ -796,6 +939,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
796
939
  const index = this.editingIndex !== null ? this.commitEdit() : this.blocks.length;
797
940
  if (item.image) {
798
941
  this.pickImage(index);
942
+ } else if (item.include) {
943
+ this.pickInclude(index);
799
944
  } else {
800
945
  this.insertDraftBlock(index, item.prefix ?? '');
801
946
  }
@@ -867,6 +1012,33 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
867
1012
  </div>`;
868
1013
  }
869
1014
 
1015
+ private renderIncludeChip(data: IncludeBlockData) {
1016
+ const option = this.includeOptions?.find(item => item.id === data.target);
1017
+ // A path-like target is remarkd's file include, which this editor cannot
1018
+ // resolve and must not call broken.
1019
+ const filePath = /[/.]/.test(data.target);
1020
+ const pending = !this.includeUrl || this.includeOptions === null;
1021
+ const missing = !option && !pending && !filePath;
1022
+ return html`
1023
+ <div part="include"
1024
+ class=${classMap({
1025
+ 'remarkd-editor__include': true,
1026
+ 'remarkd-editor__include--missing': missing,
1027
+ })}>
1028
+ <zn-icon src=${missing ? 'triangle-alert@lu' : 'blocks@lu'} size="16"></zn-icon>
1029
+ <span class="remarkd-editor__include-title">${option?.title || data.label || data.target}</span>
1030
+ ${option?.scope ? html`
1031
+ <span class="remarkd-editor__include-scope">${option.scope}</span>` : ''}
1032
+ <span class="remarkd-editor__include-meta">${missing
1033
+ ? `Include not found · ${data.target}`
1034
+ : !option && filePath ? 'File include' : 'Included content'}</span>
1035
+ ${option?.url ? html`
1036
+ <a class="remarkd-editor__include-link" href=${this.appPath(option.url)}
1037
+ target="_blank" rel="noreferrer"
1038
+ @click=${(e: MouseEvent) => e.stopPropagation()}>Open</a>` : ''}
1039
+ </div>`;
1040
+ }
1041
+
870
1042
  private renderBlock(block: string, index: number) {
871
1043
  if (this.editingIndex === index) {
872
1044
  if (this.imageEdit) {
@@ -890,6 +1062,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
890
1062
  </div>`;
891
1063
  }
892
1064
 
1065
+ const include = this.parseIncludeBlock(block);
893
1066
  return html`
894
1067
  <div part="block"
895
1068
  class=${classMap({
@@ -914,7 +1087,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
914
1087
  tooltip="Delete block"
915
1088
  @click=${() => this.deleteBlock(index)}></zn-button>`}
916
1089
  <div part="rendered" class="remarkd-editor__rendered remarkd-rendered"
917
- @click=${(e: MouseEvent) => this.handleRenderedClick(e, index)}>${unsafeHTML(remarkdParse(block))}
1090
+ @click=${(e: MouseEvent) => this.handleRenderedClick(e, index)}>${
1091
+ include ? this.renderIncludeChip(include) : unsafeHTML(remarkdParse(block))}
918
1092
  </div>
919
1093
  </div>`;
920
1094
  }
@@ -932,7 +1106,9 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
932
1106
  @drop=${this.handleDrop}>
933
1107
  ${editable ? html`
934
1108
  <div part="toolbar" class="remarkd-editor__toolbar">
935
- ${this.rawMode ? '' : BLOCK_TYPES.map(item => html`
1109
+ ${this.rawMode ? '' : BLOCK_TYPES
1110
+ .filter(item => !item.include || !!this.includeUrl)
1111
+ .map(item => html`
936
1112
  <zn-button type="button" icon-button plain icon=${item.icon} icon-size="18"
937
1113
  tooltip=${item.label}
938
1114
  @click=${() => this.handleToolbarInsert(item)}></zn-button>`)}
@@ -954,7 +1130,10 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
954
1130
  </div>`}
955
1131
  </div>
956
1132
  ${this.hasSlashMenu ? html`
957
- <zn-slash-menu part="slash-menu" heading="Insert block"></zn-slash-menu>` : ''}
1133
+ <zn-slash-menu
1134
+ part="slash-menu"
1135
+ heading="Insert block"
1136
+ recent-key=${this.slashRecentKey}></zn-slash-menu>` : ''}
958
1137
  <textarea class="remarkd-editor__validation"
959
1138
  .value=${this.value}
960
1139
  ?required=${this.required}
@@ -980,9 +1159,45 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
980
1159
  if (this.imagePickerIndex !== null) {
981
1160
  views.splice(this.imagePickerIndex, 0, this.renderImagePicker());
982
1161
  }
1162
+ if (this.includePickerIndex !== null) {
1163
+ views.splice(this.includePickerIndex, 0, this.renderIncludePicker());
1164
+ }
983
1165
  return views;
984
1166
  }
985
1167
 
1168
+ private renderIncludePicker() {
1169
+ const filter = this.includeQuery.trim().toLowerCase();
1170
+ const items = (this.includeOptions ?? []).filter(item => !filter || [
1171
+ item.title, item.description ?? '', (item.keywords ?? []).join(' '),
1172
+ ].some(field => field.toLowerCase().includes(filter)));
1173
+ return html`
1174
+ <div part="include-picker" class="remarkd-editor__include-picker">
1175
+ <div class="remarkd-editor__include-picker-head">
1176
+ <input class="remarkd-editor__include-filter"
1177
+ placeholder="Find an Include"
1178
+ .value=${this.includeQuery}
1179
+ @input=${(e: Event) => {
1180
+ this.includeQuery = (e.target as HTMLInputElement).value;
1181
+ }}>
1182
+ <zn-button type="button" icon-button="small" plain icon="x@lu"
1183
+ tooltip="Cancel" @click=${this.closeIncludePicker}></zn-button>
1184
+ </div>
1185
+ ${this.includeOptions === null
1186
+ ? html`<div class="remarkd-editor__include-picker-empty">${
1187
+ this.includeLoadFailed ? 'Could not load Includes' : 'Loading…'}</div>`
1188
+ : items.length
1189
+ ? items.map(item => html`
1190
+ <button type="button" class="remarkd-editor__include-option"
1191
+ ?disabled=${!item.languages}
1192
+ @click=${() => this.insertInclude(item)}>
1193
+ <span class="remarkd-editor__include-option-title">${item.title}</span>
1194
+ <span class="remarkd-editor__include-option-meta">${item.scope ?? ''}${
1195
+ item.languages ? ` · ${item.languages}` : ' · No content'}</span>
1196
+ </button>`)
1197
+ : html`<div class="remarkd-editor__include-picker-empty">No Includes found</div>`}
1198
+ </div>`;
1199
+ }
1200
+
986
1201
  private renderImagePicker() {
987
1202
  return html`
988
1203
  <div class="remarkd-editor__image-picker">
@@ -224,6 +224,87 @@
224
224
  }
225
225
  }
226
226
 
227
+ .remarkd-editor__include {
228
+ display: flex;
229
+ align-items: center;
230
+ gap: var(--zn-spacing-small);
231
+ padding: var(--zn-spacing-small);
232
+ border: 1px dashed rgb(var(--zn-border-color));
233
+ border-radius: var(--zn-border-radius);
234
+ background: var(--zn-color-neutral-0, white);
235
+ }
236
+
237
+ .remarkd-editor__include-title {
238
+ font-weight: 600;
239
+ }
240
+
241
+ .remarkd-editor__include-meta {
242
+ color: var(--zn-color-neutral-400, #9aa4b2);
243
+ font-size: var(--zn-font-size-small);
244
+ }
245
+
246
+ .remarkd-editor__include--missing {
247
+ border-style: solid;
248
+ border-color: rgb(var(--zn-color-error));
249
+ }
250
+
251
+ .remarkd-editor__include-scope {
252
+ color: var(--zn-color-neutral-400, #9aa4b2);
253
+ font-size: var(--zn-font-size-small);
254
+ }
255
+
256
+ .remarkd-editor__include-link {
257
+ margin-left: auto;
258
+ font-size: var(--zn-font-size-small);
259
+ }
260
+
261
+ .remarkd-editor__include-picker {
262
+ display: flex;
263
+ flex-direction: column;
264
+ gap: var(--zn-spacing-x-small);
265
+ padding: var(--zn-spacing-small);
266
+ border: 1px solid rgb(var(--zn-border-color));
267
+ border-radius: var(--zn-border-radius);
268
+ background: var(--zn-color-neutral-0, white);
269
+ }
270
+
271
+ .remarkd-editor__include-picker-head {
272
+ display: flex;
273
+ align-items: center;
274
+ gap: var(--zn-spacing-x-small);
275
+ }
276
+
277
+ .remarkd-editor__include-filter {
278
+ flex: 1 1 auto;
279
+ }
280
+
281
+ .remarkd-editor__include-option {
282
+ display: flex;
283
+ justify-content: space-between;
284
+ gap: var(--zn-spacing-small);
285
+ padding: var(--zn-spacing-x-small) var(--zn-spacing-small);
286
+ border: 0;
287
+ border-radius: var(--zn-border-radius);
288
+ background: none;
289
+ cursor: pointer;
290
+ text-align: left;
291
+
292
+ &:hover:not(:disabled) {
293
+ background: var(--zn-color-neutral-100);
294
+ }
295
+
296
+ &:disabled {
297
+ cursor: default;
298
+ opacity: 0.6;
299
+ }
300
+ }
301
+
302
+ .remarkd-editor__include-option-meta,
303
+ .remarkd-editor__include-picker-empty {
304
+ color: var(--zn-color-neutral-400, #9aa4b2);
305
+ font-size: var(--zn-font-size-small);
306
+ }
307
+
227
308
  .remarkd-editor__image-controls {
228
309
  display: flex;
229
310
  flex-direction: column;