@sikka/hawa 0.0.238 → 0.0.239

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sikka/hawa",
3
- "version": "0.0.238",
3
+ "version": "0.0.239",
4
4
  "description": "SaaS Oriented UI Kit",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.es.js",
@@ -40,6 +40,22 @@ const stylers = {
40
40
  // Paste = Removal + Addition -> Styling Removal + Styling Addition
41
41
  // Drag & Drop = Removal + Addition -> Styling Removal + Styling Addition
42
42
 
43
+ // FIXME:
44
+ // - Creating a new line, and typing data in it, and then setting two different stylings on the same text
45
+ // FIXME:
46
+ // - Type characters, create new line, remove the new line immediately
47
+
48
+ // FIXME: Line at first index of content editable
49
+
50
+ // FIXME: Typing behind a line break identifier
51
+ // One way to prevent it, is to check in the onChange event if the data added (removed might not be needed here), is behind a line break identifier
52
+ // if so, move the start and end index by the length of the added, and replace the added, and then add the added back in the offset index
53
+
54
+ // FIXME:
55
+ // Deleting all text in a line removes that line entirely
56
+
57
+ const lineBreakIdentifier = "​"
58
+
43
59
  export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
44
60
  props
45
61
  ) => {
@@ -66,6 +82,28 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
66
82
  return i
67
83
  }
68
84
 
85
+ const getRelativePrecedingSum = (element, endIndex) => {
86
+ let nodes: any = Array.from(element.childNodes)
87
+ let sum = nodes
88
+ .slice(0, endIndex)
89
+ .map((node) => node.textContent.length)
90
+ .reduce((a, b) => a + b, 0)
91
+ return sum
92
+ }
93
+
94
+ const getLinePrecedingSum = (endIndex) => {
95
+ let fieldNodes = Array.from(field.current.childNodes)
96
+ let sum = fieldNodes
97
+ .slice(0, endIndex)
98
+ .map((lineNode: any) => {
99
+ let lineNodes = Array.from(lineNode.childNodes)
100
+ return getRelativePrecedingSum(lineNode, lineNodes.length)
101
+ })
102
+ .reduce((a, b) => a + b, 0)
103
+
104
+ return sum
105
+ }
106
+
69
107
  const getSelectionPrecedingSum = (name) => {
70
108
  let selection = window.getSelection()
71
109
  let nodes = Array.from(field.current.childNodes)
@@ -80,39 +118,83 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
80
118
  (item: any) => !["#text", "BR"].includes(item.nodeName)
81
119
  )
82
120
 
83
- let parent: any = selection[name].parentNode
84
- let special = 0
121
+ let node = selection[name]
122
+ let parent: any = node.parentNode
123
+ let sum = 0
124
+ // let special = 0
85
125
 
86
- // Special case for empty text
87
- // if (parent == field.current) {
88
- // console.log("hi")
89
- // }
126
+ const isNodeLine =
127
+ node.nodeName == "DIV" && Array.from(node.classList).includes("line")
128
+
129
+ // If the parent node is a span, this must be a text node
130
+ if (parent.nodeName == "SPAN") {
131
+ // Get index of span within line
132
+ let spanIndex = getChildIndex(parent)
133
+
134
+ // Get relative sum within line
135
+ sum += getRelativePrecedingSum(parent.parentNode, spanIndex)
136
+
137
+ // Get parent line index
138
+ let lineIndex = getChildIndex(parent.parentNode)
139
+
140
+ // Get relative sum within entire field
141
+ sum += getLinePrecedingSum(lineIndex)
142
+ }
90
143
 
