UNPKG

slate-hyperscript

Version:

A hyperscript helper for creating Slate documents.

520 lines (506 loc) • 16.1 kB
import { Text, Range, Node, Element, isObject, createEditor as createEditor$1 } from 'slate'; function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * A weak map to hold anchor tokens. */ const ANCHORS = new WeakMap(); /** * A weak map to hold focus tokens. */ const FOCI = new WeakMap(); /** * A weak map to hold point tokens. */ const POINTS = new WeakMap(); /** * All tokens inherit from a single constructor for `instanceof` checking. */ class Token { constructor(props = {}) { _defineProperty(this, "offset", void 0); _defineProperty(this, "path", void 0); _defineProperty(this, "ref", void 0); const { offset, path, ref } = props; this.offset = offset; this.path = path; if (ref) { if (path != null) { throw new Error('The ref prop of a token cannot be used with the path prop.'); } this.ref = ref; } } } /** * Anchor tokens represent the selection's anchor point. */ class AnchorToken extends Token {} /** * Focus tokens represent the selection's focus point. */ class FocusToken extends Token {} /** * Point tokens represent arbitrary points. */ class PointToken extends Token {} /** * Add an anchor token to the end of a text node. */ function addAnchorToken(text, token) { const offset = text.text.length; let anchors = ANCHORS.get(text); if (!anchors) { anchors = new Set(); ANCHORS.set(text, anchors); } anchors.add([offset, token]); } /** * Get the associated anchor tokens for a text node. */ function getAnchors(text) { const anchors = ANCHORS.get(text); if (!anchors) return []; return Array.from(anchors.values(), ([offset, token]) => new AnchorToken(_objectSpread$2(_objectSpread$2({}, token), {}, { offset }))); } /** * Add a focus token to the end of a text node. */ function addFocusToken(text, token) { const offset = text.text.length; let foci = FOCI.get(text); if (!foci) { foci = new Set(); FOCI.set(text, foci); } foci.add([offset, token]); } /** * Get the associated focus tokens for a text node. */ function getFoci(text) { const foci = FOCI.get(text); if (!foci) return []; return Array.from(foci.values(), ([offset, token]) => new FocusToken(_objectSpread$2(_objectSpread$2({}, token), {}, { offset }))); } /** * Add a point token to the end of a text node. */ function addPointToken(text, token) { const offset = text.text.length; let points = POINTS.get(text); if (!points) { points = new Set(); POINTS.set(text, points); } points.add([offset, token]); } /** * Get the associated point tokens for a text node. */ function getPoints(text) { const points = POINTS.get(text); if (!points) return []; return Array.from(points.values(), ([offset, token]) => new PointToken(_objectSpread$2(_objectSpread$2({}, token), {}, { offset }))); } function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * Resolve the descedants of a node by normalizing the children that can be * passed into a hyperscript creator function. */ const STRINGS = new WeakSet(); function resolveDescendants(children) { const nodes = []; function addChild(child) { if (child == null || typeof child === 'boolean') { return; } const prev = nodes[nodes.length - 1]; if (typeof child === 'string' || typeof child === 'number') { const text = { text: child.toString() }; STRINGS.add(text); child = text; } if (Text.isText(child)) { const c = child; // HACK: fix typescript complaining if (Text.isText(prev) && STRINGS.has(prev) && STRINGS.has(c) && Text.equals(prev, c, { loose: true })) { prev.text += c.text; } else { nodes.push(c); } } else if (Element.isElement(child)) { nodes.push(child); } else if (child instanceof Token) { let n = nodes[nodes.length - 1]; if (!Text.isText(n)) { addChild(''); n = nodes[nodes.length - 1]; } if (child instanceof AnchorToken) { addAnchorToken(n, child); } else if (child instanceof FocusToken) { addFocusToken(n, child); } else if (child instanceof PointToken) { addPointToken(n, child); } } else { throw new Error(`Unexpected hyperscript child object: ${child}`); } } for (const child of children.flat(Infinity)) { addChild(child); } return nodes; } /** * Create an anchor token. */ function createAnchor(tagName, attributes, children) { return new AnchorToken(attributes); } /** * Create an anchor and a focus token. */ function createCursor(tagName, attributes, children) { return [new AnchorToken(attributes), new FocusToken(attributes)]; } /** * Create an `Element` object. */ function createElement(tagName, attributes, children) { return _objectSpread$1(_objectSpread$1({}, attributes), {}, { children: resolveDescendants(children) }); } /** * Create a focus token. */ function createFocus(tagName, attributes, children) { return new FocusToken(attributes); } /** * Create a fragment. */ function createFragment(tagName, attributes, children) { return resolveDescendants(children); } /** * Create a point token. */ function createPoint(tagName, attributes, children) { return new PointToken(attributes); } /** * Create a `Selection` object. */ function createSelection(tagName, attributes, children) { const anchor = children.find(c => c instanceof AnchorToken); const focus = children.find(c => c instanceof FocusToken); if (!anchor || anchor.offset == null || anchor.path == null) { throw new Error(`The <selection> hyperscript tag must have an <anchor> tag as a child with \`path\` and \`offset\` attributes defined.`); } if (!focus || focus.offset == null || focus.path == null) { throw new Error(`The <selection> hyperscript tag must have a <focus> tag as a child with \`path\` and \`offset\` attributes defined.`); } return _objectSpread$1({ anchor: { offset: anchor.offset, path: anchor.path }, focus: { offset: focus.offset, path: focus.path } }, attributes); } /** * Create a `Text` object. */ function createText(tagName, attributes, children) { const nodes = resolveDescendants(children); if (nodes.length > 1) { throw new Error(`The <text> hyperscript tag must only contain a single node's worth of children.`); } let [node] = nodes; if (node == null) { node = { text: '' }; } if (!Text.isText(node)) { throw new Error(` The <text> hyperscript tag can only contain text content as children.`); } // COMPAT: If they used the <text> tag we want to guarantee that it won't be // merge with other string children. STRINGS.delete(node); Object.assign(node, attributes); return node; } /** * Create a top-level `Editor` object. */ const createEditor = makeEditor => (tagName, attributes, children) => { const otherChildren = []; let selectionChild; for (const child of children) { if (Range.isRange(child)) { selectionChild = child; } else { otherChildren.push(child); } } const descendants = resolveDescendants(otherChildren); const selection = {}; const editor = makeEditor(); Object.assign(editor, attributes); editor.children = descendants; // Search the document's texts to see if any of them have tokens associated // that need incorporated into the selection. for (const [node, path] of Node.texts(editor)) { for (const { ref = selection, offset } of getAnchors(node)) { if (offset != null) { ref.anchor = { path, offset }; } } for (const { ref = selection, offset } of getFoci(node)) { if (offset != null) { ref.focus = { path, offset }; } } for (const { ref, offset } of getPoints(node)) { if (ref) { ref.offset = offset; ref.path = path; } } } if (selection.anchor && !selection.focus) { throw new Error(`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<anchor />\`. For collapsed selections, use \`<cursor />\` instead.`); } if (!selection.anchor && selection.focus) { throw new Error(`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<focus />\`. For collapsed selections, use \`<cursor />\` instead.`); } if (selectionChild != null) { editor.selection = selectionChild; } else if (Range.isRange(selection)) { editor.selection = selection; } return editor; }; const _excluded = ["__self", "__source"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * The default creators for Slate objects. */ const DEFAULT_CREATORS = { anchor: createAnchor, cursor: createCursor, editor: createEditor(createEditor$1), element: createElement, focus: createFocus, fragment: createFragment, point: createPoint, selection: createSelection, text: createText }; /** * Create a Slate hyperscript function with `options`. */ const createHyperscript = (options = {}) => { const { elements = {} } = options; const elementCreators = normalizeElements(elements); const creators = _objectSpread(_objectSpread(_objectSpread({}, DEFAULT_CREATORS), elementCreators), options.creators); const jsx = createFactory(creators); return jsx; }; /** * Create a Slate hyperscript function with `options`. */ const createFactory = creators => { const jsx = (tagName, attributes, ...children) => { if (isObject(attributes)) { const { __self, __source } = attributes, rest = _objectWithoutProperties(attributes, _excluded); attributes = rest; } if (typeof tagName === 'function') { return tagName(_objectSpread({ children }, attributes)); } const creator = creators[tagName]; if (!creator) { throw new Error(`No hyperscript creator found for tag: <${tagName}>`); } if (attributes == null) { attributes = {}; } if (!isObject(attributes)) { children = [attributes].concat(children); attributes = {}; } children = children.filter(child => Boolean(child)).flat(); const ret = creator(tagName, attributes, children); return ret; }; return jsx; }; /** * Normalize a dictionary of element shorthands into creator functions. */ const normalizeElements = elements => { const creators = {}; for (const tagName in elements) { const props = elements[tagName]; if (typeof props !== 'object') { throw new Error(`Properties specified for a hyperscript shorthand should be an object, but for the custom element <${tagName}> tag you passed: ${props}`); } creators[tagName] = (tagName, attributes, children) => { return createElement('element', _objectSpread(_objectSpread({}, props), attributes), children); }; } return creators; }; /** * Hyperscript point refs can be used to construct arbitrary points using the * ref prop of a <point /> tag. */ class HyperscriptPointRef { constructor() { _defineProperty(this, "path", void 0); _defineProperty(this, "offset", void 0); } point() { const { path, offset } = this; if (path == null || offset == null) { throw new Error('A HyperscriptPointRef must be passed as the ref prop of a <point /> tag that is used inside an <editor>.'); } return { path, offset }; } } /** * Hyperscript range refs can be used to construct arbitrary range using the ref * props of <anchor /> and <focus /> tags. */ class HyperscriptRangeRef { constructor() { _defineProperty(this, "anchor", void 0); _defineProperty(this, "focus", void 0); } range() { const { anchor, focus } = this; if (anchor == null) { throw new Error('A HyperscriptRangeRef must be passed as the ref prop of an <anchor /> tag that is used inside an <editor>.'); } if (focus == null) { throw new Error('A HyperscriptRangeRef must be passed as the ref prop of a <focus /> tag that is used inside an <editor>.'); } return { anchor, focus }; } } /** * The default hyperscript factory that ships with Slate, without custom tags. */ const jsx = createHyperscript(); export { HyperscriptPointRef, HyperscriptRangeRef, createEditor, createHyperscript, createText, jsx }; //# sourceMappingURL=index.es.js.map