kirby-types 1.3.0 → 1.4.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.
package/README.md CHANGED
@@ -297,18 +297,25 @@ type Parsed = ParseKirbyQuery<'page.children.filterBy("status", "published")'>;
297
297
  For ProseMirror-based Writer extensions (requires optional ProseMirror peer dependencies):
298
298
 
299
299
  ```ts
300
- import type { WriterMarkContext } from "kirby-types";
300
+ import type { WriterMarkExtension } from "kirby-types";
301
301
 
302
- // In a Writer mark extension
303
- class Bold {
304
- commands({ type, utils }: WriterMarkContext) {
302
+ // Define a custom mark extension
303
+ const highlight: WriterMarkExtension = {
304
+ button: {
305
+ icon: "highlight",
306
+ label: "Highlight",
307
+ },
308
+ commands({ type, utils }) {
305
309
  return () => utils.toggleMark(type);
306
- }
307
-
308
- inputRules({ type, utils }: WriterMarkContext) {
310
+ },
311
+ inputRules({ type, utils }) {
309
312
  return [utils.markInputRule(/\*\*([^*]+)\*\*$/, type)];
310
- }
311
- }
313
+ },
314
+ schema: {
315
+ parseDOM: [{ tag: "mark" }],
316
+ toDOM: () => ["mark", 0],
317
+ },
318
+ };
312
319
  ```
313
320
 
314
321
  ## API Reference
