@inshapardaz/likhari-react 0.1.18-dev.158 → 0.1.18-dev.162

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,15 +1,23 @@
1
1
  import { type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalNode, type NodeKey, type SerializedElementNode, type Spread, ElementNode } from 'lexical';
2
- export type PoetryLayout = 'single' | 'two-column';
3
- export type PoetryAlign = 'justify' | 'left' | 'right' | 'start';
2
+ export type PoetryLayout = 'single' | 'two-column' | 'staggered';
4
3
  /** Vertical space between couplets — an ordered scale the menu steps through. */
5
4
  export declare const POETRY_SPACINGS: readonly ["compact", "normal", "relaxed", "loose"];
6
5
  export type PoetrySpacing = (typeof POETRY_SPACINGS)[number];
7
6
  export declare const DEFAULT_POETRY_SPACING: PoetrySpacing;
7
+ /** Horizontal space either side of the divider in two-column layout. */
8
+ export declare const POETRY_GUTTERS: readonly ["compact", "normal", "relaxed", "loose"];
9
+ export type PoetryGutter = (typeof POETRY_GUTTERS)[number];
10
+ export declare const DEFAULT_POETRY_GUTTER: PoetryGutter;
11
+ /** Width of each couplet box in staggered layout, as a share of the block. */
12
+ export declare const POETRY_STAGGERS: readonly ["compact", "normal", "relaxed", "loose"];
13
+ export type PoetryStagger = (typeof POETRY_STAGGERS)[number];
14
+ export declare const DEFAULT_POETRY_STAGGER: PoetryStagger;
8
15
  export type SerializedPoetryBlockNode = Spread<{
9
16
  layout: PoetryLayout;
10
- align: PoetryAlign;
11
17
  width?: number;
12
18
  spacing?: PoetrySpacing;
19
+ gutter?: PoetryGutter;
20
+ stagger?: PoetryStagger;
13
21
  }, SerializedElementNode>;
14
22
  /**
15
23
  * A poetry *section*: one or more couplets (two-line verse units) sharing
@@ -18,24 +26,24 @@ export type SerializedPoetryBlockNode = Spread<{
18
26
  * couplet its own LayoutContainerNode row (built on the columns primitive,
19
27
  * §4.9) — see blocks/poetryActions.ts's $getCouplets for how either shape
20
28
  * is read, and $setPoetryLayout/$exitPoetryOnEnter for how children are
21
- * restructured/grown between couplets and layouts. `layout` and `align`
22
- * apply to the whole section, not per couplet; both mutate in place via
23
- * poetryActions so converting a section between layouts doesn't lose
24
- * selection/undo coherence. One extensible primitive in place of four
25
- * fixed templates, per requirements doc §4.11.
29
+ * restructured/grown between couplets and layouts. `layout` applies to the
30
+ * whole section, not per couplet, and mutates in place via poetryActions so
31
+ * converting a section between layouts doesn't lose selection/undo
32
+ * coherence. One extensible primitive in place of four fixed templates,
33
+ * per requirements doc §4.11.
26
34
  */