91
- // Special case for dropping text near or inside styled text
92
- if (!Array.from(parent.parentNode.classList).includes("selection-ignore")) {
93
- parent = parent.parentNode
144
+ // If the parent node is a line element, this must be a new line, so return the preceding sum
145
+ if (Array.from(parent.classList).includes("line")) {
146
+ // If the node is a text node, then that must mean this is a non-styled drop
147
+ if (node.nodeName == "#text") {
148
+ let spanIndex = getChildIndex(node)
94
149
 
95
- let index = getChildIndex(selection[name].parentNode)
96
- special = Array.from(parent.childNodes)
97
- .slice(0, index)
98
- .map((e: any) => e.textContent.length)
99
- .reduce((a, b) => a + b, 0)
150
+ sum += getRelativePrecedingSum(parent, spanIndex)
151
+ }
152
+
153
+ // Get line element index
154
+ let lineIndex = getChildIndex(parent)
155
+
156
+ // Get relative sum within entire field
157
+ sum += getLinePrecedingSum(lineIndex)
100
158
  }
101
159
 
102
- let index = parent == field.current ? nodes.length : getChildIndex(parent)
160
+ if (isNodeLine) {
161
+ // Get line element index
162
+ let lineIndex = getChildIndex(node)
103
163
 
104
- let sum =
105
- nodes
106
- .slice(0, index)
107
- .map((span: any) => span.textContent.length)
108
- .reduce((a, b) => a + b, 0) + special
164
+ // Get relative sum within entire field
165
+ sum = getLinePrecedingSum(lineIndex)
166
+ }
167
+
168
+ // If the parent is the field, return zero
169
+ // FIXME: Should we return zero here?
170
+ if (parent == field.current && !isNodeLine) {
171
+ return 0
172
+ }
173
+
174
+ // // Special case for dropping text near or inside styled text
175
+ // if (!Array.from(parent.parentNode.classList).includes("selection-ignore")) {
176
+ // parent = parent.parentNode
177
+
178
+ // let index = getChildIndex(selection[name].parentNode)
179
+ // special = Array.from(parent.childNodes)
180
+ // .slice(0, index)
181
+ // .map((e: any) => e.textContent.length)
182
+ // .reduce((a, b) => a + b, 0)
183
+ // }
184
+
185
+ // let index = parent == field.current ? nodes.length : getChildIndex(parent)
186
+
187
+ // let sum =
188
+ // nodes
189
+ // .slice(0, index)
190
+ // .map((span: any) => span.textContent.length)
191
+ // .reduce((a, b) => a + b, 0) + special
109
192
 
110
193
  return sum
111
194
  }
112
195
 
113
196
  const getFieldSelection = () => {
114
197
  if (document.activeElement != field.current) return [0, 0]
115
-
116
198
  let selection = window.getSelection()
117
199
 
118
200
  let startPrecedingSum = getSelectionPrecedingSum("anchorNode")
@@ -137,7 +219,8 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
137
219
  let endNode = null
138
220
 
139
221
  let total = 0
140
- let nodes = Array.from(field.current.childNodes)
222
+ // let nodes = Array.from(field.current.childNodes)
223
+ let nodes = Array.from(field.current.getElementsByTagName("span"))
141
224
 
142
225
  for (let i = 0; i < nodes.length; i++) {
143
226
  let node: any = nodes[i]
@@ -329,9 +412,14 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
329
412
  }
330
413
 
331
414
  // Get stylings encompassing an index within it's range
332
- const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
415
+ const getIntersectStylings = (
416
+ stylings,
417
+ index,
418
+ startOffset = 0,
419
+ finishOffset = 0
420
+ ) => {
333
421
  // Find all stylings with encompassing range
334
- let matches = text.stylings.filter(
422
+ let matches = stylings.filter(
335
423
  ({ start, finish }) =>
336
424
  index >= start + startOffset && index < finish + finishOffset
337
425
  )
@@ -514,6 +602,17 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
514
602
  const processPaste = (stylings, difference, selectionStart, selectionEnd) => {
515
603
  let pasteLength = _text.current.events.paste.length
516
604
 
605
+ // console.log(
606
+ // `Pasting for length ${pasteLength} at [${selectionStart}, ${selectionEnd}]`
607
+ // )
608
+ // console.log(
609
+ // `Accompanied stylings: ${JSON.stringify(
610
+ // _text.current.clipboard,
611
+ // null,
612
+ // 2
613
+ // )}`
614
+ // )
615
+
517
616
  // Get addition start index
518
617
  let additionStart = selectionStart - pasteLength
519
618
 
@@ -538,10 +637,14 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
538
637
  })
539
638
  )
540
639
 
640
+ // console.log(`End result: ${JSON.stringify(stylings, null, 2)}`)
641
+
541
642
  return stylings
542
643
  }
543
644
 