@@ -329,10 +336,15 @@ class Bold {
329
336
 
330
337
  | Type | Description |
331
338
  | --------------------------------------------------- | ---------------------------------- |
332
- | [`WriterUtils`](./src/panel/writer.d.ts) | ProseMirror commands and utilities |
339
+ | [`WriterEditor`](./src/panel/writer.d.ts) | Main editor instance |
340
+ | [`WriterExtensions`](./src/panel/writer.d.ts) | Extensions manager |
341
+ | [`WriterExtension`](./src/panel/writer.d.ts) | Generic extension interface |
342
+ | [`WriterMarkExtension`](./src/panel/writer.d.ts) | Mark extension interface |
343
+ | [`WriterNodeExtension`](./src/panel/writer.d.ts) | Node extension interface |
333
344
  | [`WriterMarkContext`](./src/panel/writer.d.ts) | Context for mark extensions |
334
345
  | [`WriterNodeContext`](./src/panel/writer.d.ts) | Context for node extensions |
335
346
  | [`WriterExtensionContext`](./src/panel/writer.d.ts) | Context for generic extensions |
347
+ | [`WriterUtils`](./src/panel/writer.d.ts) | ProseMirror commands and utilities |
336
348
 
337
349
  ### API
338
350
 
package/index.d.ts CHANGED
@@ -10,26 +10,7 @@ export type {
10
10
  } from "./src/blocks";
11
11
 
12
12
  // Blueprint types
13
- export type {
14
- KirbyAnyFieldProps,
15
- KirbyBlocksFieldProps,
16
- KirbyDateFieldProps,
17
- KirbyFieldProps,
18
- KirbyFieldsetGroup,
19
- KirbyFieldsetProps,
20
- KirbyFieldsetTab,
21
- KirbyFilesFieldProps,
22
- KirbyLayoutFieldProps,
23
- KirbyNumberFieldProps,
24
- KirbyObjectFieldProps,
25
- KirbyOption,
26
- KirbyOptionsFieldProps,
27
- KirbyStructureColumn,
28
- KirbyStructureFieldProps,
29
- KirbyTextFieldProps,
30
- KirbyToggleFieldProps,
31
- KirbyWriterFieldProps,
32
- } from "./src/blueprint";
13
+ export type * from "./src/blueprint";
33
14
 
34
15
  // KQL types
35
16
  export type {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kirby-types",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "packageManager": "pnpm@10.27.0",
6
6
  "description": "TypeScript types for Kirby Panel plugins and headless CMS usage",
7
7
  "author": "Johann Schopplich <hello@johannschopplich.com>",
@@ -110,12 +110,12 @@ export interface KirbyFieldProps {
110
110
  // =============================================================================
111
111
 
112
112
  /**
113
- * Props for text and textarea fields.
113
+ * Props for text fields.
114
114
  *
115
115
  * @see https://getkirby.com/docs/reference/panel/fields/text
116
116
  */
117
117
  export interface KirbyTextFieldProps extends KirbyFieldProps {
118
- type: "text" | "textarea" | "slug" | "url" | "email" | "tel";
118
+ type: "text" | "slug" | "url" | "email" | "tel";
119
119
  /** Value converter: `lower`, `upper`, `ucfirst`, `slug` */
120
120
  converter?: "lower" | "upper" | "ucfirst" | "slug";
121
121
  /** Whether to show character counter */
@@ -133,6 +133,34 @@ export interface KirbyTextFieldProps extends KirbyFieldProps {
133
133
  value?: string;
134
134
  }
135
135
 
136
+ /**
137
+ * Props for textarea fields.
138
+ *
139
+ * @see https://getkirby.com/docs/reference/panel/fields/textarea
140
+ */
141
+ export interface KirbyTextareaFieldProps extends KirbyFieldProps {
142
+ type: "textarea";
143
+ /** Format buttons: true/false or array of allowed buttons (headlines, italic, bold, link, email, file, code, ul, ol) */
144
+ buttons?: boolean | string[];
145
+ /** Whether to show character counter */
146
+ counter: boolean;
147
+ /** File picker options (query string or config object) */
148
+ files?: string | Record<string, any>;
149
+ /** Font family: `sans-serif` or `monospace` */
150
+ font: "sans-serif" | "monospace";
151
+ /** Maximum character length */
152
+ maxlength?: number;
153
+ /** Minimum character length */
154
+ minlength?: number;
155
+ /** Textarea size */
156
+ size?: "small" | "medium" | "large" | "huge";
157
+ /** Whether spellcheck is enabled */
158
+ spellcheck: boolean;
159
+ /** Upload configuration */
160
+ uploads?: false | string | Record<string, any>;
161
+ value?: string;
162
+ }
163
+
136
164
  /**
137
165
  * Props for number fields.
138
166
  *
@@ -200,6 +228,8 @@ export interface KirbyDateFieldProps extends KirbyFieldProps {
200
228
  calendar?: boolean;
201
229
  /** Date/time display format (dayjs tokens) */
202
230
  display?: string;
231
+ /** Storage format for the value (from datetime mixin) */
232
+ format?: string;
203
233
  /** Maximum date/time */
204
234
  max?: string;
205
235
  /** Minimum date/time */
@@ -243,6 +273,8 @@ export interface KirbyFilesFieldProps extends KirbyFieldProps {
243
273
  image?: Record<string, any>;
244
274
  /** Info text template for each item */
245
275
  info?: string;
276
+ /** Display layout for selected items */
277
+ layout?: "list" | "cardlets" | "cards";
246
278
  /** Whether each item should be clickable */
247
279
  link?: boolean;
248
280
  /** Maximum number of items */
@@ -255,10 +287,16 @@ export interface KirbyFilesFieldProps extends KirbyFieldProps {
255
287
  query?: string;
256
288
  /** Whether to show search field in picker */
257
289
  search?: boolean;
290
+ /** Layout size for cards */
291
+ size?: "tiny" | "small" | "medium" | "large" | "huge" | "full" | "auto";
258
292
  /** Whether to store `"uuid"` or `"id"` in content file */
259
293
  store?: "uuid" | "id";
294
+ /** Include subpages in picker (pages field only) */
295
+ subpages?: boolean;
260
296
  /** Text template for each item */
261
297
  text?: string;
298
+ /** Upload configuration (files field only) */
299
+ uploads?: false | string | Record<string, any>;
262
300
  /** Selected items (transformed picker data, not raw IDs) */
263
301
  value?: KirbyPickerItem[];
264
302
  }
@@ -446,6 +484,8 @@ export interface KirbyBlocksFieldProps extends KirbyFieldProps {
446
484
  max?: number;
447
485
  /** Minimum number of blocks */
448
486
  min?: number;
487
+ /** Format JSON output with indentation */
488
+ pretty?: boolean;
449
489
  value?: KirbyBlockValue[];
450
490
  }
451
491
 
@@ -488,7 +528,7 @@ export interface KirbyLayoutFieldProps extends KirbyFieldProps {
488
528
  export interface KirbyWriterFieldProps extends KirbyFieldProps {
489
529
  type: "writer";
490
530
  /** Whether to show character counter */
491
- counter?: boolean;
531
+ counter: boolean;
492
532
  /** Available heading levels (1-6) */
493
533
  headings?: number[];
494
534
  /** Whether only inline formatting is allowed */
@@ -506,6 +546,61 @@ export interface KirbyWriterFieldProps extends KirbyFieldProps {
506
546
  value?: string;
507
547
  }
508
548
 
549
+ /**
550
+ * Props for entries fields.
551
+ * A simplified structure field for single-field entries.
552
+ *
553
+ * @since Kirby 5.0.0
554
+ * @see https://getkirby.com/docs/reference/panel/fields/entries
555
+ */
556
+ export interface KirbyEntriesFieldProps extends KirbyFieldProps {
557
+ type: "entries";
558
+ /** Placeholder text when no entries exist */
559
+ empty?: string;
560
+ /** Single field definition for entry items */
561
+ field: KirbyFieldProps;
562
+ /** Maximum number of entries */
563
+ max?: number;
564
+ /** Minimum number of entries */
565
+ min?: number;
566
+ /** Whether entries are sortable via drag & drop */
567
+ sortable?: boolean;
568
+ value?: any[];
569
+ }
570
+
571
+ /**
572
+ * Stats report item for the stats field.
573
+ */
574
+ export interface KirbyStatsReport {
575
+ /** Report label */
576
+ label: string;
577
+ /** Report value */
578
+ value: string | number;
579
+ /** Additional info text */
580
+ info?: string;
581
+ /** Link URL */
582
+ link?: string;
583
+ /** Icon identifier */
584
+ icon?: string;
585
+ /** Color theme */
586
+ theme?: string;
587
+ }
588
+
589
+ /**
590
+ * Props for stats fields.
591
+ * Display stats/metrics as cards.
592
+ *
593
+ * @since Kirby 5.1.0
594
+ * @see https://getkirby.com/docs/reference/panel/fields/stats
595
+ */
596
+ export interface KirbyStatsFieldProps extends KirbyFieldProps {
597
+ type: "stats";
598
+ /** Array of report objects or query string */
599
+ reports: KirbyStatsReport[] | string;
600
+ /** Card size */
601
+ size?: "tiny" | "small" | "medium" | "large";
602
+ }
603
+
509
604
  // =============================================================================
510
605
  // Block & Layout Values
511
606
  // =============================================================================
@@ -644,6 +739,7 @@ export interface KirbyFieldsetGroup {
644
739
  export type KirbyAnyFieldProps =
645
740
  | KirbyFieldProps
646
741
  | KirbyTextFieldProps
742
+ | KirbyTextareaFieldProps
647
743
  | KirbyNumberFieldProps
648
744
  | KirbyOptionsFieldProps
649
745
  | KirbyToggleFieldProps
@@ -655,6 +751,8 @@ export type KirbyAnyFieldProps =
655
751
  | KirbyLinkFieldProps
656
752
  | KirbyStructureFieldProps
657
753
  | KirbyObjectFieldProps
754
+ | KirbyEntriesFieldProps
658
755
  | KirbyBlocksFieldProps
659
756
  | KirbyLayoutFieldProps
660
- | KirbyWriterFieldProps;
757
+ | KirbyWriterFieldProps
758
+ | KirbyStatsFieldProps;
@@ -18,7 +18,15 @@
18
18
  * @since 4.0.0
19
19
  */
20
20
 
21
- import type { ComponentPublicInstance, VueConstructor } from "vue";
21
+ import type {
22
+ ComponentOptions,
23
+ ComponentPublicInstance,
24
+ PluginFunction,
25
+ PluginObject,
26
+ VNode,
27
+ VueConstructor,
28
+ h as VueH,
29
+ } from "vue";
22
30
  import type { PanelApi } from "./api";
23
31
  import type {
24
32
  PanelContext,
@@ -191,10 +199,8 @@ export type {
191
199
  WriterMarkContext,
192
200
  WriterNodeContext,
193
201
  WriterExtensionContext,
194
- // Writer Schemas
195
- WriterMarkSchema,
196
- WriterNodeSchema,
197
202
  // Writer Extensions
203
+ WriterExtension,
198
204
  WriterMarkExtension,
199
205
  WriterNodeExtension,
200
206
  } from "./writer";
@@ -224,6 +230,32 @@ export type PanelApp = InstanceType<VueConstructor> & {
224
230
  $esc: (string: string) => string;
225
231
  };
226
232
 
233
+ // =============================================================================
234
+ // Plugin Component Types
235
+ // =============================================================================
236
+
237
+ /**
238
+ * Vue component options for Panel plugin extensions.
239
+ *
240
+ * Components can be defined as:
241
+ * - Vue component options object with template or render function
242
+ * - Component that extends another component by name
243
+ */
244
+ export type PanelComponentExtension =
245
+ | ComponentPublicInstance
246
+ | ComponentOptions<any>
247
+ | {
248
+ /** Extend another component by name (e.g., `"k-text-field"`) */
249
+ extends?: string | ComponentPublicInstance;
250
+ /** Named mixins (e.g., `"dialog"`, `"drawer"`, `"section"`) or component objects */
251
+ mixins?: (string | ComponentOptions<any>)[];
252
+ /** Template string */
253
+ template?: string;
254
+ /** Render function */
255
+ render?: (h: typeof VueH) => VNode;
256
+ [key: string]: any;
257
+ };
258
+
227
259
  // =============================================================================
228
260
  // Panel Configuration
229
261
  // =============================================================================
@@ -423,6 +455,175 @@ export interface PanelRequestResponse {
423
455
  };
424
456
  }
425
457
 
458
+ // =============================================================================
459
+ // Panel Plugin Extensions (window.panel.plugin)
460
+ // =============================================================================
461
+
462
+ /**
463
+ * Extensions object passed to `window.panel.plugin()`.
464
+ *
465
+ * @example
466
+ * ```ts
467
+ * window.panel.plugin("my-plugin", {
468
+ * // Custom block types
469
+ * blocks: {
470
+ * video: `<k-block-video :source="content.source" />`
471
+ * },
472
+ *
473
+ * // Custom field types
474
+ * fields: {
475
+ * "color-picker": {
476
+ * extends: "k-text-field",
477
+ * template: `<k-field v-bind="$props">...</k-field>`
478
+ * }
479
+ * },
480
+ *
481
+ * // Custom sections
482
+ * sections: {
483
+ * stats: {
484
+ * template: `<div>{{ data }}</div>`
485
+ * }
486
+ * },
487
+ *
488
+ * // Textarea toolbar buttons
489
+ * textareaButtons: {
490
+ * timestamp: {
491
+ * label: "Insert Timestamp",
492
+ * icon: "clock",
493
+ * click() {
494
+ * this.command("insert", () => new Date().toISOString());
495
+ * }
496
+ * }
497
+ * },
498
+ *
499
+ * // Writer marks and nodes
500
+ * writerMarks: {
501
+ * highlight: {
502
+ * button: { icon: "highlight", label: "Highlight" },
503
+ * schema: {
504
+ * parseDOM: [{ tag: "mark" }],
505
+ * toDOM: () => ["mark", 0]
506
+ * }
507
+ * }
508
+ * }
509
+ * });
510
+ * ```
511
+ *
512
+ * @see https://getkirby.com/docs/reference/plugins/extensions
513
+ */
514
+ export interface PanelPluginExtensions {
515
+ /**
516
+ * Custom block types for the blocks field.
517
+ *
518
+ * Can be either a template string (shorthand) or a component options object.
519
+ * Registered as `k-block-type-${name}` components that automatically
520
+ * extend `k-block-type-default`.
521
+ *
522
+ * @see https://getkirby.com/docs/reference/plugins/extensions/blocks
523
+ */
524
+ blocks?: Record<string, string | PanelComponentExtension>;
525
+
526
+ /**
527
+ * Vue components to register globally in the Panel.
528
+ *
529
+ * @see https://getkirby.com/docs/reference/plugins/extensions/components
530
+ */
531
+ components?: Record<string, PanelComponentExtension>;
532
+
533
+ /**
534
+ * Custom field types.
535
+ *
536
+ * Registered as `k-${name}-field` components.
537
+ *
538
+ * @see https://getkirby.com/docs/reference/plugins/extensions/fields
539
+ */
540
+ fields?: Record<string, PanelComponentExtension>;
541
+
542
+ /**
543
+ * SVG icon definitions.
544
+ *
545
+ * @see https://getkirby.com/docs/reference/plugins/extensions/icons
546
+ */
547
+ icons?: Record<string, string>;
548
+
549
+ /**
550
+ * Custom section types.
551
+ *
552
+ * Registered as `k-${name}-section` components.
553
+ * The `section` mixin is automatically prepended to the mixins array.
554
+ *
555
+ * @see https://getkirby.com/docs/reference/plugins/extensions/sections
556
+ */
557
+ sections?: Record<string, PanelComponentExtension>;
558
+
559
+ /**
560
+ * View button components.
561
+ *
562
+ * Registered as `k-${name}-view-button` components.
563
+ *
564
+ * @see https://getkirby.com/docs/reference/plugins/extensions/view-buttons
565
+ */
566
+ viewButtons?: Record<string, PanelComponentExtension>;
567
+
568
+ /**
569
+ * Vue plugins to install via `Vue.use()`.
570
+ *
571
+ * Can be used to add global methods, directives, or mixins.
572
+ */
573
+ use?: Record<string, PluginObject<any> | PluginFunction<any>>;
574
+
575
+ /**
576
+ * Callback executed after the Panel Vue app is created.
577
+ *
578
+ * Receives the Vue app instance as parameter.
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * window.panel.plugin("my-plugin", {
583
+ * created(app) {
584
+ * console.log("Panel app created", app);
585
+ * }
586
+ * });
587
+ * ```
588
+ */
589
+ created?: (app: PanelApp) => void;
590
+
591
+ /**
592
+ * Custom login form component.
593
+ *
594
+ * Replaces the default login form with a custom implementation.
595
+ */
596
+ login?: PanelComponentExtension;
597
+
598
+ /**
599
+ * Custom textarea toolbar buttons.
600
+ *
601
+ * @see https://getkirby.com/docs/reference/plugins/extensions/textarea-buttons
602
+ */
603
+ textareaButtons?: Record<string, TextareaButton>;
604
+
605
+ /**
606
+ * Arbitrary third-party plugin data.
607
+ *
608
+ * Can be used to pass configuration to other plugins.
609
+ */
610
+ thirdParty?: Record<string, any>;
611
+
612
+ /**
613
+ * Custom Writer inline formatting marks.
614
+ *
615
+ * @see https://getkirby.com/docs/reference/plugins/extensions/writer-marks
616
+ */
617
+ writerMarks?: Record<string, WriterMarkExtension>;
618
+
619
+ /**
620
+ * Custom Writer block-level nodes.
621
+ *
622
+ * @see https://getkirby.com/docs/reference/plugins/extensions/writer-nodes
623
+ */
624
+ writerNodes?: Record<string, WriterNodeExtension>;
625
+ }
626
+
426
627
  // =============================================================================
427
628
  // Panel Plugins
428
629
  // =============================================================================
@@ -771,6 +972,37 @@ export interface Panel {
771
972
  */
772
973
  overlays: () => ("drawer" | "dialog")[];
773
974
 
975
+ /**
976
+ * Registers a Panel plugin with its extensions.
977
+ *
978
+ * @param name - Unique plugin identifier (typically vendor/plugin-name)
979
+ * @param extensions - Plugin extensions to register
980
+ *
981
+ * @example
982
+ * ```ts
983
+ * window.panel.plugin("my-plugin", {
984
+ * fields: {
985
+ * "color-picker": {
986
+ * extends: "k-text-field",
987
+ * template: `<k-field v-bind="$props">...</k-field>`
988
+ * }
989
+ * },
990
+ * textareaButtons: {
991
+ * timestamp: {
992
+ * label: "Insert Timestamp",
993
+ * icon: "clock",
994
+ * click() {
995
+ * this.command("insert", () => new Date().toISOString());
996
+ * }
997
+ * }
998
+ * }
999
+ * });
1000
+ * ```
1001
+ *
1002
+ * @see https://getkirby.com/docs/reference/plugins/extensions
1003
+ */
1004
+ plugin: (name: string, extensions: PanelPluginExtensions) => void;
1005
+
774
1006
  /**
775
1007
  * Sends a POST request through the Panel router.
776
1008
  *
@@ -149,12 +149,48 @@ export interface TextareaButton {
149
149
  */
150
150
  shortcut?: string;
151
151
 
152
+ /**
153
+ * Keyboard event handler for the button.
154
+ *
155
+ * Called when a key is pressed while the button is focused.
156
+ * This is different from `shortcut`, which is triggered globally
157
+ * with Cmd/Ctrl modifier.
158
+ *
159
+ * @param event - The native keyboard event
160
+ */
161
+ key?: (event: KeyboardEvent) => void;
162
+
152
163
  /**
153
164
  * Dropdown menu items.
154
165
  *
155
166
  * If provided, the button shows a dropdown instead of executing click directly.
156
167
  */
157
168
  dropdown?: TextareaDropdownItem[];
169
+
170
+ /**
171
+ * Conditional rendering. If false, the button won't be shown.
172
+ */
173
+ when?: boolean;
174
+
175
+ /**
176
+ * Disables the button.
177
+ */
178
+ disabled?: boolean;
179
+
180
+ /**
181
+ * Sets the aria-current attribute for active state styling.
182
+ */
183
+ current?: boolean | string;
184
+
185
+ /**
186
+ * Alternative tooltip text (defaults to label).
187
+ */
188
+ title?: string;
189
+
190
+ /**
191
+ * Custom CSS class for the button.
192
+ */
193
+ class?: string;
158
194
  }
159
195
 
160
196
  // =============================================================================
@@ -164,14 +200,23 @@ export interface TextareaButton {
164
200
  /**
165
201
  * A dropdown menu item for textarea toolbar buttons.
166
202
  *
203
+ * **Important:** Unlike the main button's `click` handler, dropdown item clicks
204
+ * are NOT called with the toolbar context as `this`. The `this` context is
205
+ * bound to the DropdownContent component which doesn't have `command()`.
206
+ *
207
+ * For this reason, dropdown items should use arrow functions and access
208
+ * the toolbar's functionality through closures or other means.
209
+ *
167
210
  * @example
168
211
  * ```js
212
+ * // Built-in buttons use arrow functions to capture toolbar's `this`
169
213
  * dropdown: [
170
214
  * {
171
215
  * label: "Option 1",
172
216
  * icon: "check",
173
- * click() {
174
- * this.command("insert", "text");
217
+ * click: () => {
218
+ * // Access toolbar methods through closure
219
+ * toolbar.command("insert", "text");
175
220
  * }
176
221
  * }
177
222
  * ]
@@ -182,11 +227,28 @@ export interface TextareaDropdownItem {
182
227
  label: string;
183
228
 
184
229
  /** Icon name */
185
- icon: string;
230
+ icon?: string;
186
231
 
187
232
  /**
188
- * Click handler. Called with `this` bound to the toolbar context.
189
- * Use a regular function (not arrow function) to access `this.command()`.
233
+ * Click handler.
234
+ *
235
+ * Note: Unlike main button clicks, `this` is NOT bound to the toolbar context.
236
+ * Use arrow functions and capture any needed references through closures.
190
237
  */
191
- click: (this: TextareaToolbarContext) => void;
238
+ click: () => void;
239
+
240
+ /**
241
+ * Conditional rendering. If false, the item won't be shown.
242
+ */
243
+ when?: boolean;
244
+
245
+ /**
246
+ * Disables the dropdown item.
247
+ */
248
+ disabled?: boolean;
249
+
250
+ /**
251
+ * Sets the aria-current attribute for active state styling.
252
+ */
253
+ current?: boolean | string;
192
254
  }
@@ -4,7 +4,6 @@
4
4
  * This module provides types for the Writer component, including:
5
5
  * - Editor instance and options
6
6
  * - Mark and node extensions for plugins
7
- * - ProseMirror schema wrappers
8
7
  * - Utility functions and contexts
9
8
  *
10
9
  * @see https://getkirby.com/docs/reference/plugins/extensions/writer-marks-nodes
@@ -14,7 +13,6 @@
14
13
 
15
14
  import type { InputRule } from "prosemirror-inputrules";
16
15
  import type {
17
- DOMOutputSpec,
18
16
  Fragment,
19
17
  Mark,
20
18
  MarkSpec,
@@ -51,6 +49,10 @@ import type {
51
49
  * @see https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Editor.js
52
50
  */
53
51
  export interface WriterEditor {
52
+ // ---------------------------------------------------------------------------
53
+ // Properties
54
+ // ---------------------------------------------------------------------------
55
+
54
56
  /** Currently active mark names */
55
57
  activeMarks: string[];
56
58
  /** Currently active mark attributes by mark name */
@@ -61,19 +63,41 @@ export interface WriterEditor {
61
63
  activeNodeAttrs: Record<string, Record<string, any>>;
62
64
  /** Available commands */
63
65
  commands: Record<string, (attrs?: any) => any>;
66
+ /** The DOM element the editor is mounted to */
67
+ element: HTMLElement | null;
68
+ /** Registered event handlers */
69
+ events: Record<string, (...args: any[]) => any>;
70
+ /** The extensions manager instance */
71
+ extensions: WriterExtensions;
64
72
  /** Whether the editor is focused */
65
73
  focused: boolean;
66
- /** Check if a mark or node is active, returns functions that accept optional attrs */
74
+ /** Active input rules */
75
+ inputRules: InputRule[];
76
+ /** Check if a mark or node is active */
67
77
  isActive: Record<string, (attrs?: Record<string, any>) => boolean>;
68
- /** ProseMirror marks registered in the schema */
69
- marks: Record<string, MarkType>;
70
- /** ProseMirror nodes registered in the schema */
71
- nodes: Record<string, NodeType>;
78
+ /** Keymap plugins */
79
+ keymaps: Plugin[];
80
+ /**
81
+ * Raw mark schema definitions.
82
+ *
83
+ * For ProseMirror MarkType instances, use `schema.marks` instead.
84
+ */
85
+ marks: Record<string, MarkSpec>;
86
+ /**
87
+ * Raw node schema definitions.
88
+ *
89
+ * For ProseMirror NodeType instances, use `schema.nodes` instead.
90
+ */
91
+ nodes: Record<string, NodeSpec>;
72
92
  /** Editor options */
73
93
  options: WriterEditorOptions;
94
+ /** Paste rule plugins */
95
+ pasteRules: Plugin[];
96
+ /** Custom ProseMirror plugins */
97
+ plugins: Plugin[];
74
98
  /** ProseMirror schema */
75
99
  schema: Schema;
76
- /** Current editor selection (ProseMirror Selection object) */
100
+ /** Current editor selection */
77
101
  selection: ProseMirrorSelection;
78
102
  /** Selection at the end of the document */
79
103
  selectionAtEnd: ProseMirrorSelection;
@@ -88,22 +112,41 @@ export interface WriterEditor {
88
112
  /** ProseMirror editor view */
89
113
  view: EditorView;
90
114
 
115
+ // ---------------------------------------------------------------------------
116
+ // Methods
117
+ // ---------------------------------------------------------------------------
118
+
91
119
  /** Removes focus from the editor */
92
120
  blur: () => void;
93
- /** Get available toolbar buttons */
121
+ /** Returns available toolbar buttons for the given type */
94
122
  buttons: (type: "mark" | "node") => Record<string, WriterToolbarButton>;
95
123
  /** Clears the editor content */
96
124
  clearContent: (emitUpdate?: boolean) => void;
97
125
  /** Executes a command by name */
98
126
  command: (command: string, ...args: any[]) => void;
127
+ /**
128
+ * Creates a ProseMirror document from content.
129
+ *
130
+ * @param content - HTML string, JSON object, or null for empty document
131
+ * @param parseOptions - Optional ProseMirror parse options
132
+ * @returns The created document node, or false if content type is unsupported
133
+ */
134
+ createDocument: (
135
+ content: string | Record<string, any> | null,
136
+ parseOptions?: Record<string, any>,
137
+ ) => ProseMirrorNode | false;
99
138
  /** Destroys the editor instance */
100
139
  destroy: () => void;
101
- /** Emits an event */
102
- emit: (event: string, ...args: any[]) => void;
103
- /** Focuses the editor */
140
+ /** Emits an event to all registered listeners */
141
+ emit: (event: string, ...args: any[]) => this;
142
+ /** Focuses the editor at the given position */
104
143
  focus: (position?: "start" | "end" | number | boolean | null) => void;
105
- /** Returns the current content as HTML */
106
- getHTML: () => string;
144
+ /**
145
+ * Returns content as HTML.
146
+ *
147
+ * @param fragment - Optional fragment to serialize (defaults to full document)
148
+ */
149
+ getHTML: (fragment?: Fragment) => string;
107
150
  /** Returns HTML content from start to current selection */
108
151
  getHTMLStartToSelection: () => string;
109
152
  /** Returns HTML content from current selection to end */
@@ -124,17 +167,39 @@ export interface WriterEditor {
124
167
  /** Checks if the editor is editable */
125
168
  isEditable: () => boolean;
126
169
  /** Checks if the editor is empty */
127
- isEmpty: () => boolean;
170
+ isEmpty: () => boolean | undefined;
171
+ /**
172
+ * Unsubscribes from events.
173
+ *
174
+ * @param event - Event name (omit to remove all listeners)
175
+ * @param fn - Specific handler to remove (omit to remove all for event)
176
+ */
177
+ off: (event?: string, fn?: (...args: any[]) => any) => this;
178
+ /**
179
+ * Subscribes to an event.
180
+ *
181
+ * @param event - Event name (e.g., "update", "focus", "blur", "transaction")
182
+ * @param fn - Event handler function
183
+ */
184
+ on: (event: string, fn: (...args: any[]) => any) => this;
128
185
  /** Removes a mark from the current selection */
129
- removeMark: (mark: string) => boolean;
186
+ removeMark: (mark: string) => boolean | undefined;
187
+ /**
188
+ * Returns selection at the given position.
189
+ *
190
+ * @param position - Position indicator or numeric position
191
+ */
192
+ selectionAtPosition: (
193
+ position?: "start" | "end" | number | boolean | null,
194
+ ) => ProseMirrorSelection | { from: number; to: number };
130
195
  /** Sets the editor content */
131
196
  setContent: (content?: any, emitUpdate?: boolean, parseOptions?: any) => void;
132
197
  /** Sets the selection range */
133
198
  setSelection: (from?: number, to?: number) => void;
134
199
  /** Toggles a mark on the current selection */
135
- toggleMark: (mark: string) => boolean;
200
+ toggleMark: (mark: string) => boolean | undefined;
136
201
  /** Updates a mark's attributes */
137
- updateMark: (mark: string, attrs: Record<string, any>) => boolean;
202
+ updateMark: (mark: string, attrs: Record<string, any>) => boolean | undefined;
138
203
  }
139
204
 
140
205
  /**
@@ -143,8 +208,8 @@ export interface WriterEditor {
143
208
  * @see https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Editor.js
144
209
  */
145
210
  export interface WriterEditorOptions {
146
- autofocus?: boolean | "start" | "end";
147
- content?: string | Record<string, any>;
211
+ autofocus?: boolean | "start" | "end" | number;
212
+ content?: string | Record<string, any> | null;
148
213
  disableInputRules?: boolean | string[];
149
214
  disablePasteRules?: boolean | string[];
150
215
  editable?: boolean;
@@ -158,6 +223,33 @@ export interface WriterEditorOptions {
158
223
  useBuiltInExtensions?: boolean;
159
224
  }
160
225
 
226
+ /**
227
+ * The extensions manager for the Writer editor.
228
+ *
229
+ * Manages all registered mark, node, and generic extensions.
230
+ *
231
+ * @see https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Extensions.js
232
+ */
233
+ export interface WriterExtensions {
234
+ /** All registered extension instances */
235
+ extensions: (WriterExtension | WriterMarkExtension | WriterNodeExtension)[];
236
+ /** ProseMirror EditorView (set after editor initialization) */
237
+ view: EditorView;
238
+
239
+ /** Returns toolbar buttons for the given type */
240
+ buttons: (type: "mark" | "node") => Record<string, WriterToolbarButton>;
241
+ /** Raw mark schema definitions from all mark extensions */
242
+ marks: Record<string, MarkSpec>;
243
+ /** Mark view constructors */
244
+ markViews: Record<string, WriterMarkExtension["view"]>;
245
+ /** Raw node schema definitions from all node extensions */
246
+ nodes: Record<string, NodeSpec>;
247
+ /** Node view constructors */
248
+ nodeViews: Record<string, WriterNodeExtension["view"]>;
249
+ /** Extension options with reactive proxy */
250
+ options: Record<string, Record<string, any>>;
251
+ }
252
+
161
253
  // =============================================================================
162
254
  // Writer Toolbar
163
255
  // =============================================================================
@@ -176,7 +268,7 @@ export interface WriterToolbarButton {
176
268
  command?: string;
177
269
  /** Icon name from Kirby's icon set */
178
270
  icon: string;
179
- /** Display label (usually translated via `window.panel.$t()`) */
271
+ /** Display label (usually translated via `window.panel.t()`) */
180
272
  label: string;
181
273
  /** Extension name this button belongs to */
182
274
  name?: string;
@@ -184,6 +276,8 @@ export interface WriterToolbarButton {
184
276
  attrs?: Record<string, any>;
185
277
  /** Show separator line after this button */
186
278
  separator?: boolean;
279
+ /** Whether this is an inline node button (shown inline, not in dropdown) */
280
+ inline?: boolean;
187
281
  /** Node types when this button should be visible */
188
282
  when?: string[];
189
283
  }
@@ -490,63 +584,106 @@ export interface WriterExtensionContext {
490
584
  }
491
585
 
492
586
  // =============================================================================
493
- // ProseMirror Schema Types
587
+ // Writer Generic Extension
494
588
  // =============================================================================
495
589
 
496
590
  /**
497
- * Mark schema specification for ProseMirror.
591
+ * A generic Writer extension (non-mark, non-node).
498
592
  *
499
- * @see https://prosemirror.net/docs/ref/#model.MarkSpec
500
- */
501
- export interface WriterMarkSchema extends Omit<MarkSpec, "parseDOM" | "toDOM"> {
502
- /** Attribute definitions with defaults */
503
- attrs?: Record<
504
- string,
505
- {
506
- default?: any;
507
- }
508
- >;
509
- /** DOM parsing rules */
510
- parseDOM?: {
511
- tag?: string;
512
- style?: string;
513
- priority?: number;
514
- consuming?: boolean;
515
- context?: string;
516
- getAttrs?: (
517
- node: HTMLElement | string,
518
- ) => Record<string, any> | false | null;
519
- }[];
520
- /** DOM serialization */
521
- toDOM?: (mark: Mark) => DOMOutputSpec;
522
- }
523
-
524
- /**
525
- * Node schema specification for ProseMirror.
593
+ * Generic extensions provide functionality like history (undo/redo),
594
+ * custom keyboard shortcuts, or other editor-wide features.
595
+ * They are registered via `window.panel.plugin("name", { writerExtensions: { ... } })`.
596
+ *
597
+ * @example
598
+ * ```js
599
+ * window.panel.plugin("my-plugin", {
600
+ * writerExtensions: {
601
+ * customKeys: {
602
+ * keys() {
603
+ * return {
604
+ * "Ctrl-s": () => {
605
+ * // Custom save handler
606
+ * return true;
607
+ * }
608
+ * };
609
+ * }
610
+ * }
611
+ * }
612
+ * });
613
+ * ```
526
614
  *
527
- * @see https://prosemirror.net/docs/ref/#model.NodeSpec
615
+ * @see https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Extension.js
528
616
  */
529
- export interface WriterNodeSchema extends Omit<NodeSpec, "parseDOM" | "toDOM"> {
530
- /** Attribute definitions with defaults */
531
- attrs?: Record<
532
- string,
533
- {
534
- default?: any;
535
- }
536
- >;
537
- /** DOM parsing rules */
538
- parseDOM?: {
539
- tag?: string;
540
- priority?: number;
541
- consuming?: boolean;
542
- context?: string;
543
- attrs?: Record<string, any>;
544
- getAttrs?: (node: HTMLElement) => Record<string, any> | false | null;
545
- getContent?: (node: HTMLElement, schema: Schema) => Fragment;
546
- preserveWhitespace?: boolean | "full";
547
- }[];
548
- /** DOM serialization */
549
- toDOM?: (node: ProseMirrorNode) => DOMOutputSpec;
617
+ export interface WriterExtension {
618
+ /**
619
+ * Unique name of the extension.
620
+ */
621
+ name?: string;
622
+
623
+ /** Extension type identifier */
624
+ type?: string;
625
+
626
+ /**
627
+ * The editor instance, available after `bindEditor()` is called.
628
+ */
629
+ editor?: WriterEditor;
630
+
631
+ /**
632
+ * Merged extension options from `defaults` and constructor options.
633
+ */
634
+ options?: Record<string, any>;
635
+
636
+ /**
637
+ * Default options for the extension.
638
+ */
639
+ defaults?: Record<string, any>;
640
+
641
+ /**
642
+ * Called after the editor is bound to the extension.
643
+ */
644
+ init?: () => null | void;
645
+
646
+ /**
647
+ * Commands provided by this extension.
648
+ *
649
+ * @param context - Context with schema and utils (no type for generic extensions)
650
+ * @returns A command function, or an object mapping command names to functions.
651
+ */
652
+ commands?: (
653
+ context: WriterExtensionContext,
654
+ ) => (() => any) | Record<string, (attrs?: any) => any>;
655
+
656
+ /**
657
+ * Additional ProseMirror plugins.
658
+ *
659
+ * @param context - Context with schema and utils
660
+ * @returns Array of ProseMirror plugins or plugin specs
661
+ */
662
+ plugins?: (context: WriterExtensionContext) => (Plugin | PluginSpec<any>)[];
663
+
664
+ /**
665
+ * Input rules for automatic formatting.
666
+ *
667
+ * @param context - Context with schema and utils
668
+ * @returns Array of ProseMirror input rules
669
+ */
670
+ inputRules?: (context: WriterExtensionContext) => InputRule[];
671
+
672
+ /**
673
+ * Paste rules for processing pasted content.
674
+ *
675
+ * @param context - Context with schema and utils
676
+ * @returns Array of ProseMirror plugins
677
+ */
678
+ pasteRules?: (context: WriterExtensionContext) => Plugin[];
679
+
680
+ /**
681
+ * Keyboard shortcuts.
682
+ *
683
+ * @param context - Context with schema and utils
684
+ * @returns Object mapping key combinations to command functions
685
+ */
686
+ keys?: (context: WriterExtensionContext) => Record<string, () => any>;
550
687
  }
551
688
 
552
689
  // =============================================================================
@@ -587,6 +724,39 @@ export interface WriterNodeSchema extends Omit<NodeSpec, "parseDOM" | "toDOM"> {
587
724
  * @see https://getkirby.com/docs/reference/plugins/extensions/writer-marks-nodes
588
725
  */
589
726
  export interface WriterMarkExtension {
727
+ // ---------------------------------------------------------------------------
728
+ // Instance Properties (available via `this` in extension methods)
729
+ // ---------------------------------------------------------------------------
730
+
731
+ /**
732
+ * Unique name of the mark extension.
733
+ *
734
+ * When using object literals with `window.panel.plugin()`, this is
735
+ * typically derived from the object key in `writerMarks`.
736
+ */
737
+ name?: string;
738
+
739
+ /** Extension type identifier */
740
+ type?: "mark";
741
+
742
+ /**
743
+ * The editor instance, available after `bindEditor()` is called.
744
+ *
745
+ * Use this to access editor methods like `emit()`, `toggleMark()`, etc.
746
+ */
747
+ editor?: WriterEditor;
748
+
749
+ /**
750
+ * Merged extension options from `defaults` and constructor options.
751
+ *
752
+ * Available at runtime after the extension is instantiated.
753
+ */
754
+ options?: Record<string, any>;
755
+
756
+ // ---------------------------------------------------------------------------
757
+ // Configuration
758
+ // ---------------------------------------------------------------------------
759
+
590
760
  /**
591
761
  * Toolbar button configuration.
592
762
  *
@@ -606,7 +776,7 @@ export interface WriterMarkExtension {
606
776
  *
607
777
  * Defines how the mark is parsed from and serialized to DOM.
608
778
  */
609
- schema?: WriterMarkSchema;
779
+ schema?: MarkSpec;
610
780
 
611
781
  /**
612
782
  * Commands provided by this extension.
@@ -719,6 +889,42 @@ export interface WriterMarkExtension {
719
889
  view: EditorView,
720
890
  inline: boolean,
721
891
  ) => { dom: HTMLElement; contentDOM?: HTMLElement };
892
+
893
+ // ---------------------------------------------------------------------------
894
+ // Lifecycle
895
+ // ---------------------------------------------------------------------------
896
+
897
+ /**
898
+ * Called after the editor is bound to the extension.
899
+ *
900
+ * Use this for initialization logic that requires access to `this.editor`.
901
+ */
902
+ init?: () => null | void;
903
+
904
+ // ---------------------------------------------------------------------------
905
+ // Mark Helper Methods (inherited from Mark base class)
906
+ // ---------------------------------------------------------------------------
907
+
908
+ /**
909
+ * Toggles this mark on the current selection.
910
+ *
911
+ * Shorthand for `this.editor.toggleMark(this.name)`.
912
+ */
913
+ toggle?: () => boolean | undefined;
914
+
915
+ /**
916
+ * Removes this mark from the current selection.
917
+ *
918
+ * Shorthand for `this.editor.removeMark(this.name)`.
919
+ */
920
+ remove?: () => void;
921
+
922
+ /**
923
+ * Updates the attributes of this mark.
924
+ *
925
+ * Shorthand for `this.editor.updateMark(this.name, attrs)`.
926
+ */
927
+ update?: (attrs: Record<string, any>) => void;
722
928
  }
723
929
 
724
930
  // =============================================================================
@@ -761,6 +967,39 @@ export interface WriterMarkExtension {
761
967
  * @see https://getkirby.com/docs/reference/plugins/extensions/writer-marks-nodes
762
968
  */
763
969
  export interface WriterNodeExtension {
970
+ // ---------------------------------------------------------------------------
971
+ // Instance Properties (available via `this` in extension methods)
972
+ // ---------------------------------------------------------------------------
973
+
974
+ /**
975
+ * Unique name of the node extension.
976
+ *
977
+ * When using object literals with `window.panel.plugin()`, this is
978
+ * typically derived from the object key in `writerNodes`.
979
+ */
980
+ name?: string;
981
+
982
+ /** Extension type identifier */
983
+ type?: "node";
984
+
985
+ /**
986
+ * The editor instance, available after `bindEditor()` is called.
987
+ *
988
+ * Use this to access editor methods like `emit()`, `command()`, etc.
989
+ */
990
+ editor?: WriterEditor;
991
+
992
+ /**
993
+ * Merged extension options from `defaults` and constructor options.
994
+ *
995
+ * Available at runtime after the extension is instantiated.
996
+ */
997
+ options?: Record<string, any>;
998
+
999
+ // ---------------------------------------------------------------------------
1000
+ // Configuration
1001
+ // ---------------------------------------------------------------------------
1002
+
764
1003
  /**
765
1004
  * Toolbar button configuration.
766
1005
  *
@@ -776,7 +1015,7 @@ export interface WriterNodeExtension {
776
1015
  /**
777
1016
  * ProseMirror node schema definition.
778
1017
  */
779
- schema?: WriterNodeSchema;
1018
+ schema?: NodeSpec;
780
1019
 
781
1020
  /**
782
1021
  * Commands provided by this extension.
@@ -834,4 +1073,15 @@ export interface WriterNodeExtension {
834
1073
  decorations: Decoration[],
835
1074
  innerDecorations: DecorationSource,
836
1075
  ) => NodeView;
1076
+
1077
+ // ---------------------------------------------------------------------------
1078
+ // Lifecycle
1079
+ // ---------------------------------------------------------------------------
1080
+
1081
+ /**
1082
+ * Called after the editor is bound to the extension.
1083
+ *
1084
+ * Use this for initialization logic that requires access to `this.editor`.
1085
+ */
1086
+ init?: () => null | void;
837
1087
  }