@inshapardaz/likhari-react 0.1.18-dev.128 → 0.1.18-dev.132

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PoetryNode.d.ts","sourceRoot":"","sources":["../../src/blocks/PoetryNode.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAKjE,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;gBAET,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,EAAE,GAAG,CAAC,EAAE,OAAO;IAM1F,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe;IAIpD,SAAS,IAAI,YAAY;IAIzB,QAAQ,IAAI,WAAW;IAIvB;;gEAE4D;IAC5D,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAM9C,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAMlC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,yBAAyB,GAAG,eAAe;IAI7E,UAAU,IAAI,yBAAyB;IAIvC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAa3C,SAAS,IAAI,eAAe;IAQ5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAY5C,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO;IAYrF,UAAU,IAAI,KAAK;IAInB,YAAY,IAAI,OAAO;CAGxB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,GAAG,eAAe,CAEvH;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,eAAe,CAEhG"}
1
+ {"version":3,"file":"PoetryNode.d.ts","sourceRoot":"","sources":["../../src/blocks/PoetryNode.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAejE,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;gBAET,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,EAAE,GAAG,CAAC,EAAE,OAAO;IAM1F,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe;IAIpD,SAAS,IAAI,YAAY;IAIzB,QAAQ,IAAI,WAAW;IAIvB;;gEAE4D;IAC5D,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAM9C,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAMlC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,yBAAyB,GAAG,eAAe;IAI7E,UAAU,IAAI,yBAAyB;IAIvC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAa3C,SAAS,IAAI,eAAe;IAQ5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAY5C,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO;IAYrF,UAAU,IAAI,KAAK;IAInB,YAAY,IAAI,OAAO;CAGxB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,GAAG,eAAe,CAEvH;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,eAAe,CAEhG"}
@@ -2,6 +2,15 @@ import { addClassNamesToElement } from '@lexical/utils';
2
2
  import { $applyNodeReplacement, ElementNode, } from 'lexical';
3
3
  const VALID_LAYOUTS = new Set(['single', 'two-column']);
4
4
  const VALID_ALIGNS = new Set(['justify', 'left', 'right', 'start']);
5
+ /** `text-align: justify` only stretches a line that actually wraps — a
6
+ * misra short enough to fit on one line would otherwise just sit at its own
7
+ * start edge. `text-align-last: center` centers that single/last line while
8
+ * still justifying any line that does wrap, which is what "justified
9
+ * couplets, centered on the page" means in practice for most real verse. */
10
+ function applyAlignStyle(element, align) {
11
+ element.style.textAlign = align;
12
+ element.style.textAlignLast = align === 'justify' ? 'center' : '';
13
+ }
5
14
  /**
6
15
  * A couplet (two-line verse unit), the one extensible primitive requirements
7
16
  * doc §4.11 recommends in place of four fixed templates. `layout` picks
@@ -66,14 +75,14 @@ export class PoetryBlockNode extends ElementNode {
66
75
  const element = document.createElement('div');
67
76
  element.setAttribute('data-likhari-poetry-layout', this.getLayout());
68
77
  element.setAttribute('data-likhari-poetry-align', this.getAlign());
69
- element.style.textAlign = this.getAlign();
78
+ applyAlignStyle(element, this.getAlign());
70
79
  return { element };
71
80
  }
72
81
  createDOM(config) {
73
82
  const element = document.createElement('div');
74
83
  const base = config.theme.poetry ?? 'likhari-poetry';
75
84
  addClassNamesToElement(element, base, `${base}--${this.__layout}`);
76
- element.style.textAlign = this.__align;
85
+ applyAlignStyle(element, this.__align);
77
86
  return element;
78
87
  }
79
88
  // Only ever adds/removes the layout modifier class, never touches the
@@ -87,7 +96,7 @@ export class PoetryBlockNode extends ElementNode {
87
96
  dom.classList.add(`${base}--${this.__layout}`);
88
97
  }
89
98
  if (prevNode.__align !== this.__align) {
90
- dom.style.textAlign = this.__align;
99
+ applyAlignStyle(dom, this.__align);
91
100
  }
92
101
  return false;
93
102
  }
@@ -16,4 +16,14 @@ export declare function $setPoetryLayout(node: PoetryBlockNode, layout: PoetryLa
16
16
  /** Inserts a new two-misra couplet after the selection's top-level block,
17
17
  * in `layout`/`align`, and focuses its first misra. */
18
18
  export declare function $insertPoetryCouplet(layout: PoetryLayout, align: PoetryAlign): boolean;
19
+ /**
20
+ * Enter at the end of a couplet's last misra would otherwise land inside
21
+ * ParagraphNode's own insertNewAfter — which inserts the new paragraph as a
22
+ * sibling of the misra, i.e. still inside the couplet (or, for two-column,
23
+ * inside its LayoutItemNode) — trapping the caret with no way to add a line
24
+ * after the block. Handled here instead: a plain paragraph after the whole
25
+ * couplet, focused. Returns false (let normal Enter handling proceed) for
26
+ * every other caret position, including earlier in the couplet.
27
+ */
28
+ export declare function $exitPoetryOnEnter(): boolean;
19
29
  //# sourceMappingURL=poetryActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"poetryActions.d.ts","sourceRoot":"","sources":["../../src/blocks/poetryActions.ts"],"names":[],"mappings":"AACA,OAAO,EAA4E,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvH,OAAO,EAA8C,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErI;wCACwC;AACxC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,EAAE,CAW1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,eAAe,GAAG,IAAI,CAKrE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAclF;AAED;uDACuD;AACvD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAmBtF"}
1
+ {"version":3,"file":"poetryActions.d.ts","sourceRoot":"","sources":["../../src/blocks/poetryActions.ts"],"names":[],"mappings":"AACA,OAAO,EAA4E,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvH,OAAO,EAA8C,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErI;wCACwC;AACxC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,EAAE,CAW1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,eAAe,GAAG,IAAI,CAKrE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAclF;AAED;uDACuD;AACvD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAmBtF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAoB5C"}
@@ -74,3 +74,33 @@ export function $insertPoetryCouplet(layout, align) {
74
74
  misraA.selectStart();
75
75
  return true;
76
76
  }
77
+ /**
78
+ * Enter at the end of a couplet's last misra would otherwise land inside
79
+ * ParagraphNode's own insertNewAfter — which inserts the new paragraph as a
80
+ * sibling of the misra, i.e. still inside the couplet (or, for two-column,
81
+ * inside its LayoutItemNode) — trapping the caret with no way to add a line
82
+ * after the block. Handled here instead: a plain paragraph after the whole
83
+ * couplet, focused. Returns false (let normal Enter handling proceed) for
84
+ * every other caret position, including earlier in the couplet.
85
+ */
86
+ export function $exitPoetryOnEnter() {
87
+ const selection = $getSelection();
88
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
89
+ return false;
90
+ const couplet = $getPoetryBlockFromSelection();
91
+ if (!couplet)
92
+ return false;
93
+ const misras = $getMisraParagraphs(couplet);
94
+ const lastMisra = misras[misras.length - 1];
95
+ if (!lastMisra)
96
+ return false;
97
+ const anchor = selection.anchor;
98
+ const lastDesc = lastMisra.getLastDescendant();
99
+ const isAtEnd = lastDesc ? anchor.key === lastDesc.getKey() && anchor.offset === lastDesc.getTextContentSize() : anchor.key === lastMisra.getKey();
100
+ if (!isAtEnd)
101
+ return false;
102
+ const paragraph = $createParagraphNode();
103
+ couplet.insertAfter(paragraph);
104
+ paragraph.select();
105
+ return true;
106
+ }
@@ -6,9 +6,12 @@ export declare const INSERT_POETRY_COUPLET_COMMAND: LexicalCommand<{
6
6
  }>;
7
7
  /**
8
8
  * Registers INSERT_POETRY_COUPLET_COMMAND (mirrors LayoutPlugin/FootnotePlugin's
9
- * shape) and a node transform that removes a couplet left with no misra
9
+ * shape), a node transform that removes a couplet left with no misra
10
10
  * content — e.g. after undo/paste leaves it structurally empty — rather
11
- * than letting an unselectable husk linger in the document.
11
+ * than letting an unselectable husk linger in the document, and a
12
+ * KEY_ENTER_COMMAND handler (above RichTextPlugin's own, which would
13
+ * otherwise trap Enter inside the couplet — see poetryActions.ts) that lets
14
+ * Enter at the end of the last misra add a paragraph after the block.
12
15
  */
13
16
  export declare function PoetryPlugin(): null;
14
17
  //# sourceMappingURL=PoetryPlugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PoetryPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PoetryPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAGtF,OAAO,EAAmB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG5F,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAEtG,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,SAqB3B"}
1
+ {"version":3,"file":"PoetryPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PoetryPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoF,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAGhI,OAAO,EAAmB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG5F,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAEtG,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,YAAY,SA+B3B"}
@@ -1,16 +1,19 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import { COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
3
+ import { COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, createCommand } from 'lexical';
4
4
  import { mergeRegister } from '@lexical/utils';
5
5
  import { LayoutContainerNode, LayoutItemNode } from '../blocks/LayoutNode.js';
6
6
  import { PoetryBlockNode } from '../blocks/PoetryNode.js';
7
- import { $insertPoetryCouplet } from '../blocks/poetryActions.js';
7
+ import { $exitPoetryOnEnter, $insertPoetryCouplet } from '../blocks/poetryActions.js';
8
8
  export const INSERT_POETRY_COUPLET_COMMAND = createCommand('INSERT_POETRY_COUPLET_COMMAND');
9
9
  /**
10
10
  * Registers INSERT_POETRY_COUPLET_COMMAND (mirrors LayoutPlugin/FootnotePlugin's
11
- * shape) and a node transform that removes a couplet left with no misra
11
+ * shape), a node transform that removes a couplet left with no misra
12
12
  * content — e.g. after undo/paste leaves it structurally empty — rather
13
- * than letting an unselectable husk linger in the document.
13
+ * than letting an unselectable husk linger in the document, and a
14
+ * KEY_ENTER_COMMAND handler (above RichTextPlugin's own, which would
15
+ * otherwise trap Enter inside the couplet — see poetryActions.ts) that lets
16
+ * Enter at the end of the last misra add a paragraph after the block.
14
17
  */
15
18
  export function PoetryPlugin() {
16
19
  const [editor] = useLexicalComposerContext();
@@ -21,7 +24,14 @@ export function PoetryPlugin() {
21
24
  return mergeRegister(editor.registerCommand(INSERT_POETRY_COUPLET_COMMAND, ({ layout, align }) => $insertPoetryCouplet(layout, align), COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(PoetryBlockNode, (node) => {
22
25
  if (node.getChildrenSize() === 0)
23
26
  node.remove();
24
- }));
27
+ }), editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
28
+ if (event?.shiftKey)
29
+ return false; // Shift+Enter stays a plain line break
30
+ const handled = $exitPoetryOnEnter();
31
+ if (handled)
32
+ event?.preventDefault();
33
+ return handled;
34
+ }, COMMAND_PRIORITY_HIGH));
25
35
  }, [editor]);
26
36
  return null;
27
37
  }
@@ -383,8 +383,15 @@
383
383
  conventional poetry typesetting — rather than a full-width paragraph where
384
384
  a short line just hugs the start edge regardless of its own text-align. */
385
385
  .likhari-poetry {
386
+ box-sizing: border-box;
386
387
  margin: 0 auto 1.4em auto;
387
388
  max-width: 70%;
389
+ /* A visible boundary while editing, same affordance as a table's own
390
+ border — poetry couplets are a distinct structural block, not a plain
391
+ paragraph, and should read as one at a glance. */
392
+ border: 1px dashed var(--editor-line-strong);
393
+ border-radius: 6px;
394
+ padding: 10px 16px;
388
395
  }
389
396
 
390
397
  .likhari-poetry.likhari-ltr {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.18-dev.128",
3
+ "version": "0.1.18-dev.132",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {