@jackuait/blok 0.23.0 → 0.23.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.
- package/dist/blok.cjs +1 -1
- package/dist/blok.iife.js +5 -5
- package/dist/blok.mjs +2 -2
- package/dist/blok.umd.js +5 -5
- package/dist/chunks/{blok-BmckXjmW.mjs → blok-BPeM6wKv.mjs} +33 -1
- package/dist/chunks/{blok-D4WXeg3t.cjs → blok-Ci1zCOUg.cjs} +4 -4
- package/dist/chunks/{constants-BbCnmzAr.cjs → constants-Bkncy0y5.cjs} +1 -1
- package/dist/chunks/{constants-tXKHkDAG.mjs → constants-DR3sya6U.mjs} +1 -1
- package/dist/chunks/{tools-oXMAa9zD.mjs → tools-B72rczVw.mjs} +4 -1
- package/dist/chunks/{tools-tG5_UaWz.cjs → tools-DlOZpcVU.cjs} +1 -1
- package/dist/full.cjs +1 -1
- package/dist/full.mjs +3 -3
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +21 -21
- package/dist/tools.cjs +1 -1
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/blok.ts +43 -0
- package/src/components/block/index.ts +13 -0
- package/src/components/modules/blockManager/blockManager.ts +19 -0
- package/src/components/tools/base.ts +9 -0
- package/src/react/BlokEditor.tsx +36 -11
- package/src/react/config-keys.ts +51 -0
- package/src/react/types.ts +1 -0
- package/src/react/useBlok.ts +9 -0
- package/src/tools/paragraph/index.ts +20 -0
- package/types/api/index.d.ts +1 -0
- package/types/api/placeholder.d.ts +18 -0
- package/types/index.d.ts +5 -0
- package/types/react.d.ts +7 -5
package/dist/full.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-Ci1zCOUg.cjs`),t=require(`./chunks/constants-Bkncy0y5.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-DlOZpcVU.cjs`);var i={paragraph:{class:r.k,inlineToolbar:!0,config:{preserveBlank:!0}},header:{class:r.O,inlineToolbar:!0},list:{class:r.D,inlineToolbar:!0}},a=n.t(n.t({},i),{},{bold:{class:r.l},italic:{class:r.c},marker:{class:r.o},underline:{class:r.a},strikethrough:{class:r.i},link:{class:r.s}});exports.Blok=e.t,exports.Bold=r.l,exports.DATA_ATTR=t.ja,exports.Header=r.O,exports.Italic=r.c,exports.Link=r.s,exports.List=r.D,exports.Marker=r.o,exports.Paragraph=r.k,exports.Strikethrough=r.i,exports.Toggle=r.T,exports.Underline=r.a,exports.allTools=a,exports.defaultBlockTools=r.t,exports.defaultInlineTools=r.n,exports.defaultTools=i,exports.version=e.n;
|
package/dist/full.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as e, t } from "./chunks/blok-
|
|
2
|
-
import { ja as n } from "./chunks/constants-
|
|
1
|
+
import { n as e, t } from "./chunks/blok-BPeM6wKv.mjs";
|
|
2
|
+
import { ja as n } from "./chunks/constants-DR3sya6U.mjs";
|
|
3
3
|
import { t as r } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
4
|
-
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-
|
|
4
|
+
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-B72rczVw.mjs";
|
|
5
5
|
//#region src/full.ts
|
|
6
6
|
var g = {
|
|
7
7
|
paragraph: {
|
package/dist/react.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-Ci1zCOUg.cjs`),t=require(`./chunks/objectSpread2-iNkE6aJh.cjs`);let n=require(`react`),r=require(`react/jsx-runtime`);var i=new WeakMap;function a(e,t){i.set(e,t)}function o(e){return i.get(e)}function s(e){i.delete(e)}function c(r,i){let[o,c]=(0,n.useState)(null),u=(0,n.useRef)(r);u.current=r;let d=(0,n.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,depsToken:null}),f=(0,n.useMemo)(()=>({}),i==null?[]:i);(0,n.useEffect)(()=>{if(typeof window>`u`)return;let n=d.current;if(n.destroyTimeout!==null&&(clearTimeout(n.destroyTimeout),n.destroyTimeout=null),n.editor!==null&&!n.isDestroyed&&n.depsToken===f)return c(n.editor),()=>{l(n,c)};if(n.editor!==null&&!n.isDestroyed){s(n.editor);try{n.editor.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,c(null)}let r=document.createElement(`div`);n.holder=r,n.isDestroyed=!1,n.depsToken=f;let i=u.current,o=new e.t(t.t(t.t({},i),{},{holder:r,onReady:()=>{var e,t;(e=(t=u.current).onReady)==null||e.call(t)},onChange:(...e)=>{var t,n;(t=(n=u.current).onChange)==null||t.call(n,...e)}}));return n.editor=o,a(o,r),o.isReady.then(()=>{n.editor===o&&!n.isDestroyed&&c(o)}).catch(()=>{if(n.editor===o&&!n.isDestroyed){s(o);try{o.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,c(null)}}),()=>{l(n,c)}},[f]);let{readOnly:p}=r;(0,n.useEffect)(()=>{o!==null&&o.readOnly.set(p==null?!1:p)},[o,p]);let{autofocus:m}=r;(0,n.useEffect)(()=>{o===null||!m||o.focus()},[o,m]);let{theme:h}=r;(0,n.useEffect)(()=>{o===null||h===void 0||o.theme.set(h)},[o,h]);let{width:g}=r;(0,n.useEffect)(()=>{o===null||g===void 0||o.width.set(g)},[o,g]);let{placeholder:_}=r;return(0,n.useEffect)(()=>{o===null||_===void 0||o.placeholder.set(_)},[o,_]),o}function l(e,t){e.destroyTimeout=setTimeout(()=>{if(e.editor!==null){s(e.editor);try{e.editor.destroy()}catch(e){}e.editor=null,e.holder=null,e.isDestroyed=!0,e.destroyTimeout=null,t(null)}},0)}var u=[`editor`],d=(0,n.forwardRef)(function(i,a){let{editor:s}=i,c=e.i(i,u),l=(0,n.useRef)(null);return(0,n.useEffect)(()=>{if(s===null||l.current===null)return;let e=o(s);if(e!==void 0)return l.current.appendChild(e),()=>{e.remove()}},[s]),(0,r.jsx)(`div`,t.t({ref:e=>{if(l.current=e,typeof a==`function`)a(e);else if(a!=null){let t=a;t.current=e}}},c))}),f=`autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.linkPaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width`.split(`.`),p=[`deps`,`onReady`],m=new Set(f),h=(0,n.forwardRef)(function(i,a){let{deps:o,onReady:s}=i,l=e.i(i,p),u={},f={};for(let e of Object.keys(l)){let t=l[e];m.has(e)?u[e]=t:f[e]=t}let h=c(u,o);(0,n.useImperativeHandle)(a,()=>h,[h]);let g=(0,n.useRef)(s);return g.current=s,(0,n.useEffect)(()=>{if(h!==null){var e;(e=g.current)==null||e.call(g,h)}},[h]),(0,r.jsx)(d,t.t({editor:h},f))});exports.BlokContent=d,exports.BlokEditor=h,exports.useBlok=c;
|
package/dist/react.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as e, t } from "./chunks/blok-
|
|
1
|
+
import { i as e, t } from "./chunks/blok-BPeM6wKv.mjs";
|
|
2
2
|
import { t as n } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
3
3
|
import { forwardRef as r, useEffect as i, useImperativeHandle as a, useMemo as o, useRef as s, useState as c } from "react";
|
|
4
4
|
import { jsx as l } from "react/jsx-runtime";
|
|
@@ -78,9 +78,13 @@ function m(e, r) {
|
|
|
78
78
|
a === null || v === void 0 || a.theme.set(v);
|
|
79
79
|
}, [a, v]);
|
|
80
80
|
let { width: y } = e;
|
|
81
|
-
|
|
81
|
+
i(() => {
|
|
82
82
|
a === null || y === void 0 || a.width.set(y);
|
|
83
|
-
}, [a, y])
|
|
83
|
+
}, [a, y]);
|
|
84
|
+
let { placeholder: b } = e;
|
|
85
|
+
return i(() => {
|
|
86
|
+
a === null || b === void 0 || a.placeholder.set(b);
|
|
87
|
+
}, [a, b]), a;
|
|
84
88
|
}
|
|
85
89
|
function h(e, t) {
|
|
86
90
|
e.destroyTimeout = setTimeout(() => {
|
|
@@ -110,25 +114,21 @@ var g = ["editor"], _ = r(function(t, r) {
|
|
|
110
114
|
t.current = e;
|
|
111
115
|
}
|
|
112
116
|
} }, o));
|
|
113
|
-
}), v = [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let
|
|
120
|
-
a(
|
|
121
|
-
let
|
|
122
|
-
return
|
|
123
|
-
if (
|
|
117
|
+
}), v = /* @__PURE__ */ "autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.linkPaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width".split("."), y = ["deps", "onReady"], b = new Set(v), x = r(function(t, r) {
|
|
118
|
+
let { deps: o, onReady: c } = t, u = e(t, y), d = {}, f = {};
|
|
119
|
+
for (let e of Object.keys(u)) {
|
|
120
|
+
let t = u[e];
|
|
121
|
+
b.has(e) ? d[e] = t : f[e] = t;
|
|
122
|
+
}
|
|
123
|
+
let p = m(d, o);
|
|
124
|
+
a(r, () => p, [p]);
|
|
125
|
+
let h = s(c);
|
|
126
|
+
return h.current = c, i(() => {
|
|
127
|
+
if (p !== null) {
|
|
124
128
|
var e;
|
|
125
|
-
(e =
|
|
129
|
+
(e = h.current) == null || e.call(h, p);
|
|
126
130
|
}
|
|
127
|
-
}, [
|
|
128
|
-
editor: d,
|
|
129
|
-
className: o,
|
|
130
|
-
"data-testid": u
|
|
131
|
-
});
|
|
131
|
+
}, [p]), /* @__PURE__ */ l(_, n({ editor: p }, f));
|
|
132
132
|
});
|
|
133
133
|
//#endregion
|
|
134
|
-
export { _ as BlokContent,
|
|
134
|
+
export { _ as BlokContent, x as BlokEditor, m as useBlok };
|
package/dist/tools.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-Bkncy0y5.cjs`),t=require(`./chunks/tools-DlOZpcVU.cjs`);exports.Audio=t.g,exports.Bold=t.l,exports.Bookmark=t.u,exports.Callout=t.w,exports.Code=t.y,exports.Column=t.p,exports.ColumnList=t.m,exports.Columns=t.f,exports.Convert=e.m,exports.Database=t.C,exports.DatabaseRow=t.S,exports.Divider=t.x,exports.Embed=t.d,exports.File=t._,exports.Header=t.O,exports.Image=t.v,exports.InlineCode=t.r,exports.Italic=t.c,exports.Link=t.s,exports.List=t.D,exports.Marker=t.o,exports.Paragraph=t.k,exports.Quote=t.b,exports.Strikethrough=t.i,exports.Table=t.E,exports.Toggle=t.T,exports.Underline=t.a,exports.Video=t.h,exports.defaultBlockTools=t.t,exports.defaultInlineTools=t.n;
|
package/dist/tools.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as e } from "./chunks/constants-
|
|
2
|
-
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-
|
|
1
|
+
import { m as e } from "./chunks/constants-DR3sya6U.mjs";
|
|
2
|
+
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-B72rczVw.mjs";
|
|
3
3
|
export { p as Audio, _ as Bold, T as Bookmark, D as Callout, k as Code, x as Column, v as ColumnList, f as Columns, e as Convert, t as Database, a as DatabaseRow, O as Divider, d as Embed, s as File, i as Header, E as Image, S as InlineCode, u as Italic, C as Link, n as List, b as Marker, g as Paragraph, l as Quote, h as Strikethrough, r as Table, o as Toggle, c as Underline, m as Video, w as defaultBlockTools, y as defaultInlineTools };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackuait/blok",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
|
|
5
5
|
"module": "dist/blok.mjs",
|
|
6
6
|
"main": "./dist/blok.cjs",
|
package/src/blok.ts
CHANGED
|
@@ -175,6 +175,39 @@ class Blok {
|
|
|
175
175
|
toggle: (): void => applyWidthMode(readWidthMode() === 'full' ? 'narrow' : 'full'),
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Placeholder API — exposed immediately (mirrors theme/width) so host apps
|
|
180
|
+
* can call instance.placeholder.set(...) before isReady without a silent
|
|
181
|
+
* no-op. The value is buffered and replayed once BlockManager is ready.
|
|
182
|
+
*/
|
|
183
|
+
const placeholderBuffer = { pending: null as string | false | null };
|
|
184
|
+
|
|
185
|
+
const getBlockManager = (): BlokModules['BlockManager'] | undefined =>
|
|
186
|
+
(blok.moduleInstances as Partial<BlokModules>).BlockManager;
|
|
187
|
+
|
|
188
|
+
const applyPlaceholder = (value: string | false): void => {
|
|
189
|
+
placeholderBuffer.pending = value;
|
|
190
|
+
|
|
191
|
+
const bm = getBlockManager();
|
|
192
|
+
|
|
193
|
+
if (bm !== undefined) {
|
|
194
|
+
bm.setPlaceholder(value);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
(this as Record<string, unknown>).placeholder = {
|
|
199
|
+
get: (): string | false => {
|
|
200
|
+
if (placeholderBuffer.pending !== null) {
|
|
201
|
+
return placeholderBuffer.pending;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const cfg = this.initialConfiguration;
|
|
205
|
+
|
|
206
|
+
return isObject(cfg) ? ((cfg as BlokConfig).placeholder ?? false) : false;
|
|
207
|
+
},
|
|
208
|
+
set: (value: string | false): void => applyPlaceholder(value),
|
|
209
|
+
};
|
|
210
|
+
|
|
178
211
|
/**
|
|
179
212
|
* We need to export isReady promise in the constructor
|
|
180
213
|
* as it can be used before other API methods are exported
|
|
@@ -247,6 +280,16 @@ class Blok {
|
|
|
247
280
|
widthBuffer.pendingMode = null;
|
|
248
281
|
}
|
|
249
282
|
|
|
283
|
+
// Apply any placeholder buffered before isReady resolved.
|
|
284
|
+
if (placeholderBuffer.pending !== null) {
|
|
285
|
+
const bm = (blok.moduleInstances as Partial<BlokModules>).BlockManager;
|
|
286
|
+
|
|
287
|
+
if (bm !== undefined) {
|
|
288
|
+
bm.setPlaceholder(placeholderBuffer.pending);
|
|
289
|
+
}
|
|
290
|
+
placeholderBuffer.pending = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
250
293
|
// Scroll to the block referenced by the URL hash, if present.
|
|
251
294
|
// isReady resolves only after all blocks are in the DOM (requestIdleCallback fence in Renderer),
|
|
252
295
|
// so no extra polling is needed even on slow connections.
|
|
@@ -610,6 +610,19 @@ export class Block extends EventsDispatcher<BlockEvents> {
|
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
/**
|
|
614
|
+
* Update the block's placeholder in place (reactive editor.placeholder API).
|
|
615
|
+
* Delegates to the tool instance if it implements `setPlaceholder`. No-op otherwise.
|
|
616
|
+
* @param value - new placeholder text, or false to clear it
|
|
617
|
+
*/
|
|
618
|
+
public setPlaceholder(value: string | false): void {
|
|
619
|
+
const tool = this.toolInstance as unknown as { setPlaceholder?: (v: string | false) => void };
|
|
620
|
+
|
|
621
|
+
if (typeof tool.setPlaceholder === 'function') {
|
|
622
|
+
tool.setPlaceholder(value);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
613
626
|
/**
|
|
614
627
|
* Tool could specify several entries to be displayed at the Toolbox (for example, "Heading 1", "Heading 2", "Heading 3")
|
|
615
628
|
* This method returns the entry that is related to the Block (depended on the Block data)
|
|
@@ -153,6 +153,25 @@ export class BlockManager extends Module {
|
|
|
153
153
|
return this.repository.blocks;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Apply a new editor-level placeholder to existing blocks and future ones.
|
|
158
|
+
* Backs the reactive `editor.placeholder` API: mutates the shared config,
|
|
159
|
+
* updates the default tool adapter (so blocks created afterward use it), and
|
|
160
|
+
* sweeps existing blocks for an in-place update.
|
|
161
|
+
* @param value - new placeholder text, or false to clear it
|
|
162
|
+
*/
|
|
163
|
+
public setPlaceholder(value: string | false): void {
|
|
164
|
+
this.config.placeholder = value;
|
|
165
|
+
|
|
166
|
+
const defaultToolName = this.config.defaultBlock ?? 'paragraph';
|
|
167
|
+
|
|
168
|
+
this.Blok.Tools.blockTools.get(defaultToolName)?.setDefaultPlaceholder(value);
|
|
169
|
+
|
|
170
|
+
for (const block of this.blocks) {
|
|
171
|
+
block.setPlaceholder(value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
/**
|
|
157
176
|
* Check if each Block is empty
|
|
158
177
|
* @returns {boolean}
|
|
@@ -191,6 +191,15 @@ export abstract class BaseToolAdapter<Type extends ToolType = ToolType, ToolClas
|
|
|
191
191
|
this.defaultPlaceholder = defaultPlaceholder;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Update the editor-level default placeholder. Used by the reactive
|
|
196
|
+
* `editor.placeholder` API so blocks created AFTER the change use the new value.
|
|
197
|
+
* @param value - new default placeholder, or false to disable it
|
|
198
|
+
*/
|
|
199
|
+
public setDefaultPlaceholder(value: string | false): void {
|
|
200
|
+
this.defaultPlaceholder = value;
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
/**
|
|
195
204
|
* Returns Tool user configuration.
|
|
196
205
|
* Extracts tool-specific options from flat config and merges with nested config.
|
package/src/react/BlokEditor.tsx
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
type DependencyList,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import { useBlok } from './useBlok';
|
|
3
10
|
import { BlokContent } from './BlokContent';
|
|
11
|
+
import { USE_BLOK_CONFIG_KEYS } from './config-keys';
|
|
4
12
|
import type { Blok } from '@/types';
|
|
5
13
|
import type { UseBlokConfig } from './types';
|
|
6
14
|
|
|
7
15
|
/**
|
|
8
16
|
* Props for the all-in-one BlokEditor component.
|
|
9
17
|
* Accepts every useBlok config prop (except `onReady`, re-typed below to receive
|
|
10
|
-
* the ready instance)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
18
|
+
* the ready instance) AND every standard HTML div attribute, which is forwarded
|
|
19
|
+
* to the editor container element (mirrors `BlokContent`). `style` and `onChange`
|
|
20
|
+
* keep their editor-config meaning (they collide with div attributes of the same
|
|
21
|
+
* name), so the container is styled via `className` rather than an inline `style`.
|
|
13
22
|
*
|
|
14
23
|
* `data` is uncontrolled (seed-only): it sets the INITIAL content. After mount the
|
|
15
24
|
* editor owns the document — passing a new `data` reference does NOT reload it.
|
|
16
25
|
* Read content via `onChange` or the ref (`ref.current.save()`); replace it
|
|
17
26
|
* imperatively via `ref.current.render(newData)`.
|
|
18
27
|
*/
|
|
19
|
-
export interface BlokEditorProps
|
|
28
|
+
export interface BlokEditorProps
|
|
29
|
+
extends Omit<UseBlokConfig, 'onReady'>,
|
|
30
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'onChange'> {
|
|
20
31
|
/** When any value changes, the editor is destroyed and recreated. */
|
|
21
32
|
deps?: DependencyList;
|
|
22
|
-
/** Class name applied to the editor container element. */
|
|
23
|
-
className?: string;
|
|
24
33
|
/** Test id forwarded to the editor container element (via data-testid). */
|
|
25
34
|
'data-testid'?: string;
|
|
26
35
|
/**
|
|
@@ -30,6 +39,9 @@ export interface BlokEditorProps extends Omit<UseBlokConfig, 'onReady'> {
|
|
|
30
39
|
onReady?: (editor: Blok) => void;
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
/** Fast membership test for partitioning props into editor config vs. div attributes. */
|
|
43
|
+
const CONFIG_KEY_SET = new Set<string>(USE_BLOK_CONFIG_KEYS);
|
|
44
|
+
|
|
33
45
|
/**
|
|
34
46
|
* The recommended way to embed Blok in React. Internally wires `useBlok` and
|
|
35
47
|
* `BlokContent`, and forwards a ref to the live `Blok` instance.
|
|
@@ -37,12 +49,25 @@ export interface BlokEditorProps extends Omit<UseBlokConfig, 'onReady'> {
|
|
|
37
49
|
* @example
|
|
38
50
|
* ```tsx
|
|
39
51
|
* const ref = useRef<Blok | null>(null);
|
|
40
|
-
* <BlokEditor ref={ref} tools={tools} data={data} theme={theme} onReady={(e) => e.focus()} />;
|
|
52
|
+
* <BlokEditor ref={ref} id="editor" tools={tools} data={data} theme={theme} onReady={(e) => e.focus()} />;
|
|
41
53
|
* ```
|
|
42
54
|
*/
|
|
43
55
|
export const BlokEditor = forwardRef<Blok | null, BlokEditorProps>(
|
|
44
|
-
function BlokEditor({ deps,
|
|
45
|
-
const
|
|
56
|
+
function BlokEditor({ deps, onReady, ...rest }, ref) {
|
|
57
|
+
const config: Record<string, unknown> = {};
|
|
58
|
+
const divProps: Record<string, unknown> = {};
|
|
59
|
+
|
|
60
|
+
for (const key of Object.keys(rest)) {
|
|
61
|
+
const value = (rest as Record<string, unknown>)[key];
|
|
62
|
+
|
|
63
|
+
if (CONFIG_KEY_SET.has(key)) {
|
|
64
|
+
config[key] = value;
|
|
65
|
+
} else {
|
|
66
|
+
divProps[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const editor = useBlok(config as UseBlokConfig, deps);
|
|
46
71
|
|
|
47
72
|
useImperativeHandle<Blok | null, Blok | null>(ref, () => editor, [editor]);
|
|
48
73
|
|
|
@@ -58,6 +83,6 @@ export const BlokEditor = forwardRef<Blok | null, BlokEditorProps>(
|
|
|
58
83
|
}
|
|
59
84
|
}, [editor]);
|
|
60
85
|
|
|
61
|
-
return <BlokContent editor={editor}
|
|
86
|
+
return <BlokContent editor={editor} {...(divProps as HTMLAttributes<HTMLDivElement>)} />;
|
|
62
87
|
}
|
|
63
88
|
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { UseBlokConfig } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keys that belong to the editor config (consumed by `useBlok`). Any prop on
|
|
5
|
+
* `BlokEditor` whose key is NOT in this set is treated as a container div
|
|
6
|
+
* attribute and spread onto the underlying element (mirrors `BlokContent`).
|
|
7
|
+
*
|
|
8
|
+
* Keep in sync with `UseBlokConfig`. The two compile-time guards below fail the
|
|
9
|
+
* `yarn lint:types` typecheck if this list drifts.
|
|
10
|
+
*/
|
|
11
|
+
export const USE_BLOK_CONFIG_KEYS = [
|
|
12
|
+
'autofocus',
|
|
13
|
+
'defaultBlock',
|
|
14
|
+
'dataModel',
|
|
15
|
+
'placeholder',
|
|
16
|
+
'sanitizer',
|
|
17
|
+
'hideToolbar',
|
|
18
|
+
'maxHistoryLength',
|
|
19
|
+
'historyDebounceTime',
|
|
20
|
+
'newGroupDelay',
|
|
21
|
+
'globalUndoRedo',
|
|
22
|
+
'tools',
|
|
23
|
+
'data',
|
|
24
|
+
'minHeight',
|
|
25
|
+
'logLevel',
|
|
26
|
+
'readOnly',
|
|
27
|
+
'i18n',
|
|
28
|
+
'linkPaste',
|
|
29
|
+
'onReady',
|
|
30
|
+
'onChange',
|
|
31
|
+
'inlineToolbar',
|
|
32
|
+
'tunes',
|
|
33
|
+
'style',
|
|
34
|
+
'theme',
|
|
35
|
+
'onThemeChange',
|
|
36
|
+
'scrollToBlock',
|
|
37
|
+
'user',
|
|
38
|
+
'resolveUser',
|
|
39
|
+
'notifierPosition',
|
|
40
|
+
'notifier',
|
|
41
|
+
'width',
|
|
42
|
+
] as const satisfies readonly (keyof UseBlokConfig)[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compile error if a `UseBlokConfig` key is missing from the list above.
|
|
46
|
+
* (`satisfies` only proves each entry is a valid key; this proves coverage.)
|
|
47
|
+
*/
|
|
48
|
+
type _MissingConfigKey = Exclude<keyof UseBlokConfig, (typeof USE_BLOK_CONFIG_KEYS)[number]>;
|
|
49
|
+
const _exhaustive: _MissingConfigKey extends never ? true : never = true;
|
|
50
|
+
|
|
51
|
+
void _exhaustive;
|
package/src/react/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { BlokConfig, Blok, EditorWidth } from '@/types';
|
|
|
10
10
|
* - `autofocus` — calls `editor.focus()` when changed to true
|
|
11
11
|
* - `theme` — calls `editor.theme.set(value)`
|
|
12
12
|
* - `width` — calls `editor.width.set(value)`
|
|
13
|
+
* - `placeholder` — calls `editor.placeholder.set(value)`
|
|
13
14
|
*/
|
|
14
15
|
export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
|
|
15
16
|
/** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */
|
package/src/react/useBlok.ts
CHANGED
|
@@ -164,6 +164,15 @@ export function useBlok(config: UseBlokConfig, deps?: DependencyList): Blok | nu
|
|
|
164
164
|
editor.width.set(width);
|
|
165
165
|
}, [editor, width]);
|
|
166
166
|
|
|
167
|
+
// Reactive: placeholder
|
|
168
|
+
const { placeholder } = config;
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (editor === null || placeholder === undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
editor.placeholder.set(placeholder);
|
|
174
|
+
}, [editor, placeholder]);
|
|
175
|
+
|
|
167
176
|
return editor;
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -321,6 +321,26 @@ export class Paragraph implements BlockTool {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Update the block's placeholder text in place (reactive editor.placeholder API).
|
|
326
|
+
* Updates internal state always; writes the live attribute only when editable
|
|
327
|
+
* and rendered. A read-only block picks up the new text on the next edit toggle.
|
|
328
|
+
*
|
|
329
|
+
* @param value - new placeholder text, or false to clear it
|
|
330
|
+
*/
|
|
331
|
+
public setPlaceholder(value: string | false): void {
|
|
332
|
+
this._placeholder = value === false ? '' : value;
|
|
333
|
+
|
|
334
|
+
if (!this._element || this.readOnly) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this._element.setAttribute(
|
|
339
|
+
'data-blok-placeholder-active',
|
|
340
|
+
value === false ? '' : this.api.i18n.t(this._placeholder)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
324
344
|
/**
|
|
325
345
|
* Method that specified how to merge two Text blocks.
|
|
326
346
|
* Called by Editor by backspace at the beginning of the Block
|
package/types/api/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes Blok's placeholder API for controlling the editor-level placeholder
|
|
3
|
+
* (the hint shown on the empty default block) at runtime.
|
|
4
|
+
*/
|
|
5
|
+
export interface Placeholder {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current editor placeholder.
|
|
8
|
+
* @returns the placeholder text, or `false` when disabled.
|
|
9
|
+
*/
|
|
10
|
+
get(): string | false;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sets the editor placeholder. Updates existing blocks in place and applies to
|
|
14
|
+
* blocks created afterward. Pass `false` to clear it.
|
|
15
|
+
* @param value - new placeholder text, or false to disable it
|
|
16
|
+
*/
|
|
17
|
+
set(value: string | false): void;
|
|
18
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
ThemeMode,
|
|
34
34
|
ResolvedTheme,
|
|
35
35
|
Width,
|
|
36
|
+
Placeholder,
|
|
36
37
|
} from './api';
|
|
37
38
|
|
|
38
39
|
import { OutputData } from './data-formats';
|
|
@@ -129,6 +130,7 @@ export {
|
|
|
129
130
|
ResolvedTheme,
|
|
130
131
|
Width,
|
|
131
132
|
EditorWidth,
|
|
133
|
+
Placeholder,
|
|
132
134
|
} from './api';
|
|
133
135
|
export {
|
|
134
136
|
BlockMutationType,
|
|
@@ -228,6 +230,8 @@ export interface PendingBlok {
|
|
|
228
230
|
theme: Theme;
|
|
229
231
|
/** Width API, exposed immediately after construction. */
|
|
230
232
|
width: Width;
|
|
233
|
+
/** Placeholder API, exposed immediately after construction. */
|
|
234
|
+
placeholder: Placeholder;
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/**
|
|
@@ -249,6 +253,7 @@ export class Blok {
|
|
|
249
253
|
public readOnly: ReadOnly;
|
|
250
254
|
public theme: Theme;
|
|
251
255
|
public width: Width;
|
|
256
|
+
public placeholder: Placeholder;
|
|
252
257
|
constructor(configuration?: BlokConfig|string);
|
|
253
258
|
|
|
254
259
|
/**
|
package/types/react.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type React from 'react';
|
|
|
11
11
|
* - `autofocus` — calls `editor.focus()` when changed to true
|
|
12
12
|
* - `theme` — calls `editor.theme.set(value)`
|
|
13
13
|
* - `width` — calls `editor.width.set(value)`
|
|
14
|
+
* - `placeholder` — calls `editor.placeholder.set(value)`
|
|
14
15
|
*
|
|
15
16
|
* All other config is consumed once at editor creation.
|
|
16
17
|
*/
|
|
@@ -64,14 +65,15 @@ export declare const BlokContent: React.ForwardRefExoticComponent<BlokContentPro
|
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Props for the BlokEditor component — all useBlok config (except `onReady`,
|
|
67
|
-
* re-typed to receive the ready instance) plus
|
|
68
|
-
* and an optional `deps` list
|
|
68
|
+
* re-typed to receive the ready instance) plus every standard HTML div attribute
|
|
69
|
+
* (forwarded to the container, like `BlokContent`) and an optional `deps` list
|
|
70
|
+
* that forces recreation when changed. `style`/`onChange` keep editor-config meaning.
|
|
69
71
|
*/
|
|
70
|
-
export interface BlokEditorProps
|
|
72
|
+
export interface BlokEditorProps
|
|
73
|
+
extends Omit<UseBlokConfig, 'onReady'>,
|
|
74
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, 'style' | 'onChange'> {
|
|
71
75
|
/** When any value changes, the editor is destroyed and recreated. */
|
|
72
76
|
deps?: React.DependencyList;
|
|
73
|
-
/** Class name applied to the editor container element. */
|
|
74
|
-
className?: string;
|
|
75
77
|
/** Test id forwarded to the editor container element (via data-testid). */
|
|
76
78
|
'data-testid'?: string;
|
|
77
79
|
/** Called once the editor is ready, with the live Blok instance (ref is also committed). */
|