544
645
  const processDrop = (stylings, difference, dropStart, dropEnd) => {
646
+ console.log(`Dropped at: ${[dropStart, dropEnd]}`)
647
+
545
648
  let dropLength = _text.current.events.drop.text.length
546
649
 
547
650
  let [dragStart, dragEnd] = _text.current.events.drop.drag
@@ -593,7 +696,7 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
593
696
  return stylings
594
697
  }
595
698
 
596
- const onChange = (value) => {
699
+ const onChange = (value, selectionStart, selectionEnd) => {
597
700
  // Since drop events cause onChange to invoke twice, ignore the first incomplete event
598
701
  if (
599
702
  _text.current.events.drop.is &&
@@ -601,8 +704,10 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
601
704
  )
602
705
  return
603
706
 
707
+ console.log(`Changed at: ${[selectionStart, selectionEnd]}`)
708
+
604
709
  setTimeout(function () {
605
- let [selectionStart, selectionEnd] = getFieldSelection()
710
+ // let [selectionStart, selectionEnd] = getFieldSelection()
606
711
  let difference = value.length - _text.current.content.length
607
712
  let stylings = _text.current.stylings
608
713
 
@@ -644,51 +749,130 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
644
749
  }
645
750
 
646
751
  const getContent = () => {
752
+ // console.clear()
647
753
  let content = _text.current.content
754
+ let stylings = [..._text.current.stylings]
648
755
 
649
- // Get all styling indices
650
- let indices = _text.current.stylings
651
- .map(({ start, finish }) => [start, finish])
652
- .flat()
756
+ let result = []
653
757
 
654
- // Sort ascendingly
655
- indices = indices.sort((a, b) => a - b)
758
+ // Get line indices
759
+ let lineIndices = content
760
+ .split("")
761
+ .map((character, index) => {
762
+ if (character == lineBreakIdentifier) return index
763
+ return null
764
+ })
765
+ .filter((item) => item)
656
766
 
657
- // Remove duplicates
658
- indices = indices.filter(
659
- (element, index) => indices.indexOf(element) == index
660
- )
767
+ // Add end point
768
+ lineIndices.unshift(0)
769
+ lineIndices.push(content.length)
661
770
 
662
- // Add first index if not present
663
- if (indices[0] != 0) indices.unshift(0)
771
+ // Ignore last element
772
+ for (let i = 0; i < lineIndices.length - 1; i++) {
773
+ let lineStart = lineIndices[i]
774
+ let lineEnd = lineIndices[i + 1]
664
775
 
665
- // Add last index if not present
666
- let last = content.length
667
- if (indices[indices.length - 1] != last) indices.push(last)
776
+ let lineContent = content.slice(lineStart, lineEnd)
668
777
 
669
- let result = []
778
+ // console.log(
779
+ // `Line: ${lineStart} -> ${lineEnd}, with content: ${lineContent}`
780
+ // )
781
+
782
+ // Get all stylings within line
783
+ let lineStylings = getStylingsInRange(stylings, lineStart, lineEnd)
784
+
785
+ // Collect all spans relative to stylings within line
786
+ let spans = []
787
+
788
+ // Acquire all flattened unique indices
789
+ let indices = lineStylings
790
+ .map(({ start, finish }) => [start, finish])
791
+ .flat()
792
+ .sort((a, b) => a - b)
793
+
794
+ indices = indices.filter(
795
+ (element, index) => indices.indexOf(element) == index
796
+ )
797
+
798
+ // Add first index if not present
799
+ if (indices[0] != 0) indices.unshift(0)
670
800
 
671
- for (let i = 0; i < indices.length - 1; i++) {
672
- result.push([indices[i], content.substring(indices[i], indices[i + 1])])
801
+ // Add last index if not present
802
+ let last = lineContent.length
803
+ if (indices[indices.length - 1] != last) indices.push(last)
804
+
805
+ // Iterate through all indices except the last
806
+ for (let i = 0; i < indices.length - 1; i++) {
807
+ let startIndex = indices[i]
808
+ let endIndex = indices[i + 1]
809
+
810
+ spans.push({
811
+ content: lineContent.substring(startIndex, endIndex),
812
+ types: getIntersectStylings(lineStylings, startIndex).map(
813
+ (styling) => styling.type
814
+ ),
815
+ })
816
+ }
817
+
818
+ result.push(spans)
673
819
  }
674
820
 
821
+ // // Get all styling indices
822
+ // let indices = _text.current.stylings
823
+ // .map(({ start, finish }) => [start, finish])
824
+ // .flat()
825
+
826
+ // // Sort ascendingly
827
+ // indices = indices.sort((a, b) => a - b)
828
+
829
+ // // Remove duplicates
830
+ // indices = indices.filter(
831
+ // (element, index) => indices.indexOf(element) == index
832
+ // )
833
+
834
+ // // Add first index if not present
835
+ // if (indices[0] != 0) indices.unshift(0)
836
+
837
+ // // Add last index if not present
838
+ // let last = content.length
839
+ // if (indices[indices.length - 1] != last) indices.push(last)
840
+
841
+ // // let result = []
842
+
843
+ // for (let i = 0; i < indices.length - 1; i++) {
844
+ // result.push([indices[i], content.substring(indices[i], indices[i + 1])])
845
+ // }
846
+
675
847
  return result
676
848
  }
677
849
 
678
850
  // dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
679
851
  useEffect(() => {
680
852
  let html = getContent()
681
- .map((_data, index) => {
682
- let [start, data] = _data
683
-
684
- // Get stylings encompassing an index within it's range
685
- let stylings = getIntersectStylings(start)
686
- return `<span class="${stylings
687
- .map((styling) => stylers[styling.type].css)
688
- .join(" ")}" data-child-index="${index}">${data}</span>`
853
+ .map((line, index) => {
854
+ return `<div class="line" data-line-index="${index}">${line
855
+ .map((span, _index) => {
856
+ return `<span class="${span.types
857
+ .map((type) => stylers[type].css)
858
+ .join(" ")}" data-child-index="${_index}">${span.content}</span>`
859
+ })
860
+ .join("")}</div>`
689
861
  })
690
862
  .join("")
691
863
 
864
+ // let html = getContent()
865
+ // .map((_data, index) => {
866
+ // let [start, data] = _data
867
+
868
+ // // Get stylings encompassing an index within it's range
869
+ // let stylings = getIntersectStylings(_text.current.stylings, start)
870
+ // return `<span class="${stylings
871
+ // .map((styling) => stylers[styling.type].css)
872
+ // .join(" ")}" data-child-index="${index}">${data}</span>`
873
+ // })
874
+ // .join("")
875
+
692
876
  field.current.innerHTML = html
693
877
  }, [text.content, text.stylings, text.revert])
694
878
 
@@ -722,6 +906,17 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
722
906
  })}
723
907
  </div>
724
908
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
909
+ {/* <div className="selection-ignore rtl h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none font-['Arial'] text-[16px] outline-none">
910
+ <div className="line" data-line-index="0">
911
+ <span className="" data-child-index="0">
912
+ asd
913
+ </span>
914
+ </div>
915
+ <div className="line" data-line-index="1">
916
+ <br />
917
+ </div>
918
+ </div>
919
+ */}
725
920
  <div className="selection-ignore box-border w-full p-2">
726
921
  <div
727
922
  ref={field}
@@ -733,9 +928,45 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
733
928
  onPaste={(event) => {
734
929
  // pastes all copied text from the content editable as plain text
735
930
  event.preventDefault()
736
- const data = event.clipboardData.getData("text/plain")
931
+ let data = event.clipboardData.getData("text/plain")
932
+ data = data.replaceAll("\n", "")
933
+ data = data.replaceAll("\r", "")
934
+
737
935
  document.execCommand("insertHTML", false, data)
738
936
 
937
+ // selection.anchorOffset <= 1 &&
938
+ // selection.focusOffset <= 1
939
+ // ) {
940
+ // let lineIndex = getChildIndex(anchorNode.parentNode.parentNode)
941
+
942
+ // let _content = content
943
+ // if (content.startsWith(lineBreakIdentifier)) {
944
+ // _content = _content.slice(1)
945
+ // }
946
+
947
+ // let originalLine: any =
948
+ // _content.split(lineBreakIdentifier)[lineIndex]
949
+
950
+ // console.log(_content.split(lineBreakIdentifier))
951
+
952
+ // originalLine = originalLine.split("")
953
+ // originalLine.splice(0, 0, eventData)
954
+ // originalLine = originalLine.join("")
955
+
956
+ // let lines = _content.split(lineBreakIdentifier)
957
+
958
+ // lines[lineIndex] = originalLine
959
+
960
+ // textContent = lines.join(lineBreakIdentifier)
961
+
962
+ // if (content.startsWith(lineBreakIdentifier)) {
963
+ // textContent = lineBreakIdentifier + textContent
964
+ // }
965
+
966
+ // start += 1
967
+ // end += 1
968
+ // }
969
+
739
970
  setText({
740
971
  ..._text.current,
741
972
  events: {
@@ -745,7 +976,94 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
745
976
  })
746
977
  }}
747
978
  onInput={(event: any) => {
748
- onChange(event.target.textContent)
979
+ // console.log(event)
980
+ // console.log(event.target.textContent)
981
+ // console.log(event.target.textContent.split(""))
982
+
983
+ let [start, end] = getFieldSelection()
984
+ let textContent = event.target.textContent
985
+ let content = _text.current.content
986
+ // Try creating twice the lines if the index is at 0,0
987
+ // if (!textContent.startsWith(lineBreakIdentifier)) {
988
+ // textContent = lineBreakIdentifier + textContent
989
+ // }
990
+
991
+ let eventType = event.nativeEvent.inputType
992
+
993
+ if (eventType == "insertParagraph") {
994
+ console.log(`Inserted new line at [${start}, ${end}]`)
995
+
996
+ let split = textContent.split("")
997
+
998
+ split.splice(start, 0, lineBreakIdentifier)
999
+ if (start == 0 && !content.startsWith(lineBreakIdentifier)) {
1000
+ split.splice(start, 0, lineBreakIdentifier)
1001
+ start += 1
1002
+ end += 1
1003
+ }
1004
+
1005
+ textContent = split.join("")
1006
+
1007
+ // Increase selection by one to accomodate for the new line
1008
+ start += 1
1009
+ end += 1
1010
+ } else {
1011
+ let selection = window.getSelection()
1012
+ let { anchorNode, focusNode } = selection
1013
+
1014
+ let eventData = event.nativeEvent.data
1015
+
1016
+ // Handle typing behind lines
1017
+ if (
1018
+ anchorNode.parentNode == focusNode.parentNode &&
1019
+ anchorNode.parentNode.nodeName == "SPAN" &&
1020
+ selection.anchorOffset <= 1 &&
1021
+ selection.focusOffset <= 1 &&
1022
+ eventType == "insertText"
1023
+ ) {
1024
+ let lineIndex = getChildIndex(anchorNode.parentNode.parentNode)
1025
+
1026
+ let _content = content
1027
+ if (content.startsWith(lineBreakIdentifier)) {
1028
+ _content = _content.slice(1)
1029
+ }
1030
+
1031
+ let originalLine: any =
1032
+ _content.split(lineBreakIdentifier)[lineIndex]
1033
+
1034
+ console.log(_content.split(lineBreakIdentifier))
1035
+
1036
+ originalLine = originalLine.split("")
1037
+ originalLine.splice(0, 0, eventData)
1038
+ originalLine = originalLine.join("")
1039
+
1040
+ let lines = _content.split(lineBreakIdentifier)
1041
+
1042
+ lines[lineIndex] = originalLine
1043
+
1044
+ textContent = lines.join(lineBreakIdentifier)
1045
+
1046
+ if (content.startsWith(lineBreakIdentifier)) {
1047
+ textContent = lineBreakIdentifier + textContent
1048
+ }
1049
+
1050
+ start += 1
1051
+ end += 1
1052
+ }
1053
+ }
1054
+
1055
+ // if (
1056
+ // eventData != null &&
1057
+ // start - eventData.length == 0 &&
1058
+ // end - eventData.length == 0 &&
1059
+ // content.startsWith(lineBreakIdentifier)
1060
+ // ) {
1061
+ // console.log("STOP")
1062
+ // }
1063
+
1064
+ // console.log(event.nativeEvent.inputType)
1065
+
1066
+ onChange(textContent, start, end)
749
1067
  }}
750
1068
  onCopy={() => {
751
1069
  let [start, end] = getFieldSelection()