27
35
  export declare class PoetryBlockNode extends ElementNode {
28
36
  __layout: PoetryLayout;
29
- __align: PoetryAlign;
30
37
  /** User-chosen width in px (drag-resized), overriding editor.css's default
31
38
  * max-width: 70%. Undefined until the user resizes it. */
32
39
  __width?: number;
33
40
  __spacing: PoetrySpacing;
34
- constructor(layout?: PoetryLayout, align?: PoetryAlign, width?: number, spacing?: PoetrySpacing, key?: NodeKey);
41
+ __gutter: PoetryGutter;
42
+ __stagger: PoetryStagger;
43
+ constructor(layout?: PoetryLayout, width?: number, spacing?: PoetrySpacing, gutter?: PoetryGutter, stagger?: PoetryStagger, key?: NodeKey);
35
44
  static getType(): string;
36
45
  static clone(node: PoetryBlockNode): PoetryBlockNode;
37
46
  getLayout(): PoetryLayout;
38
- getAlign(): PoetryAlign;
39
47
  getWidth(): number | undefined;
40
48
  setWidth(width: number | undefined): this;
41
49
  /** Attribute-only — restructuring children for a layout change is
@@ -44,7 +52,10 @@ export declare class PoetryBlockNode extends ElementNode {
44
52
  setLayoutAttribute(layout: PoetryLayout): this;
45
53
  getSpacing(): PoetrySpacing;
46
54
  setSpacing(spacing: PoetrySpacing): this;
47
- setAlign(align: PoetryAlign): this;
55
+ getGutter(): PoetryGutter;
56
+ setGutter(gutter: PoetryGutter): this;
57
+ getStagger(): PoetryStagger;
58
+ setStagger(stagger: PoetryStagger): this;
48
59
  static importJSON(serializedNode: SerializedPoetryBlockNode): PoetryBlockNode;
49
60
  exportJSON(): SerializedPoetryBlockNode;
50
61
  static importDOM(): DOMConversionMap | null;
@@ -54,6 +65,6 @@ export declare class PoetryBlockNode extends ElementNode {
54
65
  canBeEmpty(): false;
55
66
  isShadowRoot(): boolean;
56
67
  }
57
- export declare function $createPoetryBlockNode(layout?: PoetryLayout, align?: PoetryAlign, width?: number, spacing?: PoetrySpacing): PoetryBlockNode;
68
+ export declare function $createPoetryBlockNode(layout?: PoetryLayout, width?: number, spacing?: PoetrySpacing, gutter?: PoetryGutter, stagger?: PoetryStagger): PoetryBlockNode;
58
69
  export declare function $isPoetryBlockNode(node: LexicalNode | null | undefined): node is PoetryBlockNode;
59
70
  //# sourceMappingURL=PoetryNode.d.ts.map
@@ -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;AACjE,iFAAiF;AACjF,eAAO,MAAM,eAAe,oDAAqD,CAAC;AAClF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7D,eAAO,MAAM,sBAAsB,EAAE,aAAwB,CAAC;AAyB9D,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAC5C;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,EACrF,qBAAqB,CACtB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;gBAGvB,MAAM,GAAE,YAAuB,EAC/B,KAAK,GAAE,WAAuB,EAC9B,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,aAAsC,EAC/C,GAAG,CAAC,EAAE,OAAO;IASf,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe;IAIpD,SAAS,IAAI,YAAY;IAIzB,QAAQ,IAAI,WAAW;IAIvB,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAMzC;;gEAE4D;IAC5D,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAM9C,UAAU,IAAI,aAAa;IAI3B,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAMxC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAMlC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,yBAAyB,GAAG,eAAe;IAI7E,UAAU,IAAI,yBAAyB;IAavC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAiB3C,SAAS,IAAI,eAAe;IAY5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAa5C,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO;IAoBrF,UAAU,IAAI,KAAK;IAInB,YAAY,IAAI,OAAO;CAGxB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,GAAE,YAAuB,EAC/B,KAAK,GAAE,WAAuB,EAC9B,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,aAAsC,GAC9C,eAAe,CAEjB;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":"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"}
@@ -3,18 +3,22 @@ import { $applyNodeReplacement, ElementNode, } from 'lexical';
3
3
  /** Vertical space between couplets — an ordered scale the menu steps through. */
4
4
  export const POETRY_SPACINGS = ['compact', 'normal', 'relaxed', 'loose'];
5
5
  export const DEFAULT_POETRY_SPACING = 'normal';
6
- const VALID_LAYOUTS = new Set(['single', 'two-column']);
7
- const VALID_ALIGNS = new Set(['justify', 'left', 'right', 'start']);
6
+ /** Horizontal space either side of the divider in two-column layout. */
7
+ export const POETRY_GUTTERS = ['compact', 'normal', 'relaxed', 'loose'];
8
+ export const DEFAULT_POETRY_GUTTER = 'normal';
9
+ /** Width of each couplet box in staggered layout, as a share of the block. */
10
+ export const POETRY_STAGGERS = ['compact', 'normal', 'relaxed', 'loose'];
11
+ export const DEFAULT_POETRY_STAGGER = 'normal';
12
+ const VALID_LAYOUTS = new Set(['single', 'two-column', 'staggered']);
8
13
  const VALID_SPACINGS = new Set(POETRY_SPACINGS);
9
- /** `text-align: justify` only stretches a line that actually wraps — a
10
- * misra short enough to fit on one line would otherwise just sit at its own
11
- * start edge instead of spreading its words across the full column width.
12
- * `text-align-last: justify` extends that same word-spacing stretch to the
13
- * single/last line too, so a one-line misra reads edge-to-edge exactly like
14
- * a wrapped one would. */
15
- function applyAlignStyle(element, align) {
16
- element.style.textAlign = align;
17
- element.style.textAlignLast = align === 'justify' ? 'justify' : '';
14
+ const VALID_GUTTERS = new Set(POETRY_GUTTERS);
15
+ const VALID_STAGGERS = new Set(POETRY_STAGGERS);
16
+ /** Poetry is always justified: `text-align-last: justify` stretches a
17
+ * one-line misra across the full column width too, not just wrapped lines,
18
+ * so every line reads edge-to-edge. */
19
+ function applyJustifyStyle(element) {
20
+ element.style.textAlign = 'justify';
21
+ element.style.textAlignLast = 'justify';
18
22
  }
19
23
  /** A user-dragged width overrides editor.css's default max-width: 70% —
20
24
  * clearing the inline style (rather than setting it to that same 70%)
@@ -32,32 +36,30 @@ function applyWidthStyle(element, width) {
32
36
  * couplet its own LayoutContainerNode row (built on the columns primitive,
33
37
  * §4.9) — see blocks/poetryActions.ts's $getCouplets for how either shape
34
38
  * is read, and $setPoetryLayout/$exitPoetryOnEnter for how children are
35
- * restructured/grown between couplets and layouts. `layout` and `align`
36
- * apply to the whole section, not per couplet; both mutate in place via
37
- * poetryActions so converting a section between layouts doesn't lose
38
- * selection/undo coherence. One extensible primitive in place of four
39
- * fixed templates, per requirements doc §4.11.
39
+ * restructured/grown between couplets and layouts. `layout` applies to the
40
+ * whole section, not per couplet, and mutates in place via poetryActions so
41
+ * converting a section between layouts doesn't lose selection/undo
42
+ * coherence. One extensible primitive in place of four fixed templates,
43
+ * per requirements doc §4.11.
40
44
  */
41
45
  export class PoetryBlockNode extends ElementNode {
42
- constructor(layout = 'single', align = 'justify', width, spacing = DEFAULT_POETRY_SPACING, key) {
46
+ constructor(layout = 'single', width, spacing = DEFAULT_POETRY_SPACING, gutter = DEFAULT_POETRY_GUTTER, stagger = DEFAULT_POETRY_STAGGER, key) {
43
47
  super(key);
44
48
  this.__layout = layout;
45
- this.__align = align;
46
49
  this.__width = width;
47
50
  this.__spacing = spacing;
51
+ this.__gutter = gutter;
52
+ this.__stagger = stagger;
48
53
  }
49
54
  static getType() {
50
55
  return 'poetry-couplet';
51
56
  }
52
57
  static clone(node) {
53
- return new PoetryBlockNode(node.__layout, node.__align, node.__width, node.__spacing, node.__key);
58
+ return new PoetryBlockNode(node.__layout, node.__width, node.__spacing, node.__gutter, node.__stagger, node.__key);
54
59
  }
55
60
  getLayout() {
56
61
  return this.getLatest().__layout;
57
62
  }
58
- getAlign() {
59
- return this.getLatest().__align;
60
- }
61
63
  getWidth() {
62
64
  return this.getLatest().__width;
63
65
  }
@@ -82,13 +84,24 @@ export class PoetryBlockNode extends ElementNode {
82
84
  writable.__spacing = spacing;
83
85
  return writable;
84
86
  }
85
- setAlign(align) {
87
+ getGutter() {
88
+ return this.getLatest().__gutter;
89
+ }
90
+ setGutter(gutter) {
91
+ const writable = this.getWritable();
92
+ writable.__gutter = gutter;
93
+ return writable;
94
+ }
95
+ getStagger() {
96
+ return this.getLatest().__stagger;
97
+ }
98
+ setStagger(stagger) {
86
99
  const writable = this.getWritable();
87
- writable.__align = align;
100
+ writable.__stagger = stagger;
88
101
  return writable;
89
102
  }
90
103
  static importJSON(serializedNode) {
91
- return $createPoetryBlockNode(serializedNode.layout, serializedNode.align, serializedNode.width, serializedNode.spacing);
104
+ return $createPoetryBlockNode(serializedNode.layout, serializedNode.width, serializedNode.spacing, serializedNode.gutter, serializedNode.stagger);
92
105
  }
93
106
  exportJSON() {
94
107
  const width = this.getWidth();
@@ -97,8 +110,9 @@ export class PoetryBlockNode extends ElementNode {
97
110
  type: 'poetry-couplet',
98
111
  version: 1,
99
112
  layout: this.getLayout(),
100
- align: this.getAlign(),
101
113
  spacing: this.getSpacing(),
114
+ gutter: this.getGutter(),
115
+ stagger: this.getStagger(),
102
116
  ...(width !== undefined ? { width } : {}),
103
117
  };
104
118
  }
@@ -109,53 +123,60 @@ export class PoetryBlockNode extends ElementNode {
109
123
  if (!rawLayout)
110
124
  return null;
111
125
  const layout = VALID_LAYOUTS.has(rawLayout) ? rawLayout : 'single';
112
- const rawAlign = domNode.getAttribute('data-likhari-poetry-align') ?? 'justify';
113
- const align = VALID_ALIGNS.has(rawAlign) ? rawAlign : 'justify';
114
126
  const rawWidth = Number(domNode.getAttribute('data-likhari-poetry-width'));
115
127
  const width = Number.isFinite(rawWidth) && rawWidth > 0 ? rawWidth : undefined;
116
128
  const rawSpacing = domNode.getAttribute('data-likhari-poetry-spacing');
117
129
  const spacing = VALID_SPACINGS.has(rawSpacing) ? rawSpacing : DEFAULT_POETRY_SPACING;
118
- return { conversion: () => ({ node: $createPoetryBlockNode(layout, align, width, spacing) }), priority: 2 };
130
+ const rawGutter = domNode.getAttribute('data-likhari-poetry-gutter');
131
+ const gutter = VALID_GUTTERS.has(rawGutter) ? rawGutter : DEFAULT_POETRY_GUTTER;
132
+ const rawStagger = domNode.getAttribute('data-likhari-poetry-stagger');
133
+ const stagger = VALID_STAGGERS.has(rawStagger) ? rawStagger : DEFAULT_POETRY_STAGGER;
134
+ return { conversion: () => ({ node: $createPoetryBlockNode(layout, width, spacing, gutter, stagger) }), priority: 2 };
119
135
  },
120
136
  };
121
137
  }
122
138
  exportDOM() {
123
139
  const element = document.createElement('div');
124
140
  element.setAttribute('data-likhari-poetry-layout', this.getLayout());
125
- element.setAttribute('data-likhari-poetry-align', this.getAlign());
126
141
  element.setAttribute('data-likhari-poetry-spacing', this.getSpacing());
142
+ element.setAttribute('data-likhari-poetry-gutter', this.getGutter());
143
+ element.setAttribute('data-likhari-poetry-stagger', this.getStagger());
127
144
  const width = this.getWidth();
128
145
  if (width !== undefined)
129
146
  element.setAttribute('data-likhari-poetry-width', String(width));
130
- applyAlignStyle(element, this.getAlign());
147
+ applyJustifyStyle(element);
131
148
  applyWidthStyle(element, width);
132
149
  return { element };
133
150
  }
134
151
  createDOM(config) {
135
152
  const element = document.createElement('div');
136
153
  const base = config.theme.poetry ?? 'likhari-poetry';
137
- addClassNamesToElement(element, base, `${base}--${this.__layout}`, `${base}--spacing-${this.__spacing}`);
138
- applyAlignStyle(element, this.__align);
154
+ addClassNamesToElement(element, base, `${base}--${this.__layout}`, `${base}--spacing-${this.__spacing}`, `${base}--gutter-${this.__gutter}`, `${base}--stagger-${this.__stagger}`);
155
+ applyJustifyStyle(element);
139
156
  applyWidthStyle(element, this.__width);
140
157
  return element;
141
158
  }
142
- // Only ever adds/removes the layout modifier class, never touches the
143
- // element's full className — the reconciler separately classList.adds the
144
- // per-block ltr/rtl theme class (editor.css) onto this same element, and
145
- // a wholesale className reassignment here would silently wipe it out.
159
+ // Only ever adds/removes modifier classes, never touches the element's full
160
+ // className — the reconciler separately classList.adds the per-block ltr/rtl
161
+ // theme class (editor.css) onto this same element, and a wholesale className
162
+ // reassignment here would silently wipe it out.
146
163
  updateDOM(prevNode, dom, config) {
164
+ const base = config.theme.poetry ?? 'likhari-poetry';
147
165
  if (prevNode.__layout !== this.__layout) {
148
- const base = config.theme.poetry ?? 'likhari-poetry';
149
166
  dom.classList.remove(`${base}--${prevNode.__layout}`);
150
167
  dom.classList.add(`${base}--${this.__layout}`);
151
168
  }
152
169
  if (prevNode.__spacing !== this.__spacing) {
153
- const base = config.theme.poetry ?? 'likhari-poetry';
154
170
  dom.classList.remove(`${base}--spacing-${prevNode.__spacing}`);
155
171
  dom.classList.add(`${base}--spacing-${this.__spacing}`);
156
172
  }
157
- if (prevNode.__align !== this.__align) {
158
- applyAlignStyle(dom, this.__align);
173
+ if (prevNode.__gutter !== this.__gutter) {
174
+ dom.classList.remove(`${base}--gutter-${prevNode.__gutter}`);
175
+ dom.classList.add(`${base}--gutter-${this.__gutter}`);
176
+ }
177
+ if (prevNode.__stagger !== this.__stagger) {
178
+ dom.classList.remove(`${base}--stagger-${prevNode.__stagger}`);
179
+ dom.classList.add(`${base}--stagger-${this.__stagger}`);
159
180
  }
160
181
  if (prevNode.__width !== this.__width) {
161
182
  applyWidthStyle(dom, this.__width);
@@ -169,8 +190,8 @@ export class PoetryBlockNode extends ElementNode {
169
190
  return true;
170
191
  }
171
192
  }
172
- export function $createPoetryBlockNode(layout = 'single', align = 'justify', width, spacing = DEFAULT_POETRY_SPACING) {
173
- return $applyNodeReplacement(new PoetryBlockNode(layout, align, width, spacing));
193
+ export function $createPoetryBlockNode(layout = 'single', width, spacing = DEFAULT_POETRY_SPACING, gutter = DEFAULT_POETRY_GUTTER, stagger = DEFAULT_POETRY_STAGGER) {
194
+ return $applyNodeReplacement(new PoetryBlockNode(layout, width, spacing, gutter, stagger));
174
195
  }
175
196
  export function $isPoetryBlockNode(node) {
176
197
  return node instanceof PoetryBlockNode;
@@ -1,5 +1,5 @@
1
1
  import { type ParagraphNode } from 'lexical';
2
- import { type PoetryAlign, type PoetryBlockNode, type PoetryLayout } from './PoetryNode.js';
2
+ import { type PoetryBlockNode, type PoetryLayout } from './PoetryNode.js';
3
3
  /** One couplet's two misra paragraphs, in order. */
4
4
  export type Couplet = [ParagraphNode, ParagraphNode];
5
5
  /**
@@ -49,10 +49,10 @@ export declare function $ensureTrailingParagraph(node: PoetryBlockNode): void;
49
49
  * semantics, in its existing layout: "one poetry block can contain one or
50
50
  * more couplets, in one or two column layout", so a block's layout is
51
51
  * fixed once it has couplets, not chosen per couplet) and focuses its first
52
- * misra. Otherwise creates a new block (in `layout`/`align`) with one
52
+ * misra. Otherwise creates a new block (in `layout`) with one
53
53
  * couplet, after the selection's top-level element.
54
54
  */
55
- export declare function $insertPoetryCouplet(layout: PoetryLayout, align: PoetryAlign): boolean;
55
+ export declare function $insertPoetryCouplet(layout: PoetryLayout): boolean;
56
56
  /** Removes the couplet the selection is inside. If it's the block's only
57
57
  * couplet, removes the whole block (an empty poetry section serves no
58
58
  * purpose) — the explicit, always-available counterpart to
@@ -60,17 +60,21 @@ export declare function $insertPoetryCouplet(layout: PoetryLayout, align: Poetry
60
60
  * menu's own "Delete table" action). */
61
61
  export declare function $deletePoetryCouplet(): boolean;
62
62
  /**
63
- * Backspace at the very start of an empty couplet removes just that
64
- * couplet — merging back into the previous one's end, or (if it's the
65
- * block's only couplet) removing the whole block, since an empty poetry
66
- * section serves no purpose. Otherwise there was no way to get rid of an
67
- * empty couplet created by mistake (its misra paragraphs each have their
68
- * own default canBeEmpty, so Lexical's usual "delete the empty block"
69
- * handling never reaches the couplet itself). Returns false for every
70
- * other caret position, including a non-empty couplet, so normal Backspace
71
- * handling (e.g. deleting within text) proceeds.
63
+ * Backspace at the start of a misra never merges it into its neighbour: the
64
+ * block's lines are paired into couplets, so merging one line away would
65
+ * shift every couplet after it (and take the previous couplet's last line
66
+ * with it). Instead, Backspace at the start of an empty couplet removes that
67
+ * couplet, and at the start of any other line it moves the caret to the end
68
+ * of the previous line. Other caret positions (deleting within text) fall
69
+ * through to normal handling.
72
70
  */
73
71
  export declare function $deletePoetryOnBackspace(): boolean;
72
+ /**
73
+ * Forward Delete at the end of a misra never merges the next line into it, for
74
+ * the same reason as $deletePoetryOnBackspace: the caret moves to the start of
75
+ * the next line instead. Other caret positions fall through to normal handling.
76
+ */
77
+ export declare function $deletePoetryForward(): boolean;
74
78
  /**
75
79
  * Enter inside a poetry block never grows a couplet past its fixed two
76
80
  * misras. The block's *last* couplet being completely empty is always the
@@ -91,4 +95,23 @@ export declare function $exitPoetryOnEnter(): boolean;
91
95
  /** Steps the caret's poetry block one notch looser (+1) or tighter (-1) on
92
96
  * the couplet-spacing scale; clamps at either end. */
93
97
  export declare function $adjustPoetrySpacing(step: 1 | -1): boolean;
98
+ /** Steps the caret's two-column gutter one notch wider (+1) or narrower (-1);
99
+ * clamps at either end. */
100
+ export declare function $adjustPoetryGutter(step: 1 | -1): boolean;
101
+ /** Steps the caret's staggered couplet width one notch narrower (-1) or wider
102
+ * (+1); clamps at either end. */
103
+ export declare function $adjustPoetryStagger(step: 1 | -1): boolean;
104
+ /** Whether the couplet the caret is in is centered, or null outside a
105
+ * two-column poetry block (centering only applies there). */
106
+ export declare function $getCoupletCenteredFromSelection(): boolean | null;
107
+ /** Centers (or un-centers) the caret's couplet within a two-column block by
108
+ * rebuilding its row around the same two misra paragraphs. */
109
+ export declare function $setCoupletCentered(centered: boolean): boolean;
110
+ /** Removes the whole poetry block the caret is in, including every couplet. */
111
+ export declare function $deletePoetryBlock(): boolean;
112
+ /** Single-column blocks pair their paragraphs into couplets, so a line left
113
+ * without a partner (from pasted or imported content) would be invisible to
114
+ * the couplet logic. Give it an empty partner instead, so every line belongs
115
+ * to a couplet and can be deleted like any other. */
116
+ export declare function $completeSingleCoupletBlock(node: PoetryBlockNode): void;
94
117
  //# sourceMappingURL=poetryActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"poetryActions.d.ts","sourceRoot":"","sources":["../../src/blocks/poetryActions.ts"],"names":[],"mappings":"AACA,OAAO,EAA4F,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvI,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,oDAAoD;AACpD,MAAM,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,EAAE,CAgB7D;AAED;;6CAE6C;AAC7C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,EAAE,CAE1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,eAAe,GAAG,IAAI,CAKrE;AA0DD;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAevF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmBlF;AAED;;;;;;qDAMqD;AACrD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAiBtF;AAED;;;;wCAIwC;AACxC,wBAAgB,oBAAoB,IAAI,OAAO,CAwB9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAqClD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAgD5C;AAED;sDACsD;AACtD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAO1D"}
1
+ {"version":3,"file":"poetryActions.d.ts","sourceRoot":"","sources":["../../src/blocks/poetryActions.ts"],"names":[],"mappings":"AACA,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,oDAAoD;AACpD,MAAM,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,EAAE,CAqB7D;AAED;;6CAE6C;AAC7C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,EAAE,CAE1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,eAAe,GAAG,IAAI,CAKrE;AA0DD;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAevF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmBlF;AAED;;;;;;qDAMqD;AACrD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAiBlE;AAED;;;;wCAIwC;AACxC,wBAAgB,oBAAoB,IAAI,OAAO,CAwB9C;AAUD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAsClD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAkB9C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAgD5C;AAED;sDACsD;AACtD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAO1D;AAED;2BAC2B;AAC3B,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAOzD;AAED;iCACiC;AACjC,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAO1D;AAYD;6DAC6D;AAC7D,wBAAgB,gCAAgC,IAAI,OAAO,GAAG,IAAI,CAUjE;AAED;8DAC8D;AAC9D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAuB9D;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,IAAI,OAAO,CAK5C;AAED;;;qDAGqD;AACrD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAGvE"}
@@ -1,7 +1,7 @@
1
1
  import { $findMatchingParent } from '@lexical/utils';
2
- import { $createParagraphNode, $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection } from 'lexical';
2
+ import { $createParagraphNode, $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection, } from 'lexical';
3
3
  import { $createLayoutContainerNode, $createLayoutItemNode, $isLayoutContainerNode, $isLayoutItemNode } from './LayoutNode.js';
4
- import { $createPoetryBlockNode, $isPoetryBlockNode, POETRY_SPACINGS, } from './PoetryNode.js';
4
+ import { $createPoetryBlockNode, $isPoetryBlockNode, POETRY_GUTTERS, POETRY_SPACINGS, POETRY_STAGGERS, } from './PoetryNode.js';
5
5
  /**
6
6
  * A poetry block is a *section* of one or more couplets sharing one layout
7
7
  * (single-column stacks each couplet's two misras directly as paragraph
@@ -11,7 +11,7 @@ import { $createPoetryBlockNode, $isPoetryBlockNode, POETRY_SPACINGS, } from './
11
11
  * read either shape, so nothing else needs to.
12
12
  */
13
13
  export function $getCouplets(node) {
14
- if (node.getLayout() === 'single') {
14
+ if (node.getLayout() !== 'two-column') {
15
15
  const paragraphs = node.getChildren().filter($isParagraphNode);
16
16
  const couplets = [];
17
17
  for (let i = 0; i + 1 < paragraphs.length; i += 2)
@@ -22,6 +22,12 @@ export function $getCouplets(node) {
22
22
  const couplets = [];
23
23
  for (const container of containers) {
24
24
  const items = container.getChildren().filter($isLayoutItemNode);
25
+ if (items.length === 1) {
26
+ const [a, b] = items[0].getChildren();
27
+ if ($isParagraphNode(a) && $isParagraphNode(b))
28
+ couplets.push([a, b]);
29
+ continue;
30
+ }
25
31
  const a = items[0]?.getFirstChild();
26
32
  const b = items[1]?.getFirstChild();
27
33
  if ($isParagraphNode(a) && $isParagraphNode(b))
@@ -58,7 +64,7 @@ function $findCoupletPosition(couplets, target) {
58
64
  function $appendCoupletTo(node) {
59
65
  const misraA = $createParagraphNode();
60
66
  const misraB = $createParagraphNode();
61
- if (node.getLayout() === 'single') {
67
+ if (node.getLayout() !== 'two-column') {
62
68
  node.append(misraA, misraB);
63
69
  }
64
70
  else {
@@ -80,7 +86,7 @@ function $appendCoupletTo(node) {
80
86
  function $insertCoupletRelativeTo(existingFirstMisra, layout, position) {
81
87
  const misraA = $createParagraphNode();
82
88
  const misraB = $createParagraphNode();
83
- if (layout === 'single') {
89
+ if (layout !== 'two-column') {
84
90
  if (position === 'before') {
85
91
  existingFirstMisra.insertBefore(misraA);
86
92
  misraA.insertAfter(misraB);
@@ -143,7 +149,7 @@ export function $setPoetryLayout(node, layout) {
143
149
  }
144
150
  for (const leftover of node.getChildren())
145
151
  leftover.remove();
146
- if (layout === 'single') {
152
+ if (layout !== 'two-column') {
147
153
  for (const [a, b] of couplets)
148
154
  node.append(a, b);
149
155
  }
@@ -174,10 +180,10 @@ export function $ensureTrailingParagraph(node) {
174
180
  * semantics, in its existing layout: "one poetry block can contain one or
175
181
  * more couplets, in one or two column layout", so a block's layout is
176
182
  * fixed once it has couplets, not chosen per couplet) and focuses its first
177
- * misra. Otherwise creates a new block (in `layout`/`align`) with one
183
+ * misra. Otherwise creates a new block (in `layout`) with one
178
184
  * couplet, after the selection's top-level element.
179
185
  */
180
- export function $insertPoetryCouplet(layout, align) {
186
+ export function $insertPoetryCouplet(layout) {
181
187
  const selection = $getSelection();
182
188
  if (!$isRangeSelection(selection))
183
189
  return false;
@@ -186,7 +192,7 @@ export function $insertPoetryCouplet(layout, align) {
186
192
  return $insertCoupletRelativeToSelection('after');
187
193
  }
188
194
  const anchorTopLevel = selection.anchor.getNode().getTopLevelElementOrThrow();
189
- const node = $createPoetryBlockNode(layout, align);
195
+ const node = $createPoetryBlockNode(layout);
190
196
  const [misraA] = $appendCoupletTo(node);
191
197
  anchorTopLevel.insertAfter(node);
192
198
  $ensureTrailingParagraph(node);
@@ -223,16 +229,23 @@ export function $deletePoetryCouplet() {
223
229
  }
224
230
  return true;
225
231
  }
232
+ /** Whether a point sits at the very start (or, with `atEnd`, very end) of a paragraph. */
233
+ function $isPointAt(paragraph, point, atEnd) {
234
+ if (point.key === paragraph.getKey())
235
+ return point.offset === (atEnd ? paragraph.getChildrenSize() : 0);
236
+ const edge = atEnd ? paragraph.getLastDescendant() : paragraph.getFirstDescendant();
237
+ if (point.getNode() !== edge)
238
+ return false;
239
+ return point.offset === (atEnd ? point.getNode().getTextContent().length : 0);
240
+ }
226
241
  /**
227
- * Backspace at the very start of an empty couplet removes just that
228
- * couplet — merging back into the previous one's end, or (if it's the
229
- * block's only couplet) removing the whole block, since an empty poetry
230
- * section serves no purpose. Otherwise there was no way to get rid of an
231
- * empty couplet created by mistake (its misra paragraphs each have their
232
- * own default canBeEmpty, so Lexical's usual "delete the empty block"
233
- * handling never reaches the couplet itself). Returns false for every
234
- * other caret position, including a non-empty couplet, so normal Backspace
235
- * handling (e.g. deleting within text) proceeds.
242
+ * Backspace at the start of a misra never merges it into its neighbour: the
243
+ * block's lines are paired into couplets, so merging one line away would
244
+ * shift every couplet after it (and take the previous couplet's last line
245
+ * with it). Instead, Backspace at the start of an empty couplet removes that
246
+ * couplet, and at the start of any other line it moves the caret to the end
247
+ * of the previous line. Other caret positions (deleting within text) fall
248
+ * through to normal handling.
236
249
  */
237
250
  export function $deletePoetryOnBackspace() {
238
251
  const selection = $getSelection();
@@ -242,35 +255,60 @@ export function $deletePoetryOnBackspace() {
242
255
  if (!block)
243
256
  return false;
244
257
  const couplets = $getCouplets(block);
245
- const anchorNode = selection.anchor.getNode();
246
- const position = $findCoupletPosition(couplets, anchorNode);
247
- if (!position || position.misraIndex !== 0)
248
- return false;
249
- const [firstMisra] = couplets[position.coupletIndex];
250
- const anchor = selection.anchor;
251
- const isAtStart = anchor.key === firstMisra.getKey() && anchor.offset === 0;
252
- if (!isAtStart)
258
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
259
+ if (!position)
253
260
  return false;
254
- const [a, b] = couplets[position.coupletIndex];
255
- if (!a.isEmpty() || !b.isEmpty())
261
+ const { coupletIndex, misraIndex } = position;
262
+ const [a, b] = couplets[coupletIndex];
263
+ const misra = couplets[coupletIndex][misraIndex];
264
+ if (!$isPointAt(misra, selection.anchor, false))
256
265
  return false;
257
- if (couplets.length <= 1) {
258
- const previous = block.getPreviousSibling();
259
- block.remove();
260
- if ($isElementNode(previous))
261
- previous.selectEnd();
266
+ if (a.isEmpty() && b.isEmpty()) {
267
+ if (couplets.length <= 1) {
268
+ const previous = block.getPreviousSibling();
269
+ block.remove();
270
+ if ($isElementNode(previous))
271
+ previous.selectEnd();
272
+ return true;
273
+ }
274
+ const container = $findMatchingParent(a, $isLayoutContainerNode);
275
+ if (container)
276
+ container.remove();
277
+ else {
278
+ a.remove();
279
+ b.remove();
280
+ }
281
+ const remaining = $getCouplets(block);
282
+ const previousCouplet = remaining[coupletIndex - 1] ?? remaining[0];
283
+ previousCouplet?.[1].selectEnd();
262
284
  return true;
263
285
  }
264
- const container = $findMatchingParent(a, $isLayoutContainerNode);
265
- if (container)
266
- container.remove();
267
- else {
268
- a.remove();
269
- b.remove();
270
- }
271
- const remaining = $getCouplets(block);
272
- const previousCouplet = remaining[position.coupletIndex - 1] ?? remaining[0];
273
- previousCouplet?.[1].selectEnd();
286
+ const previousLine = misraIndex === 1 ? a : couplets[coupletIndex - 1]?.[1];
287
+ previousLine?.selectEnd();
288
+ return true;
289
+ }
290
+ /**
291
+ * Forward Delete at the end of a misra never merges the next line into it, for
292
+ * the same reason as $deletePoetryOnBackspace: the caret moves to the start of
293
+ * the next line instead. Other caret positions fall through to normal handling.
294
+ */
295
+ export function $deletePoetryForward() {
296
+ const selection = $getSelection();
297
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
298
+ return false;
299
+ const block = $getPoetryBlockFromSelection();
300
+ if (!block)
301
+ return false;
302
+ const couplets = $getCouplets(block);
303
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
304
+ if (!position)
305
+ return false;
306
+ const { coupletIndex, misraIndex } = position;
307
+ const misra = couplets[coupletIndex][misraIndex];
308
+ if (!$isPointAt(misra, selection.anchor, true))
309
+ return false;
310
+ const nextLine = misraIndex === 0 ? couplets[coupletIndex][1] : couplets[coupletIndex + 1]?.[0];
311
+ nextLine?.selectStart();
274
312
  return true;
275
313
  }
276
314
  /**
@@ -347,3 +385,97 @@ export function $adjustPoetrySpacing(step) {
347
385
  block.setSpacing(next);
348
386
  return true;
349
387
  }
388
+ /** Steps the caret's two-column gutter one notch wider (+1) or narrower (-1);
389
+ * clamps at either end. */
390
+ export function $adjustPoetryGutter(step) {
391
+ const block = $getPoetryBlockFromSelection();
392
+ if (!block)
393
+ return false;
394
+ const index = POETRY_GUTTERS.indexOf(block.getGutter());
395
+ const next = POETRY_GUTTERS[Math.min(POETRY_GUTTERS.length - 1, Math.max(0, index + step))];
396
+ block.setGutter(next);
397
+ return true;
398
+ }
399
+ /** Steps the caret's staggered couplet width one notch narrower (-1) or wider
400
+ * (+1); clamps at either end. */
401
+ export function $adjustPoetryStagger(step) {
402
+ const block = $getPoetryBlockFromSelection();
403
+ if (!block)
404
+ return false;
405
+ const index = POETRY_STAGGERS.indexOf(block.getStagger());
406
+ const next = POETRY_STAGGERS[Math.min(POETRY_STAGGERS.length - 1, Math.max(0, index + step))];
407
+ block.setStagger(next);
408
+ return true;
409
+ }
410
+ /** A centered couplet is a two-column row holding both misras in one item
411
+ * (a single-item row), rather than one item per misra. */
412
+ function $isCoupletRowCentered(row) {
413
+ return row.getChildren().filter($isLayoutItemNode).length === 1;
414
+ }
415
+ function $findCoupletRow(couplet) {
416
+ return $findMatchingParent(couplet[0], $isLayoutContainerNode);
417
+ }
418
+ /** Whether the couplet the caret is in is centered, or null outside a
419
+ * two-column poetry block (centering only applies there). */
420
+ export function $getCoupletCenteredFromSelection() {
421
+ const block = $getPoetryBlockFromSelection();
422
+ if (!block || block.getLayout() !== 'two-column')
423
+ return null;
424
+ const selection = $getSelection();
425
+ if (!$isRangeSelection(selection))
426
+ return null;
427
+ const couplets = $getCouplets(block);
428
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
429
+ if (!position)
430
+ return null;
431
+ const row = $findCoupletRow(couplets[position.coupletIndex]);
432
+ return row ? $isCoupletRowCentered(row) : false;
433
+ }
434
+ /** Centers (or un-centers) the caret's couplet within a two-column block by
435
+ * rebuilding its row around the same two misra paragraphs. */
436
+ export function $setCoupletCentered(centered) {
437
+ const block = $getPoetryBlockFromSelection();
438
+ if (!block || block.getLayout() !== 'two-column')
439
+ return false;
440
+ const selection = $getSelection();
441
+ if (!$isRangeSelection(selection))
442
+ return false;
443
+ const couplets = $getCouplets(block);
444
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
445
+ if (!position)
446
+ return false;
447
+ const couplet = couplets[position.coupletIndex];
448
+ const row = $findCoupletRow(couplet);
449
+ if (!row || $isCoupletRowCentered(row) === centered)
450
+ return true;
451
+ const [a, b] = couplet;
452
+ const newRow = $createLayoutContainerNode(centered ? '1fr' : 'repeat(2, 1fr)');
453
+ if (centered) {
454
+ newRow.append($createLayoutItemNode().append(a, b));
455
+ }
456
+ else {
457
+ newRow.append($createLayoutItemNode().append(a), $createLayoutItemNode().append(b));
458
+ }
459
+ row.insertAfter(newRow);
460
+ row.remove();
461
+ a.selectStart();
462
+ return true;
463
+ }
464
+ /** Removes the whole poetry block the caret is in, including every couplet. */
465
+ export function $deletePoetryBlock() {
466
+ const block = $getPoetryBlockFromSelection();
467
+ if (!block)
468
+ return false;
469
+ block.remove();
470
+ return true;
471
+ }
472
+ /** Single-column blocks pair their paragraphs into couplets, so a line left
473
+ * without a partner (from pasted or imported content) would be invisible to
474
+ * the couplet logic. Give it an empty partner instead, so every line belongs
475
+ * to a couplet and can be deleted like any other. */
476
+ export function $completeSingleCoupletBlock(node) {
477
+ if (node.getLayout() === 'two-column')
478
+ return;
479
+ if (node.getChildrenSize() % 2 === 1)
480
+ node.append($createParagraphNode());
481
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AAuEA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAsfvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BA+jCxJ"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AAqFA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA8gBvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAmkCxJ"}
@@ -14,8 +14,8 @@ import { $createImageNode } from '../image/ImageNode.js';
14
14
  import { INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
15
15
  import { INSERT_LAYOUT_COMMAND } from '../blocks/LayoutNode.js';
16
16
  import { INSERT_FOOTNOTE_COMMAND } from '../blocks/FootnoteNode.js';
17
- import { $isPoetryBlockNode } from '../blocks/PoetryNode.js';
18
- import { $adjustPoetrySpacing, $deletePoetryCouplet, $getPoetryBlockFromSelection, $insertCoupletRelativeToSelection, $setPoetryLayout, } from '../blocks/poetryActions.js';
17
+ import { $isPoetryBlockNode, POETRY_GUTTERS, POETRY_SPACINGS, POETRY_STAGGERS, } from '../blocks/PoetryNode.js';
18
+ import { $adjustPoetryGutter, $getCoupletCenteredFromSelection, $setCoupletCentered, $adjustPoetryStagger, $adjustPoetrySpacing, $deletePoetryBlock, $deletePoetryCouplet, $getPoetryBlockFromSelection, $insertCoupletRelativeToSelection, $setPoetryLayout, } from '../blocks/poetryActions.js';
19
19
  import { INSERT_POETRY_COUPLET_COMMAND } from '../plugins/PoetryPlugin.js';
20
20
  import { TableDialog } from './TableDialog.js';
21
21
  import { LayoutDialog } from './LayoutDialog.js';
@@ -29,7 +29,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
29
29
  import { setStyleProperty } from '../utils/style.js';
30
30
  import { useStrings } from '../i18n/useStrings.js';
31
31
  import { usePortalTarget } from '../PortalTargetContext.js';
32
- import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
32
+ import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconArrowsShuffle, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
33
33
  /** Sentinel for "no inline style": Lexical returns the default it is given when
34
34
  * the selection has none, so this tells "unstyled" (show the canvas default)
35
35
  * apart from "styled with a value the dropdown doesn't list" (show nothing). */
@@ -50,7 +50,8 @@ const INITIAL_STATE = {
50
50
  canUnmergeCell: false,
51
51
  inPoetry: false,
52
52
  poetryLayout: 'single',
53
- poetryAlign: 'justify',
53
+ poetryScale: { spacing: 'normal', gutter: 'normal', stagger: 'normal' },
54
+ poetryCentered: false,
54
55
  canUndo: false,
55
56
  canRedo: false,
56
57
  };
@@ -162,16 +163,14 @@ function TableMenuItems({ strings, direction, rows, columns, canMergeCells, canU
162
163
  };
163
164
  return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), (canMergeCells || canUnmergeCell) && _jsx(Menu.Divider, {}), canMergeCells && item(IconArrowMergeBoth, t.mergeCells, $mergeTableCells), canUnmergeCell && item(IconArrowsSplit2, t.unmergeCell, $unmergeTableCell), _jsx(Menu.Divider, {}), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
164
165
  }
165
- /** The poetry couplet menu's contents — layout (single/two-column) and
166
- * literal alignment, both per-instance overrides (requirements doc §4.11:
167
- * "set per CoupletNode, not inherited from a document-wide setting"), plus
168
- * table-row-style structural actions (insert a couplet above/below the one
169
- * the caret is in, delete it) — a poetry block is "one object with multiple
170
- * couplets, add/remove like table rows", so these mirror TableMenuItems'
171
- * own insert-before/insert-after/delete-row actions. */
172
- function PoetryMenuItems({ strings, alignIcons, layout, align, onAction, }) {
166
+ /** Whether a one-notch step from `value` stays on the scale — a step that
167
+ * would fall off either end is disabled rather than silently clamped. */
168
+ function canStep(scale, value, step) {
169
+ const next = scale.indexOf(value) + step;
170
+ return next >= 0 && next < scale.length;
171
+ }
172
+ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
173
173
  const t = strings.poetryMenu;
174
- const a = strings.toolbar.alignOptions;
175
174
  const item = (icon, label, action, active = false, color) => {
176
175
  const Icon = icon;
177
176
  return (_jsx(Menu.Item, { color: color, disabled: active, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
@@ -181,10 +180,7 @@ function PoetryMenuItems({ strings, alignIcons, layout, align, onAction, }) {
181
180
  if (node)
182
181
  $setPoetryLayout(node, next);
183
182
  };
184
- const setAlign = (next) => () => {
185
- $getPoetryBlockFromSelection()?.setAlign(next);
186
- };
187
- return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), _jsx(Menu.Divider, {}), item(alignIcons.justify ?? IconAlignJustified, a.justify, setAlign('justify'), align === 'justify'), item(alignIcons.start ?? IconAlignLeft, a.start, setAlign('start'), align === 'start'), item(IconAlignLeft, a.left, setAlign('left'), align === 'left'), item(IconAlignRight, a.right, setAlign('right'), align === 'right'), _jsx(Menu.Divider, {}), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1)), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red')] }));
183
+ return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), item(IconArrowsShuffle, t.staggered, setLayout('staggered'), layout === 'staggered'), _jsx(Menu.Divider, {}), _jsx(Menu.Divider, {}), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1), !canStep(POETRY_SPACINGS, scale.spacing, -1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1), !canStep(POETRY_SPACINGS, scale.spacing, 1)), layout === 'staggered' && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCouplets, () => $adjustPoetryStagger(-1), !canStep(POETRY_STAGGERS, scale.stagger, -1)), item(IconArrowsMaximize, t.widerCouplets, () => $adjustPoetryStagger(1), !canStep(POETRY_STAGGERS, scale.stagger, 1))] })), layout === 'two-column' && (_jsxs(_Fragment, { children: [item(IconAlignCenter, centered ? t.uncenterCouplet : t.centerCouplet, () => $setCoupletCentered(!centered)), item(IconColumns2, t.narrowerGutter, () => $adjustPoetryGutter(-1), !canStep(POETRY_GUTTERS, scale.gutter, -1)), item(IconColumns2, t.widerGutter, () => $adjustPoetryGutter(1), !canStep(POETRY_GUTTERS, scale.gutter, 1))] })), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red'), item(IconTrash, t.deletePoetry, $deletePoetryBlock, false, 'red')] }));
188
184
  }
189
185
  export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
190
186
  const [editor] = useLexicalComposerContext();
@@ -246,7 +242,10 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
246
242
  // node method (getFormatType() included) must not be deferred into it.
247
243
  const elementFormat = ($isElementNode(element) ? element.getFormatType() : 'start') || 'start';
248
244
  const poetryLayout = poetryBlock?.getLayout();
249
- const poetryAlign = poetryBlock?.getAlign();
245
+ const poetryCentered = $getCoupletCenteredFromSelection();
246
+ const poetryScale = poetryBlock
247
+ ? { spacing: poetryBlock.getSpacing(), gutter: poetryBlock.getGutter(), stagger: poetryBlock.getStagger() }
248
+ : null;
250
249
  setState((s) => ({
251
250
  ...s,
252
251
  blockType,
@@ -264,7 +263,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
264
263
  canUnmergeCell,
265
264
  inPoetry: poetryBlock !== null,
266
265
  poetryLayout: poetryLayout ?? s.poetryLayout,
267
- poetryAlign: poetryAlign ?? s.poetryAlign,
266
+ poetryScale: poetryScale ?? s.poetryScale,
267
+ poetryCentered: poetryCentered ?? false,
268
268
  }));
269
269
  });
270
270
  }, [editor]);
@@ -808,9 +808,9 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
808
808
  restoreSelection();
809
809
  openLinkDialog();
810
810
  }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
811
- showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout, align: 'justify' }) }))] }, "stubInsert")),
811
+ showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout }) }))] }, "stubInsert")),
812
812
  config.tables && state.inTable && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.tableOptions, title: strings.toolbar.tableOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconTableOptions, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] }) }, "tableActions")),
