@bloklabs/angular 1.16.1 → 2.1.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
@@ -49,9 +49,21 @@ onSave(data: OutputData) {
49
49
 
50
50
  The component also implements `ControlValueAccessor`, so it works with `[(ngModel)]` and reactive forms.
51
51
 
52
+ ## Page title
53
+
54
+ `<blok-title>` puts the page title in a place you choose, such as above your own page metadata. The editor needs `pageTitle` set: `<blok-title>` only moves the title. When `<blok-title>` is destroyed, the title goes back above the first block.
55
+
56
+ ```html
57
+ <!-- imports: [BlokEditorComponent, BlokTitleComponent] -->
58
+ <blok-title [editor]="ed.instance()"></blok-title>
59
+ <app-page-metadata></app-page-metadata>
60
+ <blok-editor #ed="blok" [config]="{ pageTitle: true }"></blok-editor>
61
+ ```
62
+
52
63
  ## Also exported
53
64
 
54
65
  - `BlokContentDirective` (`[blokContent]`) — the mount point, if you drive the editor yourself.
66
+ - `BlokTitleComponent` (`<blok-title>`) — places the page title in your own layout.
55
67
  - `injectBlocks()` — a reactive snapshot of the block tree plus a manipulation API.
56
68
  - `createAngularBlock` — author block tools as Angular components.
57
69
  - `provideBlok` / `BLOK_DEFAULT_CONFIG` — share default editor config through DI.
@@ -159,7 +159,7 @@ var BlokContentDirective = class BlokContentDirective {
159
159
  }
160
160
  static ɵfac = i0.ɵɵngDeclareFactory({
161
161
  minVersion: "12.0.0",
162
- version: "22.2.1",
162
+ version: "22.2.4",
163
163
  ngImport: i0,
164
164
  type: BlokContentDirective,
165
165
  deps: [],
@@ -167,7 +167,7 @@ var BlokContentDirective = class BlokContentDirective {
167
167
  });
168
168
  static ɵdir = i0.ɵɵngDeclareDirective({
169
169
  minVersion: "14.0.0",
170
- version: "22.2.1",
170
+ version: "22.2.4",
171
171
  type: BlokContentDirective,
172
172
  isStandalone: true,
173
173
  selector: "[blokContent]",
@@ -181,7 +181,7 @@ var BlokContentDirective = class BlokContentDirective {
181
181
  };
182
182
  i0.ɵɵngDeclareClassMetadata({
183
183
  minVersion: "12.0.0",
184
- version: "22.2.1",
184
+ version: "22.2.4",
185
185
  ngImport: i0,
186
186
  type: BlokContentDirective,
187
187
  decorators: [{
@@ -413,6 +413,7 @@ const EMPTY_API = {
413
413
  insertOutputData: () => [],
414
414
  splitBlock: () => null,
415
415
  insertInsideParent: () => null,
416
+ create: async () => null,
416
417
  render: async () => void 0,
417
418
  clear: async () => void 0,
418
419
  isSyncingFromYjs: () => false
@@ -779,9 +780,19 @@ const createBlocksApiForEditor = (editor, onRead = () => void 0) => {
779
780
  })();
780
781
  return created === void 0 || created === null ? null : getById(created.id);
781
782
  };
783
+ const create = async (spec = {}) => {
784
+ const { type, ...options } = spec;
785
+ try {
786
+ const created = await editor.blocks.create(type, options);
787
+ return getById(created.id);
788
+ } catch (error) {
789
+ if (error instanceof ToolNotFoundError || error instanceof Error && error.name === "BlockPlacementError") return null;
790
+ throw error;
791
+ }
792
+ };
782
793
  const render = (data) => editor.blocks.render(data);
783
794
  const clear = () => editor.blocks.clear();
784
- const exportMarkdown = () => editor.blocks.exportMarkdown();
795
+ const exportMarkdown = (options) => editor.blocks.exportMarkdown(options);
785
796
  const isSyncingFromYjs = () => {
786
797
  onRead();
787
798
  return editor.blocks.isSyncingFromYjs;
@@ -813,6 +824,7 @@ const createBlocksApiForEditor = (editor, onRead = () => void 0) => {
813
824
  insertOutputData,
814
825
  splitBlock,
815
826
  insertInsideParent,
827
+ create,
816
828
  render,
817
829
  clear,
818
830
  isSyncingFromYjs
@@ -856,8 +868,8 @@ function normalizeOutputBlock(block) {
856
868
  };
857
869
  }
858
870
  function equalsOutputBlock(a, b) {
859
- const { id: idA, lastEditedAt: _editedAtA, lastEditedBy: _editedByA, ...restA } = normalizeOutputBlock(a);
860
- const { id: idB, lastEditedAt: _editedAtB, lastEditedBy: _editedByB, ...restB } = normalizeOutputBlock(b);
871
+ const { id: idA, lastEditedAt: _editedAtA, lastEditedBy: _editedByA, lastEditedWith: _editedWithA, createdAt: _createdAtA, createdBy: _createdByA, createdWith: _createdWithA, ...restA } = normalizeOutputBlock(a);
872
+ const { id: idB, lastEditedAt: _editedAtB, lastEditedBy: _editedByB, lastEditedWith: _editedWithB, createdAt: _createdAtB, createdBy: _createdByB, createdWith: _createdWithB, ...restB } = normalizeOutputBlock(b);
861
873
  if (typeof idA === "string" && idA !== "" && typeof idB === "string" && idB !== "" && idA !== idB) return false;
862
874
  return deepEqual(restA, restB);
863
875
  }
@@ -873,7 +885,7 @@ function comparableBlocks(data, options) {
873
885
  function equalsOutputData(a, b, options) {
874
886
  const blocksA = comparableBlocks(a, options);
875
887
  const blocksB = comparableBlocks(b, options);
876
- return blocksA.length === blocksB.length && blocksA.every((block, index) => equalsOutputBlock(block, blocksB[index]));
888
+ return (a?.title || void 0) === (b?.title || void 0) && deepEqual(a?.icon ?? void 0, b?.icon ?? void 0) && blocksA.length === blocksB.length && blocksA.every((block, index) => equalsOutputBlock(block, blocksB[index]));
877
889
  }
878
890
  function createEmittedEchoWindow(capacity = 20) {
879
891
  const emitted = [];
@@ -901,11 +913,211 @@ function isEmptyValue(value) {
901
913
  if (typeof value === "object") return Object.values(value).every(isEmptyValue);
902
914
  return true;
903
915
  }
916
+ const COLOR_PRESETS = [
917
+ {
918
+ name: "gray",
919
+ text: "#787774",
920
+ bg: "#f1f1ef"
921
+ },
922
+ {
923
+ name: "brown",
924
+ text: "#9f6b53",
925
+ bg: "#f4eeee"
926
+ },
927
+ {
928
+ name: "orange",
929
+ text: "#d9730d",
930
+ bg: "#fbecdd"
931
+ },
932
+ {
933
+ name: "yellow",
934
+ text: "#cb9b00",
935
+ bg: "#fbf3db"
936
+ },
937
+ {
938
+ name: "green",
939
+ text: "#448361",
940
+ bg: "#edf3ec"
941
+ },
942
+ {
943
+ name: "blue",
944
+ text: "#337ea9",
945
+ bg: "#e7f3f8"
946
+ },
947
+ {
948
+ name: "purple",
949
+ text: "#9065b0",
950
+ bg: "#f6f3f9"
951
+ },
952
+ {
953
+ name: "pink",
954
+ text: "#c14c8a",
955
+ bg: "#f9f0f5"
956
+ },
957
+ {
958
+ name: "red",
959
+ text: "#d44c47",
960
+ bg: "#fdebec"
961
+ }
962
+ ];
963
+ const NUM = "[+-]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)";
964
+ const NUM_PCT = `${NUM}%?`;
965
+ const PCT = `${NUM}%`;
966
+ const HUE = `${NUM}(?:deg|grad|rad|turn)?`;
967
+ const COMMA = " *, *";
968
+ const SPACE = " +";
969
+ const SPACE_ALPHA = `(?: */ *${NUM_PCT})?`;
970
+ new RegExp(`^rgba?\\( *${NUM_PCT}${COMMA}${NUM_PCT}${COMMA}${NUM_PCT}(?:${COMMA}${NUM_PCT})? *\\)$`, "i");
971
+ new RegExp(`^rgba?\\( *${NUM_PCT}${SPACE}${NUM_PCT}${SPACE}${NUM_PCT}${SPACE_ALPHA} *\\)$`, "i");
972
+ new RegExp(`^hsla?\\( *${HUE}${COMMA}${PCT}${COMMA}${PCT}(?:${COMMA}${NUM_PCT})? *\\)$`, "i");
973
+ new RegExp(`^hsla?\\( *${HUE}${SPACE}${NUM_PCT}${SPACE}${NUM_PCT}${SPACE_ALPHA} *\\)$`, "i");
974
+ const DEFAULT_LINK_TARGET = "_blank";
975
+ const DEFAULT_LINK_REL = "nofollow";
976
+ const SAME_PAGE_LINK_TARGET = "_self";
977
+ const DEFAULT_HIGHLIGHT_BACKGROUND = "yellow";
978
+ new Set(COLOR_PRESETS.map((preset) => preset.name));
979
+ const PRESET_NAMES = COLOR_PRESETS.map((preset) => `"${preset.name}"`).join(", ");
980
+ const RICH_TEXT_MARKS = {
981
+ type: "object",
982
+ description: "Formatting on one run. Unknown keys drop, except custom `tag:<name>` marks.",
983
+ properties: {
984
+ bold: { const: true },
985
+ italic: { const: true },
986
+ underline: { const: true },
987
+ strikethrough: { const: true },
988
+ code: { const: true },
989
+ sup: {
990
+ const: true,
991
+ description: "Not with sub on one run."
992
+ },
993
+ sub: { const: true },
994
+ highlight: {
995
+ const: true,
996
+ description: `Stored as background "${DEFAULT_HIGHLIGHT_BACKGROUND}" unless background is set.`
997
+ },
998
+ color: {
999
+ type: "string",
1000
+ description: `${PRESET_NAMES}. Hex, rgb, hsl snap to the nearest; names and var() stay; other syntaxes drop.`
1001
+ },
1002
+ background: {
1003
+ type: "string",
1004
+ description: "Like color; page backgrounds drop."
1005
+ },
1006
+ link: {
1007
+ type: "object",
1008
+ description: `Default "${DEFAULT_LINK_TARGET}"/"${DEFAULT_LINK_REL}", "${SAME_PAGE_LINK_TARGET}" for #anchors.`,
1009
+ required: ["href"],
1010
+ additionalProperties: false,
1011
+ properties: {
1012
+ href: { type: "string" },
1013
+ target: { type: "string" },
1014
+ rel: { type: "string" }
1015
+ }
1016
+ }
1017
+ },
1018
+ patternProperties: { "^tag:": {
1019
+ type: "object",
1020
+ additionalProperties: { type: "string" }
1021
+ } }
1022
+ };
1023
+ const TEXT_SEGMENT = {
1024
+ type: "object",
1025
+ required: ["text"],
1026
+ additionalProperties: false,
1027
+ properties: {
1028
+ text: {
1029
+ type: "string",
1030
+ description: "Line breaks are \"\\n\"."
1031
+ },
1032
+ marks: RICH_TEXT_MARKS
1033
+ }
1034
+ };
1035
+ const EMBED_SEGMENT = {
1036
+ type: "object",
1037
+ required: ["embed"],
1038
+ additionalProperties: false,
1039
+ properties: {
1040
+ embed: { oneOf: [
1041
+ {
1042
+ type: "object",
1043
+ required: ["equation"],
1044
+ additionalProperties: false,
1045
+ properties: { equation: {
1046
+ type: "object",
1047
+ required: ["expression"],
1048
+ additionalProperties: false,
1049
+ properties: { expression: {
1050
+ type: "string",
1051
+ description: "LaTeX."
1052
+ } }
1053
+ } }
1054
+ },
1055
+ {
1056
+ type: "object",
1057
+ required: ["page"],
1058
+ additionalProperties: false,
1059
+ properties: { page: {
1060
+ type: "object",
1061
+ required: ["id"],
1062
+ additionalProperties: false,
1063
+ properties: { id: { type: "string" } }
1064
+ } }
1065
+ },
1066
+ {
1067
+ type: "object",
1068
+ required: ["html"],
1069
+ additionalProperties: false,
1070
+ properties: { html: {
1071
+ type: "string",
1072
+ description: "Kept verbatim."
1073
+ } }
1074
+ }
1075
+ ] },
1076
+ marks: RICH_TEXT_MARKS
1077
+ }
1078
+ };
1079
+ const richText = (description) => ({
1080
+ description,
1081
+ oneOf: [{
1082
+ type: "array",
1083
+ description: "Rich-text segments, in order. Preferred: this is the shape Blok saves.",
1084
+ items: { anyOf: [TEXT_SEGMENT, EMBED_SEGMENT] }
1085
+ }, {
1086
+ type: "string",
1087
+ description: "In saved data, inline HTML. An agent command stores a string as plain text, tags and all: use segments for formatting."
1088
+ }]
1089
+ });
1090
+ [
1091
+ "Rich text is a list of segments: { text, marks? } or { embed, marks? }. A plain string is plain text, HTML tags included.",
1092
+ `Marks: ${Object.keys(RICH_TEXT_MARKS.properties).join(", ")}, and tag:<name> for a custom inline tool. sup and sub cannot share a run.`,
1093
+ `highlight alone is stored as background "${DEFAULT_HIGHLIGHT_BACKGROUND}"; with a background, as that background. color and background take a preset: ${PRESET_NAMES}. Hex, rgb and hsl are stored as the nearest preset. A CSS colour name is lowercased and kept, and so is var(--name). Other syntaxes (oklch, lab, hwb, color()) are dropped, as are a transparent colour and a white or dark-grey page background.`,
1094
+ `A link needs href. With no target or rel it gets target "${DEFAULT_LINK_TARGET}" and rel "${DEFAULT_LINK_REL}"; an "#anchor" href gets target "${SAME_PAGE_LINK_TARGET}". A target or rel you give is kept, but in the editor a host link config replaces both.`,
1095
+ "Embeds: { equation: { expression } } is inline LaTeX; { page: { id } } mentions a page: id is the pageId in a page block's data, not the block id.",
1096
+ "text.format with unset [\"*\"] clears every mark; \"tag:*\" clears every custom mark.",
1097
+ "markdown.insert cannot write underline, sup, sub, highlight or colours: insert the text, then use text.format."
1098
+ ].join("\n");
904
1099
  const fillDefaults = (schema, data) => {
905
1100
  const result = {};
906
1101
  for (const key of Object.keys(schema)) result[key] = data[key] !== void 0 ? data[key] : schema[key].default;
907
1102
  return Object.freeze(result);
908
1103
  };
1104
+ const typeFor = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? { type: typeof value } : {};
1105
+ const describeFromPropSchema = (input) => {
1106
+ const rich = new Set(input.richTextFields ?? []);
1107
+ const entries = Object.entries(input.propSchema);
1108
+ return {
1109
+ summary: `${input.title ?? input.type} block`,
1110
+ data: {
1111
+ type: "object",
1112
+ additionalProperties: false,
1113
+ properties: Object.fromEntries(entries.map(([key, entry]) => [key, rich.has(key) ? richText(`Rich-text field "${key}".`) : {
1114
+ ...typeFor(entry.default),
1115
+ ...entry.values === void 0 ? {} : { enum: [...entry.values] }
1116
+ }]))
1117
+ },
1118
+ defaultData: Object.fromEntries(entries.map(([key, entry]) => [key, entry.default]))
1119
+ };
1120
+ };
909
1121
  const DATA_ATTR = {
910
1122
  interface: "data-blok-interface",
911
1123
  element: "data-blok-element",
@@ -917,6 +1129,12 @@ const DATA_ATTR = {
917
1129
  loading: "data-blok-loading",
918
1130
  loadingSkeleton: "data-blok-loading-skeleton",
919
1131
  skeletonBar: "data-blok-skeleton-bar",
1132
+ aiGap: "data-blok-ai-gap",
1133
+ aiReplaced: "data-blok-ai-replaced",
1134
+ aiConsumed: "data-blok-ai-consumed",
1135
+ aiRewriting: "data-blok-ai-rewriting",
1136
+ aiPreview: "data-blok-ai-preview",
1137
+ aiSlot: "data-blok-ai-slot",
920
1138
  version: "data-blok-version",
921
1139
  id: "data-blok-id",
922
1140
  pageId: "data-blok-page-id",
@@ -939,12 +1157,18 @@ const DATA_ATTR = {
939
1157
  readonly: "data-blok-readonly",
940
1158
  controlsHidden: "data-blok-controls-hidden",
941
1159
  toolbarHidden: "data-blok-toolbar-hidden",
1160
+ noControls: "data-blok-no-controls",
942
1161
  toolbarPosition: "data-blok-toolbar-position",
943
1162
  contentAlign: "data-blok-content-align",
944
1163
  rtl: "data-blok-rtl",
945
1164
  width: "data-blok-width",
946
1165
  nativeSelection: "data-blok-native-selection",
947
1166
  crossSelection: "data-blok-cross-selection",
1167
+ pageHeader: "data-blok-page-header",
1168
+ pageTitle: "data-blok-page-title",
1169
+ pageIconRow: "data-blok-page-icon-row",
1170
+ pageIcon: "data-blok-page-icon",
1171
+ pageAddIcon: "data-blok-page-add-icon",
948
1172
  dragging: "data-blok-dragging",
949
1173
  draggingMulti: "data-blok-dragging-multi",
950
1174
  duplicating: "data-blok-duplicating",
@@ -979,8 +1203,14 @@ const DATA_ATTR = {
979
1203
  popoverItem: "data-blok-popover-item",
980
1204
  popoverItemIcon: "data-blok-popover-item-icon",
981
1205
  popoverItemIconChevronRight: "data-blok-popover-item-icon-chevron-right",
1206
+ popoverItemMore: "data-blok-popover-item-more",
982
1207
  popoverItemTitle: "data-blok-popover-item-title",
983
1208
  popoverItemSecondaryTitle: "data-blok-popover-item-secondary-title",
1209
+ popoverItemValue: "data-blok-popover-item-value",
1210
+ popoverItemDropdownButton: "data-blok-popover-item-dropdown-button",
1211
+ popoverCover: "data-blok-popover-cover",
1212
+ popoverItemDescription: "data-blok-popover-item-description",
1213
+ popoverItemSearchSuffix: "data-blok-popover-item-search-suffix",
984
1214
  popoverItemActive: "data-blok-popover-item-active",
985
1215
  popoverItemChildrenOpen: "data-blok-popover-item-children-open",
986
1216
  popoverItemConfirmation: "data-blok-popover-item-confirmation",
@@ -1015,8 +1245,11 @@ const DATA_ATTR = {
1015
1245
  childStandIn: "data-blok-child-stand-in",
1016
1246
  mutationFree: "data-blok-mutation-free",
1017
1247
  chrome: "data-blok-chrome",
1248
+ annotation: "data-blok-annotation",
1018
1249
  keyboardOwner: "data-blok-keyboard-owner",
1250
+ contextMenuOwner: "data-blok-context-menu-owner",
1019
1251
  linkOwner: "data-blok-link-owner",
1252
+ selectionOwner: "data-blok-selection-owner",
1020
1253
  navigationFocused: "data-blok-navigation-focused",
1021
1254
  flipperNavigationTarget: "data-blok-flipper-navigation-target",
1022
1255
  inlineToolbar: "data-blok-inline-toolbar",
@@ -1352,6 +1585,17 @@ var BlokEditorComponent = class BlokEditorComponent {
1352
1585
  cfg.onBeforeRender = handlers.onBeforeRender;
1353
1586
  cfg.onAfterRender = handlers.onAfterRender;
1354
1587
  this.appliedHandlers = handlers;
1588
+ if (cfg.pageTitle) cfg.pageTitle = {
1589
+ ...typeof cfg.pageTitle === "object" ? cfg.pageTitle : {},
1590
+ onChange: (...args) => {
1591
+ const latest = this.escapeHatchConfig().pageTitle;
1592
+ if (latest !== null && typeof latest === "object" && latest.onChange !== void 0) this.ngZone.run(() => latest.onChange?.(...args));
1593
+ },
1594
+ onIconChange: (...args) => {
1595
+ const latest = this.escapeHatchConfig().pageTitle;
1596
+ if (latest !== null && typeof latest === "object" && latest.onIconChange !== void 0) this.ngZone.run(() => latest.onIconChange?.(...args));
1597
+ }
1598
+ };
1355
1599
  if (this.themeChange.observed) cfg.onThemeChange = (resolved) => this.ngZone.run(() => this.themeChange.emit(resolved));
1356
1600
  const beforePaste = this.onBeforePaste;
1357
1601
  if (beforePaste !== void 0) cfg.onBeforePaste = (html) => beforePaste(html);
@@ -1568,7 +1812,7 @@ var BlokEditorComponent = class BlokEditorComponent {
1568
1812
  }
1569
1813
  static ɵfac = i0.ɵɵngDeclareFactory({
1570
1814
  minVersion: "12.0.0",
1571
- version: "22.2.1",
1815
+ version: "22.2.4",
1572
1816
  ngImport: i0,
1573
1817
  type: BlokEditorComponent,
1574
1818
  deps: [],
@@ -1576,7 +1820,7 @@ var BlokEditorComponent = class BlokEditorComponent {
1576
1820
  });
1577
1821
  static ɵcmp = i0.ɵɵngDeclareComponent({
1578
1822
  minVersion: "14.0.0",
1579
- version: "22.2.1",
1823
+ version: "22.2.4",
1580
1824
  type: BlokEditorComponent,
1581
1825
  isStandalone: true,
1582
1826
  selector: "blok-editor",
@@ -1638,7 +1882,7 @@ var BlokEditorComponent = class BlokEditorComponent {
1638
1882
  };
1639
1883
  i0.ɵɵngDeclareClassMetadata({
1640
1884
  minVersion: "12.0.0",
1641
- version: "22.2.1",
1885
+ version: "22.2.4",
1642
1886
  ngImport: i0,
1643
1887
  type: BlokEditorComponent,
1644
1888
  decorators: [{
@@ -1692,6 +1936,60 @@ i0.ɵɵngDeclareClassMetadata({
1692
1936
  recreateKey: [{ type: Input }]
1693
1937
  }
1694
1938
  });
1939
+ var BlokTitleComponent = class BlokTitleComponent {
1940
+ host = inject(ElementRef).nativeElement;
1941
+ editor = null;
1942
+ ngOnChanges(changes) {
1943
+ const change = changes["editor"];
1944
+ if (change === void 0) return;
1945
+ release(change.previousValue);
1946
+ this.editor?.title.mount(this.host);
1947
+ }
1948
+ ngOnDestroy() {
1949
+ release(this.editor);
1950
+ }
1951
+ static ɵfac = i0.ɵɵngDeclareFactory({
1952
+ minVersion: "12.0.0",
1953
+ version: "22.2.4",
1954
+ ngImport: i0,
1955
+ type: BlokTitleComponent,
1956
+ deps: [],
1957
+ target: i0.ɵɵFactoryTarget.Component
1958
+ });
1959
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1960
+ minVersion: "14.0.0",
1961
+ version: "22.2.4",
1962
+ type: BlokTitleComponent,
1963
+ isStandalone: true,
1964
+ selector: "blok-title",
1965
+ inputs: { editor: "editor" },
1966
+ usesOnChanges: true,
1967
+ ngImport: i0,
1968
+ template: "",
1969
+ isInline: true,
1970
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1971
+ });
1972
+ };
1973
+ i0.ɵɵngDeclareClassMetadata({
1974
+ minVersion: "12.0.0",
1975
+ version: "22.2.4",
1976
+ ngImport: i0,
1977
+ type: BlokTitleComponent,
1978
+ decorators: [{
1979
+ type: Component,
1980
+ args: [{
1981
+ selector: "blok-title",
1982
+ standalone: true,
1983
+ changeDetection: ChangeDetectionStrategy.OnPush,
1984
+ template: ""
1985
+ }]
1986
+ }],
1987
+ propDecorators: { editor: [{ type: Input }] }
1988
+ });
1989
+ const release = (editor) => {
1990
+ const title = editor?.title;
1991
+ if (typeof title?.mount === "function") title.mount(null);
1992
+ };
1695
1993
  const RESERVED_STATICS = [
1696
1994
  "toolbox",
1697
1995
  "isReadOnlySupported",
@@ -1808,15 +2106,21 @@ function createAngularBlock(spec) {
1808
2106
  onNavigationEnter(event) {
1809
2107
  return spec.onNavigationEnter?.(event, this.blockApi) === true;
1810
2108
  }
2109
+ isReadOnly() {
2110
+ return spec.isReadOnly?.(this.blockApi) === true;
2111
+ }
2112
+ isDataReadOnly() {
2113
+ return spec.isDataReadOnly?.(this.blockApi) === true;
2114
+ }
1811
2115
  setToolbarAnchor = (element) => {
1812
2116
  this.anchorEl = element;
1813
2117
  };
1814
2118
  moved() {
1815
2119
  spec.onMoved?.(this.blockApi);
1816
2120
  }
1817
- removed() {
2121
+ removed(removal) {
1818
2122
  this.registry?.unregister(this.blockApi.id, this.hostEl ?? void 0);
1819
- spec.onRemoved?.(this.blockApi);
2123
+ spec.onRemoved?.(this.blockApi, removal);
1820
2124
  }
1821
2125
  destroy() {
1822
2126
  this.registry?.unregister(this.blockApi.id, this.hostEl ?? void 0);
@@ -1879,6 +2183,17 @@ function createAngularBlock(spec) {
1879
2183
  configurable: true
1880
2184
  });
1881
2185
  }
2186
+ if (spec.statics?.describe === void 0) Object.defineProperty(AngularBlockTool, "describe", {
2187
+ value: () => describeFromPropSchema({
2188
+ type: spec.type,
2189
+ title: spec.toolbox !== void 0 && !Array.isArray(spec.toolbox) ? spec.toolbox.title : void 0,
2190
+ propSchema: spec.propSchema,
2191
+ richTextFields: spec.statics?.richTextFields
2192
+ }),
2193
+ writable: true,
2194
+ enumerable: true,
2195
+ configurable: true
2196
+ });
1882
2197
  return AngularBlockTool;
1883
2198
  }
1884
2199
  const BLOCK_CHANGED_EVENT = "block changed";
@@ -1928,7 +2243,7 @@ function injectBlocks(editor, options = {}) {
1928
2243
  insertMany: (specs) => bound().insertMany(specs),
1929
2244
  insertTree: (spec) => bound().insertTree(spec),
1930
2245
  insertMarkdown: (markdown, options) => bound().insertMarkdown(markdown, options),
1931
- exportMarkdown: () => bound().exportMarkdown(),
2246
+ exportMarkdown: (options) => bound().exportMarkdown(options),
1932
2247
  move: (id, target) => bound().move(id, target),
1933
2248
  nest: (id, parentId) => bound().nest(id, parentId),
1934
2249
  unnest: (id) => bound().unnest(id),
@@ -1948,6 +2263,7 @@ function injectBlocks(editor, options = {}) {
1948
2263
  insertOutputData: (blocks, options) => bound().insertOutputData(blocks, options),
1949
2264
  splitBlock: (currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex) => bound().splitBlock(currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex),
1950
2265
  insertInsideParent: (parentId, insertIndex, childData) => bound().insertInsideParent(parentId, insertIndex, childData),
2266
+ create: (spec) => bound().create(spec),
1951
2267
  render: (data) => bound().render(data),
1952
2268
  clear: () => bound().clear(),
1953
2269
  isSyncingFromYjs: () => bound().isSyncingFromYjs()
@@ -1972,6 +2288,5 @@ function injectBlokReady(options = {}) {
1972
2288
  afterNextRender(read);
1973
2289
  return ready.asReadonly();
1974
2290
  }
1975
- export { BLOK_BLOCK_CONTEXT, BLOK_DEFAULT_CONFIG, BLOK_EDITOR_INSTANCE, BlokContentDirective, BlokEditorComponent, createAngularBlock, injectBlocks, injectBlokInstance, injectBlokReady, provideBlok };
2291
+ export { BLOK_BLOCK_CONTEXT, BLOK_DEFAULT_CONFIG, BLOK_EDITOR_INSTANCE, BlokContentDirective, BlokEditorComponent, BlokTitleComponent, createAngularBlock, injectBlocks, injectBlokInstance, injectBlokReady, provideBlok };
1976
2292
 
1977
- //# sourceMappingURL=bloklabs-angular.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloklabs/angular",
3
- "version": "1.16.1",
3
+ "version": "2.1.0",
4
4
  "description": "Angular block editor — official Angular bindings for Blok, the headless Notion-like rich text editor. Standalone components that render blocks and save to JSON (APF partial-Ivy bundle).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "JackUait",
@@ -36,7 +36,7 @@
36
36
  "@angular/common": ">=20 <23",
37
37
  "@angular/core": ">=20 <23",
38
38
  "@angular/forms": ">=20 <23",
39
- "@bloklabs/core": "1.16.1"
39
+ "@bloklabs/core": "2.1.0"
40
40
  },
41
41
  "module": "fesm2022/bloklabs-angular.mjs",
42
42
  "typings": "./index.d.ts",