@textbus/platform-browser 3.0.0-alpha.57 → 3.0.0-alpha.59

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,3 +1,4 @@
1
1
  export declare const isWindows: () => boolean;
2
2
  export declare const isMac: () => boolean;
3
3
  export declare const isSafari: () => boolean;
4
+ export declare const isFirefox: () => boolean;
@@ -1,18 +1,13 @@
1
1
  import { Observable } from '@tanbo/stream';
2
2
  import { Injector } from '@tanbo/di';
3
- import { Commander, Controller, Keyboard, Scheduler, Selection, Slot } from '@textbus/core';
3
+ import { Commander, Controller, Keyboard, Scheduler, Selection } from '@textbus/core';
4
4
  import { Parser } from '../dom-support/parser';
5
- import { Caret, CaretPosition, Input, Scroller } from './types';
5
+ import { Caret, CaretPosition, CompositionState, Input, Scroller } from './types';
6
6
  interface CaretStyle {
7
7
  height: string;
8
8
  lineHeight: string;
9
9
  fontSize: string;
10
10
  }
11
- interface CompositionState {
12
- slot: Slot;
13
- index: number;
14
- data: string;
15
- }
16
11
  declare class ExperimentalCaret implements Caret {
17
12
  private scheduler;
18
13
  private editorMask;
@@ -54,11 +49,13 @@ export declare class MagicInput extends Input {
54
49
  private scheduler;
55
50
  private injector;
56
51
  composition: boolean;
52
+ compositionState: CompositionState | null;
57
53
  onReady: Promise<void>;
58
54
  caret: ExperimentalCaret;
59
55
  set disabled(b: boolean);
60
56
  get disabled(): boolean;
61
57
  private isSafari;
58
+ private isFirefox;
62
59
  private isMac;
63
60
  private isWindows;
64
61
  private _disabled;
@@ -1,7 +1,7 @@
1
1
  import { Injector } from '@tanbo/di';
2
2
  import { Observable } from '@tanbo/stream';
3
3
  import { Commander, Controller, Keyboard, Scheduler, Selection } from '@textbus/core';
4
- import { Caret, CaretPosition, Input, Scroller } from './types';
4
+ import { Caret, CaretPosition, CompositionState, Input, Scroller } from './types';
5
5
  import { Parser } from '../dom-support/parser';
6
6
  declare class NativeCaret implements Caret {
7
7
  private scheduler;
@@ -33,6 +33,7 @@ export declare class NativeInput extends Input {
33
33
  private controller;
34
34
  caret: NativeCaret;
35
35
  composition: boolean;
36
+ compositionState: CompositionState | null;
36
37
  onReady: Promise<void>;
37
38
  set disabled(b: boolean);
38
39
  get disabled(): boolean;
@@ -1,4 +1,4 @@
1
- import { ComponentLiteral, Module, TextbusConfig } from '@textbus/core';
1
+ import { ComponentLiteral, Module, Slot, TextbusConfig } from '@textbus/core';
2
2
  import { Observable } from '@tanbo/stream';
3
3
  import { FormatLoader, ComponentLoader, AttributeLoader } from '../dom-support/parser';
4
4
  import { Rect } from '../_utils/uikit';
@@ -51,8 +51,14 @@ export interface Caret {
51
51
  refresh(isFixedCaret: boolean): void;
52
52
  correctScrollTop(scroller: Scroller): void;
53
53
  }
54
+ export interface CompositionState {
55
+ slot: Slot;
56
+ index: number;
57
+ data: string;
58
+ }
54
59
  export declare abstract class Input {
55
60
  abstract composition: boolean;
61
+ abstract compositionState: CompositionState | null;
56
62
  abstract onReady: Promise<void>;
57
63
  abstract caret: Caret;
58
64
  abstract disabled: boolean;
@@ -1,6 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import { InjectionToken, Injectable, Inject, Injector, Optional } from '@tanbo/di';
3
- import { VTextNode, VElement, Controller, Selection, RootComponentRef, Renderer, Scheduler, Slot, ContentType, Keyboard, Commander, makeError, Starter, NativeRenderer, NativeSelectionBridge, OutputRenderer, Registry, invokeListener, History } from '@textbus/core';
3
+ import { VTextNode, VElement, Controller, Selection, RootComponentRef, Renderer, Scheduler, Slot, ContentType, Event, invokeListener, Keyboard, Commander, makeError, Starter, NativeRenderer, NativeSelectionBridge, OutputRenderer, Registry, History } from '@textbus/core';
4
4
  import { Subject, filter, fromEvent, Subscription, merge, map, Observable, distinctUntilChanged } from '@tanbo/stream';
5
5
 
6
6
  function createElement(tagName, options = {}) {
@@ -101,6 +101,7 @@ function getLayoutRectByRange(range) {
101
101
  const isWindows = () => /win(dows|32|64)/i.test(navigator.userAgent);
102
102
  const isMac = () => /mac os/i.test(navigator.userAgent);
103
103
  const isSafari = () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
104
+ const isFirefox = () => /Firefox/.test(navigator.userAgent);
104
105
 
105
106
  /******************************************************************************
106
107
  Copyright (c) Microsoft Corporation.
@@ -1584,8 +1585,10 @@ let MagicInput = class MagicInput extends Input {
1584
1585
  this.scheduler = scheduler;
1585
1586
  this.injector = injector;
1586
1587
  this.composition = false;
1588
+ this.compositionState = null;
1587
1589
  this.caret = new ExperimentalCaret(this.scheduler, this.injector.get(VIEW_MASK));
1588
1590
  this.isSafari = isSafari();
1591
+ this.isFirefox = isFirefox();
1589
1592
  this.isMac = isMac();
1590
1593
  this.isWindows = isWindows();
1591
1594
  this._disabled = false;
@@ -1714,7 +1717,10 @@ let MagicInput = class MagicInput extends Input {
1714
1717
  this.doc.body.appendChild(div);
1715
1718
  div.focus();
1716
1719
  setTimeout(() => {
1717
- const html = div.innerHTML;
1720
+ let html = div.innerHTML;
1721
+ if (!html && text && this.isFirefox) {
1722
+ html = text;
1723
+ }
1718
1724
  this.handlePaste(html, text);
1719
1725
  this.doc.body.removeChild(div);
1720
1726
  });
@@ -1769,32 +1775,47 @@ let MagicInput = class MagicInput extends Input {
1769
1775
  handleInput(textarea) {
1770
1776
  let startIndex = 0;
1771
1777
  this.subscription.add(fromEvent(textarea, 'compositionstart').subscribe(() => {
1778
+ this.composition = true;
1779
+ this.caret.compositionState = this.compositionState = null;
1772
1780
  startIndex = this.selection.startOffset;
1781
+ const startSlot = this.selection.startSlot;
1782
+ const event = new Event(startSlot, {
1783
+ index: startIndex
1784
+ });
1785
+ invokeListener(startSlot.parent, 'onCompositionStart', event);
1773
1786
  }), fromEvent(textarea, 'compositionupdate').subscribe(ev => {
1774
1787
  if (ev.data === ' ') {
1775
1788
  // 处理搜狗五笔不符合 composition 事件预期,会意外跳光标的问题
1776
1789
  return;
1777
1790
  }
1778
- this.caret.compositionState = {
1779
- slot: this.selection.startSlot,
1791
+ const startSlot = this.selection.startSlot;
1792
+ this.caret.compositionState = this.compositionState = {
1793
+ slot: startSlot,
1780
1794
  index: startIndex,
1781
1795
  data: ev.data
1782
1796
  };
1783
1797
  this.caret.refresh(true);
1784
- }), fromEvent(textarea, 'compositionend').subscribe(() => {
1785
- var _a;
1786
- this.caret.compositionState = null;
1787
- (_a = this.caret.compositionElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.caret.compositionElement);
1798
+ const event = new Event(startSlot, {
1799
+ index: startIndex,
1800
+ data: ev.data
1801
+ });
1802
+ invokeListener(startSlot.parent, 'onCompositionUpdate', event);
1788
1803
  }));
1804
+ let isCompositionEnd = false;
1789
1805
  this.subscription.add(merge(fromEvent(textarea, 'beforeinput').pipe(filter(ev => {
1790
1806
  ev.preventDefault();
1807
+ if (this.isFirefox && ev.inputType === 'insertFromPaste') {
1808
+ return false;
1809
+ }
1791
1810
  if (this.isSafari) {
1811
+ isCompositionEnd = ev.inputType === 'insertFromComposition';
1792
1812
  return ev.inputType === 'insertText' || ev.inputType === 'insertFromComposition';
1793
1813
  }
1794
1814
  return !ev.isComposing && !!ev.data;
1795
1815
  }), map(ev => {
1796
1816
  return ev.data;
1797
1817
  })), this.isSafari ? new Observable() : fromEvent(textarea, 'compositionend').pipe(map(ev => {
1818
+ isCompositionEnd = true;
1798
1819
  ev.preventDefault();
1799
1820
  textarea.value = '';
1800
1821
  return ev.data;
@@ -1803,9 +1824,21 @@ let MagicInput = class MagicInput extends Input {
1803
1824
  this.isSougouPinYin = false;
1804
1825
  return !b;
1805
1826
  }))).subscribe(text => {
1827
+ var _a;
1828
+ this.composition = false;
1829
+ this.caret.compositionState = this.compositionState = null;
1830
+ (_a = this.caret.compositionElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.caret.compositionElement);
1806
1831
  if (text) {
1807
1832
  this.commander.write(text);
1808
1833
  }
1834
+ if (isCompositionEnd) {
1835
+ const startSlot = this.selection.startSlot;
1836
+ if (startSlot) {
1837
+ const event = new Event(startSlot, null);
1838
+ invokeListener(startSlot.parent, 'onCompositionEnd', event);
1839
+ }
1840
+ }
1841
+ isCompositionEnd = false;
1809
1842
  }));
1810
1843
  }
1811
1844
  createEditableFrame() {
@@ -1958,6 +1991,7 @@ let NativeInput = class NativeInput extends Input {
1958
1991
  this.controller = controller;
1959
1992
  this.caret = new NativeCaret(this.scheduler);
1960
1993
  this.composition = false;
1994
+ this.compositionState = null;
1961
1995
  this.onReady = Promise.resolve();
1962
1996
  this._disabled = false;
1963
1997
  this.nativeSelection = document.getSelection();
@@ -2100,17 +2134,40 @@ let NativeInput = class NativeInput extends Input {
2100
2134
  }));
2101
2135
  }
2102
2136
  handleInput(input) {
2137
+ let startIndex = 0;
2138
+ let isCompositionEnd = false;
2103
2139
  this.subscription.add(fromEvent(input, 'compositionstart').subscribe(() => {
2104
2140
  this.composition = true;
2141
+ this.compositionState = null;
2142
+ startIndex = this.selection.startOffset;
2143
+ const startSlot = this.selection.startSlot;
2144
+ const event = new Event(startSlot, {
2145
+ index: startIndex
2146
+ });
2147
+ invokeListener(startSlot.parent, 'onCompositionStart', event);
2148
+ }), fromEvent(input, 'compositionupdate').subscribe(ev => {
2149
+ const startSlot = this.selection.startSlot;
2150
+ this.compositionState = {
2151
+ slot: startSlot,
2152
+ index: startIndex,
2153
+ data: ev.data
2154
+ };
2155
+ const event = new Event(startSlot, {
2156
+ index: startIndex,
2157
+ data: ev.data
2158
+ });
2159
+ invokeListener(startSlot.parent, 'onCompositionUpdate', event);
2105
2160
  }), merge(fromEvent(input, 'beforeinput').pipe(filter(ev => {
2106
2161
  ev.preventDefault();
2107
2162
  if (this.isSafari) {
2163
+ isCompositionEnd = ev.inputType === 'insertFromComposition';
2108
2164
  return ev.inputType === 'insertText' || ev.inputType === 'insertFromComposition';
2109
2165
  }
2110
2166
  return !ev.isComposing && !!ev.data;
2111
2167
  }), map(ev => {
2112
2168
  return ev.data;
2113
2169
  })), this.isSafari ? new Observable() : fromEvent(input, 'compositionend').pipe(map(ev => {
2170
+ isCompositionEnd = true;
2114
2171
  ev.preventDefault();
2115
2172
  return ev.data;
2116
2173
  }), filter(() => {
@@ -2119,9 +2176,18 @@ let NativeInput = class NativeInput extends Input {
2119
2176
  return !b;
2120
2177
  }))).subscribe(text => {
2121
2178
  this.composition = false;
2179
+ this.compositionState = null;
2122
2180
  if (text) {
2123
2181
  this.commander.write(text);
2124
2182
  }
2183
+ if (isCompositionEnd) {
2184
+ const startSlot = this.selection.startSlot;
2185
+ if (startSlot) {
2186
+ const event = new Event(startSlot, null);
2187
+ invokeListener(startSlot.parent, 'onCompositionEnd', event);
2188
+ }
2189
+ }
2190
+ isCompositionEnd = false;
2125
2191
  }));
2126
2192
  }
2127
2193
  };
@@ -2715,4 +2781,4 @@ class Viewer extends Starter {
2715
2781
  }
2716
2782
  }
2717
2783
 
2718
- export { CollaborateCursor, CollaborateSelectionAwarenessDelegate, DomRenderer, EDITOR_OPTIONS, Input, MagicInput, NativeInput, OutputTranslator, Parser, SelectionBridge, VIEW_CONTAINER, VIEW_DOCUMENT, VIEW_MASK, Viewer, createElement, createTextNode, getLayoutRectByRange, isMac, isSafari, isWindows };
2784
+ export { CollaborateCursor, CollaborateSelectionAwarenessDelegate, DomRenderer, EDITOR_OPTIONS, Input, MagicInput, NativeInput, OutputTranslator, Parser, SelectionBridge, VIEW_CONTAINER, VIEW_DOCUMENT, VIEW_MASK, Viewer, createElement, createTextNode, getLayoutRectByRange, isFirefox, isMac, isSafari, isWindows };
package/bundles/index.js CHANGED
@@ -103,6 +103,7 @@ function getLayoutRectByRange(range) {
103
103
  const isWindows = () => /win(dows|32|64)/i.test(navigator.userAgent);
104
104
  const isMac = () => /mac os/i.test(navigator.userAgent);
105
105
  const isSafari = () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
106
+ const isFirefox = () => /Firefox/.test(navigator.userAgent);
106
107
 
107
108
  /******************************************************************************
108
109
  Copyright (c) Microsoft Corporation.
@@ -1586,8 +1587,10 @@ exports.MagicInput = class MagicInput extends Input {
1586
1587
  this.scheduler = scheduler;
1587
1588
  this.injector = injector;
1588
1589
  this.composition = false;
1590
+ this.compositionState = null;
1589
1591
  this.caret = new ExperimentalCaret(this.scheduler, this.injector.get(VIEW_MASK));
1590
1592
  this.isSafari = isSafari();
1593
+ this.isFirefox = isFirefox();
1591
1594
  this.isMac = isMac();
1592
1595
  this.isWindows = isWindows();
1593
1596
  this._disabled = false;
@@ -1716,7 +1719,10 @@ exports.MagicInput = class MagicInput extends Input {
1716
1719
  this.doc.body.appendChild(div);
1717
1720
  div.focus();
1718
1721
  setTimeout(() => {
1719
- const html = div.innerHTML;
1722
+ let html = div.innerHTML;
1723
+ if (!html && text && this.isFirefox) {
1724
+ html = text;
1725
+ }
1720
1726
  this.handlePaste(html, text);
1721
1727
  this.doc.body.removeChild(div);
1722
1728
  });
@@ -1771,32 +1777,47 @@ exports.MagicInput = class MagicInput extends Input {
1771
1777
  handleInput(textarea) {
1772
1778
  let startIndex = 0;
1773
1779
  this.subscription.add(stream.fromEvent(textarea, 'compositionstart').subscribe(() => {
1780
+ this.composition = true;
1781
+ this.caret.compositionState = this.compositionState = null;
1774
1782
  startIndex = this.selection.startOffset;
1783
+ const startSlot = this.selection.startSlot;
1784
+ const event = new core.Event(startSlot, {
1785
+ index: startIndex
1786
+ });
1787
+ core.invokeListener(startSlot.parent, 'onCompositionStart', event);
1775
1788
  }), stream.fromEvent(textarea, 'compositionupdate').subscribe(ev => {
1776
1789
  if (ev.data === ' ') {
1777
1790
  // 处理搜狗五笔不符合 composition 事件预期,会意外跳光标的问题
1778
1791
  return;
1779
1792
  }
1780
- this.caret.compositionState = {
1781
- slot: this.selection.startSlot,
1793
+ const startSlot = this.selection.startSlot;
1794
+ this.caret.compositionState = this.compositionState = {
1795
+ slot: startSlot,
1782
1796
  index: startIndex,
1783
1797
  data: ev.data
1784
1798
  };
1785
1799
  this.caret.refresh(true);
1786
- }), stream.fromEvent(textarea, 'compositionend').subscribe(() => {
1787
- var _a;
1788
- this.caret.compositionState = null;
1789
- (_a = this.caret.compositionElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.caret.compositionElement);
1800
+ const event = new core.Event(startSlot, {
1801
+ index: startIndex,
1802
+ data: ev.data
1803
+ });
1804
+ core.invokeListener(startSlot.parent, 'onCompositionUpdate', event);
1790
1805
  }));
1806
+ let isCompositionEnd = false;
1791
1807
  this.subscription.add(stream.merge(stream.fromEvent(textarea, 'beforeinput').pipe(stream.filter(ev => {
1792
1808
  ev.preventDefault();
1809
+ if (this.isFirefox && ev.inputType === 'insertFromPaste') {
1810
+ return false;
1811
+ }
1793
1812
  if (this.isSafari) {
1813
+ isCompositionEnd = ev.inputType === 'insertFromComposition';
1794
1814
  return ev.inputType === 'insertText' || ev.inputType === 'insertFromComposition';
1795
1815
  }
1796
1816
  return !ev.isComposing && !!ev.data;
1797
1817
  }), stream.map(ev => {
1798
1818
  return ev.data;
1799
1819
  })), this.isSafari ? new stream.Observable() : stream.fromEvent(textarea, 'compositionend').pipe(stream.map(ev => {
1820
+ isCompositionEnd = true;
1800
1821
  ev.preventDefault();
1801
1822
  textarea.value = '';
1802
1823
  return ev.data;
@@ -1805,9 +1826,21 @@ exports.MagicInput = class MagicInput extends Input {
1805
1826
  this.isSougouPinYin = false;
1806
1827
  return !b;
1807
1828
  }))).subscribe(text => {
1829
+ var _a;
1830
+ this.composition = false;
1831
+ this.caret.compositionState = this.compositionState = null;
1832
+ (_a = this.caret.compositionElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.caret.compositionElement);
1808
1833
  if (text) {
1809
1834
  this.commander.write(text);
1810
1835
  }
1836
+ if (isCompositionEnd) {
1837
+ const startSlot = this.selection.startSlot;
1838
+ if (startSlot) {
1839
+ const event = new core.Event(startSlot, null);
1840
+ core.invokeListener(startSlot.parent, 'onCompositionEnd', event);
1841
+ }
1842
+ }
1843
+ isCompositionEnd = false;
1811
1844
  }));
1812
1845
  }
1813
1846
  createEditableFrame() {
@@ -1960,6 +1993,7 @@ exports.NativeInput = class NativeInput extends Input {
1960
1993
  this.controller = controller;
1961
1994
  this.caret = new NativeCaret(this.scheduler);
1962
1995
  this.composition = false;
1996
+ this.compositionState = null;
1963
1997
  this.onReady = Promise.resolve();
1964
1998
  this._disabled = false;
1965
1999
  this.nativeSelection = document.getSelection();
@@ -2102,17 +2136,40 @@ exports.NativeInput = class NativeInput extends Input {
2102
2136
  }));
2103
2137
  }
2104
2138
  handleInput(input) {
2139
+ let startIndex = 0;
2140
+ let isCompositionEnd = false;
2105
2141
  this.subscription.add(stream.fromEvent(input, 'compositionstart').subscribe(() => {
2106
2142
  this.composition = true;
2143
+ this.compositionState = null;
2144
+ startIndex = this.selection.startOffset;
2145
+ const startSlot = this.selection.startSlot;
2146
+ const event = new core.Event(startSlot, {
2147
+ index: startIndex
2148
+ });
2149
+ core.invokeListener(startSlot.parent, 'onCompositionStart', event);
2150
+ }), stream.fromEvent(input, 'compositionupdate').subscribe(ev => {
2151
+ const startSlot = this.selection.startSlot;
2152
+ this.compositionState = {
2153
+ slot: startSlot,
2154
+ index: startIndex,
2155
+ data: ev.data
2156
+ };
2157
+ const event = new core.Event(startSlot, {
2158
+ index: startIndex,
2159
+ data: ev.data
2160
+ });
2161
+ core.invokeListener(startSlot.parent, 'onCompositionUpdate', event);
2107
2162
  }), stream.merge(stream.fromEvent(input, 'beforeinput').pipe(stream.filter(ev => {
2108
2163
  ev.preventDefault();
2109
2164
  if (this.isSafari) {
2165
+ isCompositionEnd = ev.inputType === 'insertFromComposition';
2110
2166
  return ev.inputType === 'insertText' || ev.inputType === 'insertFromComposition';
2111
2167
  }
2112
2168
  return !ev.isComposing && !!ev.data;
2113
2169
  }), stream.map(ev => {
2114
2170
  return ev.data;
2115
2171
  })), this.isSafari ? new stream.Observable() : stream.fromEvent(input, 'compositionend').pipe(stream.map(ev => {
2172
+ isCompositionEnd = true;
2116
2173
  ev.preventDefault();
2117
2174
  return ev.data;
2118
2175
  }), stream.filter(() => {
@@ -2121,9 +2178,18 @@ exports.NativeInput = class NativeInput extends Input {
2121
2178
  return !b;
2122
2179
  }))).subscribe(text => {
2123
2180
  this.composition = false;
2181
+ this.compositionState = null;
2124
2182
  if (text) {
2125
2183
  this.commander.write(text);
2126
2184
  }
2185
+ if (isCompositionEnd) {
2186
+ const startSlot = this.selection.startSlot;
2187
+ if (startSlot) {
2188
+ const event = new core.Event(startSlot, null);
2189
+ core.invokeListener(startSlot.parent, 'onCompositionEnd', event);
2190
+ }
2191
+ }
2192
+ isCompositionEnd = false;
2127
2193
  }));
2128
2194
  }
2129
2195
  };
@@ -2727,6 +2793,7 @@ exports.Viewer = Viewer;
2727
2793
  exports.createElement = createElement;
2728
2794
  exports.createTextNode = createTextNode;
2729
2795
  exports.getLayoutRectByRange = getLayoutRectByRange;
2796
+ exports.isFirefox = isFirefox;
2730
2797
  exports.isMac = isMac;
2731
2798
  exports.isSafari = isSafari;
2732
2799
  exports.isWindows = isWindows;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbus/platform-browser",
3
- "version": "3.0.0-alpha.57",
3
+ "version": "3.0.0-alpha.59",
4
4
  "description": "Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.",
5
5
  "main": "./bundles/index.js",
6
6
  "module": "./bundles/index.esm.js",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@tanbo/di": "^1.1.4",
29
29
  "@tanbo/stream": "^1.1.9",
30
- "@textbus/core": "^3.0.0-alpha.57",
30
+ "@textbus/core": "^3.0.0-alpha.59",
31
31
  "reflect-metadata": "^0.1.13"
32
32
  },
33
33
  "devDependencies": {
@@ -48,5 +48,5 @@
48
48
  "bugs": {
49
49
  "url": "https://github.com/textbus/textbus.git/issues"
50
50
  },
51
- "gitHead": "e81f54ea137de6299f90f14c1c7fd7c933e68f11"
51
+ "gitHead": "7b4fd0eb4680416ddf112c8cec1432ea51919d19"
52
52
  }