813
- config.poetry.enabled && state.inPoetry && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.poetryOptions, title: strings.toolbar.poetryOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconFeather, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, alignIcons: ALIGN_ICONS, layout: state.poetryLayout, align: state.poetryAlign, onAction: runMenuAction }) })] }) }, "poetryActions")),
813
+ config.poetry.enabled && state.inPoetry && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.poetryOptions, title: strings.toolbar.poetryOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconFeather, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] }) }, "poetryActions")),
814
814
  showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && _jsx(StubButton, { icon: IconWand, title: strings.toolbar.autocorrect, comingSoon: strings.toolbar.comingSoon }), config.language.textCleanup && _jsx(StubButton, { icon: IconSparkles, title: strings.toolbar.textCleanup, comingSoon: strings.toolbar.comingSoon }), config.language.spellCheck && _jsx(StubButton, { icon: IconAbc, title: strings.toolbar.spellChecker, comingSoon: strings.toolbar.comingSoon })] }, "language")),
815
815
  ];
816
816
  return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
@@ -829,7 +829,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
829
829
  }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: tableContext?.x ?? -9999, top: tableContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: tableContext?.rows ?? 1, columns: tableContext?.columns ?? 1, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), config.poetry.enabled && (_jsxs(Menu, { opened: poetryContext !== null, onChange: (opened) => {
830
830
  if (!opened)
831
831
  setPoetryContext(null);
832
- }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: poetryContext?.x ?? -9999, top: poetryContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, alignIcons: ALIGN_ICONS, layout: state.poetryLayout, align: state.poetryAlign, onAction: runMenuAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
832
+ }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: poetryContext?.x ?? -9999, top: poetryContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
833
833
  position: 'absolute',
834
834
  visibility: 'hidden',
835
835
  pointerEvents: 'none',
@@ -142,11 +142,19 @@ export interface Strings {
142
142
  menuLabel: string;
143
143
  singleColumn: string;
144
144
  twoColumn: string;
145
+ staggered: string;
146
+ narrowerCouplets: string;
147
+ widerCouplets: string;
145
148
  insertCoupletBefore: string;
146
149
  insertCoupletAfter: string;
150
+ centerCouplet: string;
151
+ uncenterCouplet: string;
147
152
  tighterSpacing: string;
148
153
  looserSpacing: string;
154
+ narrowerGutter: string;
155
+ widerGutter: string;
149
156
  deleteCouplet: string;
157
+ deletePoetry: string;
150
158
  };
151
159
  link: {
152
160
  menuLabel: string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;+DAC+D;AAC/D,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAElD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE;QACN,mGAAmG;QACnG,WAAW,EAAE,MAAM,CAAC;QACpB,gDAAgD;QAChD,YAAY,EAAE,MAAM,CAAC;QACrB,sEAAsE;QACtE,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE;QACP,gEAAgE;QAChE,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,0CAA0C;QAC1C,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,8FAA8F;QAC9F,YAAY,EAAE,MAAM,CAAC;QACrB,0GAA0G;QAC1G,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QACtC,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE;YACjB,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;YACnC,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,YAAY,EAAE;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,GAAG,EAAE,MAAM,CAAC;YACZ,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;IACF,uIAAuI;IACvI,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,2FAA2F;QAC3F,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,oGAAoG;IACpG,MAAM,EAAE;QACN,0GAA0G;QAC1G,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QACxC,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,2DAA2D;QAC3D,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QACtC,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QACzC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QAClC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QAC7B,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,yEAAyE;IACzE,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,2EAA2E;QAC3E,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,UAAU,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,MAAM,CAAC;QAC1E,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,MAAM,EAAE;YACN,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,eAAe,EAAE,MAAM,CAAC;YACxB,gBAAgB,EAAE,MAAM,CAAC;YACzB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,eAAe,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,CAAC,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,KAAK,MAAM,CAAC;QACxE,MAAM,EAAE;YACN,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAooBD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAI3C,CAAC;AAEF;;sDAEsD;AACtD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -125,11 +125,19 @@ const en = {
125
125
  menuLabel: 'Poetry',
126
126
  singleColumn: 'Single column',
127
127
  twoColumn: 'Two column',
128
+ staggered: 'Alternating sides',
129
+ narrowerCouplets: 'Narrower couplets',
130
+ widerCouplets: 'Wider couplets',
128
131
  insertCoupletBefore: 'Insert couplet above',
129
132
  insertCoupletAfter: 'Insert couplet below',
133
+ centerCouplet: 'Center this couplet',
134
+ uncenterCouplet: 'Un-center this couplet',
130
135
  tighterSpacing: 'Tighter couplet spacing',
131
136
  looserSpacing: 'Looser couplet spacing',
137
+ narrowerGutter: 'Narrower gutter',
138
+ widerGutter: 'Wider gutter',
132
139
  deleteCouplet: 'Delete couplet',
140
+ deletePoetry: 'Delete poetry',
133
141
  },
134
142
  link: {
135
143
  menuLabel: 'Link',
@@ -337,11 +345,19 @@ const ur = {
337
345
  menuLabel: 'شاعری',
338
346
  singleColumn: 'ایک کالم',
339
347
  twoColumn: 'دو کالم',
348
+ staggered: 'کالم متبادل',
349
+ narrowerCouplets: 'شعر کی چوڑائی کم',
350
+ widerCouplets: 'شعر کی چوڑائی زیادہ',
340
351
  insertCoupletBefore: 'اوپر شعر شامل کریں',
341
352
  insertCoupletAfter: 'نیچے شعر شامل کریں',
353
+ centerCouplet: 'یہ شعر وسط میں رکھیں',
354
+ uncenterCouplet: 'وسط میں رکھنا ختم کریں',
342
355
  tighterSpacing: 'شعروں کے درمیان کم جگہ',
343
356
  looserSpacing: 'شعروں کے درمیان زیادہ جگہ',
357
+ narrowerGutter: 'کالمز کے درمیان کم جگہ',
358
+ widerGutter: 'کالمز کے درمیان زیادہ جگہ',
344
359
  deleteCouplet: 'شعر حذف کریں',
360
+ deletePoetry: 'پوری شاعری حذف کریں',
345
361
  },
346
362
  link: {
347
363
  menuLabel: 'لنک',
@@ -549,11 +565,19 @@ const paShahmukhi = {
549
565
  menuLabel: 'شاعری',
550
566
  singleColumn: 'اک کالم',
551
567
  twoColumn: 'دو کالم',
568
+ staggered: 'وارو وار پاسے',
569
+ narrowerCouplets: 'شعر دی چوڑائی گھٹ',
570
+ widerCouplets: 'شعر دی چوڑائی ودھ',
552
571
  insertCoupletBefore: 'اُتے شعر شامل کرو',
553
572
  insertCoupletAfter: 'ھیٹھ شعر شامل کرو',
573
+ centerCouplet: 'ایہ شعر وچکار رکھو',
574
+ uncenterCouplet: 'وچکار رکھنا بند کرو',
554
575
  tighterSpacing: 'شعراں وچکار گھٹ جگہ',
555
576
  looserSpacing: 'شعراں وچکار ودھ جگہ',
577
+ narrowerGutter: 'کالماں وچکار گھٹ جگہ',
578
+ widerGutter: 'کالماں وچکار ودھ جگہ',
556
579
  deleteCouplet: 'شعر مٹاؤ',
580
+ deletePoetry: 'ساری شاعری مٹاؤ',
557
581
  },
558
582
  link: {
559
583
  menuLabel: 'لنک',
@@ -1,8 +1,7 @@
1
1
  import { type LexicalCommand } from 'lexical';
2
- import { type PoetryAlign, type PoetryLayout } from '../blocks/PoetryNode.js';
2
+ import { type PoetryLayout } from '../blocks/PoetryNode.js';
3
3
  export declare const INSERT_POETRY_COUPLET_COMMAND: LexicalCommand<{
4
4
  layout: PoetryLayout;
5
- align: PoetryAlign;
6
5
  }>;
7
6
  /**
8
7
  * Registers INSERT_POETRY_COUPLET_COMMAND (mirrors LayoutPlugin/FootnotePlugin's
@@ -1 +1 @@
1
- {"version":3,"file":"PoetryPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PoetryPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAGjB,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;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,SA4C3B"}
1
+ {"version":3,"file":"PoetryPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PoetryPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAU1E,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAElF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,SAsD3B"}
@@ -1,10 +1,10 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import { COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, KEY_BACKSPACE_COMMAND, KEY_ENTER_COMMAND, createCommand, } from 'lexical';
3
+ import { COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, 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 { $deletePoetryOnBackspace, $ensureTrailingParagraph, $exitPoetryOnEnter, $insertPoetryCouplet } from '../blocks/poetryActions.js';
7
+ import { $deletePoetryForward, $deletePoetryOnBackspace, $ensureTrailingParagraph, $completeSingleCoupletBlock, $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
@@ -24,11 +24,12 @@ export function PoetryPlugin() {
24
24
  if (!editor.hasNodes([PoetryBlockNode, LayoutContainerNode, LayoutItemNode])) {
25
25
  throw new Error('PoetryPlugin: PoetryBlockNode (and the columns nodes it composes) not registered on the editor');
26
26
  }
27
- return mergeRegister(editor.registerCommand(INSERT_POETRY_COUPLET_COMMAND, ({ layout, align }) => $insertPoetryCouplet(layout, align), COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(PoetryBlockNode, (node) => {
27
+ return mergeRegister(editor.registerCommand(INSERT_POETRY_COUPLET_COMMAND, ({ layout }) => $insertPoetryCouplet(layout), COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(PoetryBlockNode, (node) => {
28
28
  if (node.getChildrenSize() === 0) {
29
29
  node.remove();
30
30
  return;
31
31
  }
32
+ $completeSingleCoupletBlock(node);
32
33
  $ensureTrailingParagraph(node);
33
34
  }), editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
34
35
  if (event?.shiftKey)
@@ -42,6 +43,11 @@ export function PoetryPlugin() {
42
43
  if (handled)
43
44
  event?.preventDefault();
44
45
  return handled;
46
+ }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_DELETE_COMMAND, (event) => {
47
+ const handled = $deletePoetryForward();
48
+ if (handled)
49
+ event?.preventDefault();
50
+ return handled;
45
51
  }, COMMAND_PRIORITY_HIGH));
46
52
  }, [editor]);
47
53
  return null;
@@ -429,6 +429,40 @@
429
429
  .likhari-poetry--spacing-relaxed { --poetry-couplet-gap: 1.8em; }
430
430
  .likhari-poetry--spacing-loose { --poetry-couplet-gap: 2.8em; }
431
431
 
432
+ .likhari-poetry--gutter-compact { --poetry-gutter: 8px; }
433
+ .likhari-poetry--gutter-normal { --poetry-gutter: 20px; }
434
+ .likhari-poetry--gutter-relaxed { --poetry-gutter: 32px; }
435
+ .likhari-poetry--gutter-loose { --poetry-gutter: 48px; }
436
+
437
+ .likhari-poetry--stagger-compact { --poetry-stagger-width: 50%; }
438
+ .likhari-poetry--stagger-normal { --poetry-stagger-width: 65%; }
439
+ .likhari-poetry--stagger-relaxed { --poetry-stagger-width: 75%; }
440
+ .likhari-poetry--stagger-loose { --poetry-stagger-width: 85%; }
441
+
442
+ /* Staggered: couplets are the same flat misra paragraphs as single column,
443
+ but each couplet's two misras are placed in a 65%-wide box that alternates
444
+ sides — the first couplet pushed to the start edge, the next to the end
445
+ edge, and so on. Logical properties keep the alternation mirrored for RTL. */
446
+ .likhari-poetry--staggered .likhari-paragraph {
447
+ width: var(--poetry-stagger-width, 65%);
448
+ }
449
+
450
+ .likhari-poetry--staggered .likhari-paragraph:nth-child(4n+1),
451
+ .likhari-poetry--staggered .likhari-paragraph:nth-child(4n+2) {
452
+ margin-inline-end: auto;
453
+ }
454
+
455
+ .likhari-poetry--staggered .likhari-paragraph:nth-child(4n+3),
456
+ .likhari-poetry--staggered .likhari-paragraph:nth-child(4n+4) {
457
+ margin-inline-start: auto;
458
+ }
459
+
460
+ .likhari-poetry--staggered .likhari-paragraph:nth-child(odd):not(:first-child) {
461
+ border-top: 1px dashed var(--editor-line-strong);
462
+ margin-top: var(--poetry-couplet-gap);
463
+ padding-top: calc(var(--poetry-couplet-gap) * 0.6);
464
+ }
465
+
432
466
  .likhari-poetry--two-column .likhari-layout-container {
433
467
  margin: 0;
434
468
  /* No grid gap — the column divider sits exactly on the track boundary,
@@ -450,15 +484,27 @@
450
484
  text-align: inherit;
451
485
  }
452
486
 
487
+ /* A centered couplet: a two-column row holding one item (see poetryActions.ts's
488
+ $setCoupletCentered) — narrowed to the middle of the block, with no column
489
+ divider or gutter since there's only one column. */
490
+ .likhari-poetry--two-column > .likhari-layout-container:has(> .likhari-layout-item:only-child) {
491
+ width: 60%;
492
+ margin-inline: auto;
493
+ }
494
+
495
+ .likhari-poetry--two-column .likhari-layout-item:only-child {
496
+ padding-inline-end: 0;
497
+ }
498
+
453
499
  .likhari-poetry--two-column .likhari-layout-item:first-child {
454
- padding-inline-end: 20px;
500
+ padding-inline-end: var(--poetry-gutter, 20px);
455
501
  }
456
502
 
457
503
  /* A visible divider between misra columns, same dashed language as the
458
504
  couplet's own border — matches a table's internal cell borders. */
459
505
  .likhari-poetry--two-column .likhari-layout-item:not(:first-child) {
460
506
  border-inline-start: 1px dashed var(--editor-line-strong);
461
- padding-inline-start: 20px;
507
+ padding-inline-start: var(--poetry-gutter, 20px);
462
508
  }
463
509
 
464
510
  /* PoetryResizer.tsx's drag handle — portaled to a fixed position over the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.18-dev.158",
3
+ "version": "0.1.18-dev.162",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {