wyreframe 0.7.5 → 0.7.7
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
|
@@ -153,17 +153,30 @@ function traceBox(grid, topLeft) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
let nextRow = topLeft.row + 1 | 0;
|
|
156
|
+
let prevRow = topLeft.row - 1 | 0;
|
|
157
|
+
let isMiddleDivider;
|
|
158
|
+
if (prevRow >= 0) {
|
|
159
|
+
let prevLeftPos = Types.Position.make(prevRow, topLeft.col);
|
|
160
|
+
let prevRightPos = Types.Position.make(prevRow, topRightOpt.col);
|
|
161
|
+
let leftChar = Grid.get(grid, prevLeftPos);
|
|
162
|
+
let rightChar = Grid.get(grid, prevRightPos);
|
|
163
|
+
isMiddleDivider = leftChar === "VLine" && typeof leftChar !== "object" ? rightChar === "VLine" && typeof rightChar !== "object" : false;
|
|
164
|
+
} else {
|
|
165
|
+
isMiddleDivider = false;
|
|
166
|
+
}
|
|
156
167
|
let isValidTopEdge;
|
|
157
|
-
if (nextRow
|
|
168
|
+
if (isMiddleDivider || nextRow >= grid.height) {
|
|
169
|
+
isValidTopEdge = false;
|
|
170
|
+
} else {
|
|
158
171
|
let nextLeftPos = Types.Position.make(nextRow, topLeft.col);
|
|
159
172
|
let nextRightPos = Types.Position.make(nextRow, topRightOpt.col);
|
|
160
|
-
let leftChar = Grid.get(grid, nextLeftPos);
|
|
161
|
-
let rightChar = Grid.get(grid, nextRightPos);
|
|
162
|
-
if (leftChar !== undefined && typeof leftChar !== "object") {
|
|
163
|
-
switch (leftChar) {
|
|
173
|
+
let leftChar$1 = Grid.get(grid, nextLeftPos);
|
|
174
|
+
let rightChar$1 = Grid.get(grid, nextRightPos);
|
|
175
|
+
if (leftChar$1 !== undefined && typeof leftChar$1 !== "object") {
|
|
176
|
+
switch (leftChar$1) {
|
|
164
177
|
case "Corner" :
|
|
165
|
-
if (rightChar !== undefined && typeof rightChar !== "object") {
|
|
166
|
-
switch (rightChar) {
|
|
178
|
+
if (rightChar$1 !== undefined && typeof rightChar$1 !== "object") {
|
|
179
|
+
switch (rightChar$1) {
|
|
167
180
|
case "Corner" :
|
|
168
181
|
isValidTopEdge = false;
|
|
169
182
|
break;
|
|
@@ -175,8 +188,8 @@ function traceBox(grid, topLeft) {
|
|
|
175
188
|
}
|
|
176
189
|
break;
|
|
177
190
|
case "VLine" :
|
|
178
|
-
if (rightChar !== undefined && typeof rightChar !== "object") {
|
|
179
|
-
switch (rightChar) {
|
|
191
|
+
if (rightChar$1 !== undefined && typeof rightChar$1 !== "object") {
|
|
192
|
+
switch (rightChar$1) {
|
|
180
193
|
default:
|
|
181
194
|
isValidTopEdge = true;
|
|
182
195
|
}
|
|
@@ -190,8 +203,6 @@ function traceBox(grid, topLeft) {
|
|
|
190
203
|
} else {
|
|
191
204
|
isValidTopEdge = true;
|
|
192
205
|
}
|
|
193
|
-
} else {
|
|
194
|
-
isValidTopEdge = false;
|
|
195
206
|
}
|
|
196
207
|
if (!isValidTopEdge) {
|
|
197
208
|
return {
|
|
@@ -237,8 +237,38 @@ let traceBox = (grid: Grid.t, topLeft: Position.t): traceResult => {
|
|
|
237
237
|
// in the next row at the left and right columns, NOT Corner characters (+).
|
|
238
238
|
// If the next row has corners at both positions, this is likely the
|
|
239
239
|
// bottom edge of one box immediately followed by the top edge of another.
|
|
240
|
+
//
|
|
241
|
+
// Issue #20 fix: Also check if this corner is a MIDDLE divider of a table.
|
|
242
|
+
// A middle divider has VLine characters in the PREVIOUS row at both corners.
|
|
243
|
+
// Example table structure:
|
|
244
|
+
// +-----------+-----------+ <- Row 3: Top edge (valid)
|
|
245
|
+
// | Header1 | Header2 | <- Row 4: Content row
|
|
246
|
+
// +-----------+-----------+ <- Row 5: Middle divider (INVALID as top edge)
|
|
247
|
+
// | Cell 1 | Cell 2 | <- Row 6: Content row
|
|
248
|
+
// +-----------+-----------+ <- Row 7: Bottom edge
|
|
249
|
+
// Row 5 has VLines in both previous (row 4) AND next (row 6) rows,
|
|
250
|
+
// so it's a middle divider, not a valid top edge.
|
|
240
251
|
let nextRow = topLeft.row + 1
|
|
241
|
-
let
|
|
252
|
+
let prevRow = topLeft.row - 1
|
|
253
|
+
|
|
254
|
+
// Check if previous row has VLines at both corners (indicates middle divider)
|
|
255
|
+
let isMiddleDivider = if prevRow >= 0 {
|
|
256
|
+
let prevLeftPos = Position.make(prevRow, topLeft.col)
|
|
257
|
+
let prevRightPos = Position.make(prevRow, topRight.col)
|
|
258
|
+
let leftChar = Grid.get(grid, prevLeftPos)
|
|
259
|
+
let rightChar = Grid.get(grid, prevRightPos)
|
|
260
|
+
// If previous row has VLine at BOTH positions, this is a middle divider
|
|
261
|
+
switch (leftChar, rightChar) {
|
|
262
|
+
| (Some(VLine), Some(VLine)) => true // Previous row has content = middle divider
|
|
263
|
+
| _ => false
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
false // No previous row = could be valid top edge
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let isValidTopEdge = if isMiddleDivider {
|
|
270
|
+
false // Middle dividers are not valid top edges
|
|
271
|
+
} else if nextRow < grid.height {
|
|
242
272
|
let nextLeftPos = Position.make(nextRow, topLeft.col)
|
|
243
273
|
let nextRightPos = Position.make(nextRow, topRight.col)
|
|
244
274
|
let leftChar = Grid.get(grid, nextLeftPos)
|
|
@@ -577,6 +577,40 @@ function stripSectionBorders(line) {
|
|
|
577
577
|
}
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
+
function getSegmentWidth(segment) {
|
|
581
|
+
switch (segment.TAG) {
|
|
582
|
+
case "ButtonSegment" :
|
|
583
|
+
return segment._0.length + 4 | 0;
|
|
584
|
+
case "TextSegment" :
|
|
585
|
+
case "LinkSegment" :
|
|
586
|
+
return segment._0.length;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function getSegmentOffset(segment) {
|
|
591
|
+
return segment._1;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function calculateRowAlignment(segments, baseCol, row, bounds) {
|
|
595
|
+
let match = segments[0];
|
|
596
|
+
let match$1 = segments[segments.length - 1 | 0];
|
|
597
|
+
if (match === undefined) {
|
|
598
|
+
return "Left";
|
|
599
|
+
}
|
|
600
|
+
if (match$1 === undefined) {
|
|
601
|
+
return "Left";
|
|
602
|
+
}
|
|
603
|
+
let firstOffset = getSegmentOffset(match);
|
|
604
|
+
let startCol = baseCol + firstOffset | 0;
|
|
605
|
+
let lastOffset = getSegmentOffset(match$1);
|
|
606
|
+
let lastWidth = getSegmentWidth(match$1);
|
|
607
|
+
let endCol = (baseCol + lastOffset | 0) + lastWidth | 0;
|
|
608
|
+
let rowWidth = endCol - startCol | 0;
|
|
609
|
+
let virtualContent = "x".repeat(rowWidth);
|
|
610
|
+
let position = Types.Position.make(row, startCol);
|
|
611
|
+
return AlignmentCalc.calculate(virtualContent, position, bounds);
|
|
612
|
+
}
|
|
613
|
+
|
|
580
614
|
function segmentToElement(segment, basePosition, baseCol, bounds) {
|
|
581
615
|
switch (segment.TAG) {
|
|
582
616
|
case "TextSegment" :
|
|
@@ -667,39 +701,43 @@ function parseContentLine(line, lineIndex, contentStartRow, box, registry) {
|
|
|
667
701
|
let basePosition = Types.Position.make(row$1, baseCol$1);
|
|
668
702
|
let segments = splitInlineSegments(trimmed);
|
|
669
703
|
if (segments.length > 1) {
|
|
670
|
-
let
|
|
704
|
+
let trimmedStart = line.trimStart();
|
|
705
|
+
let leadingSpaces = line.length - trimmedStart.length | 0;
|
|
706
|
+
let actualBaseCol = baseCol$1 + leadingSpaces | 0;
|
|
707
|
+
let rowChildren = segments.map(segment => segmentToElement(segment, basePosition, actualBaseCol, box.bounds));
|
|
708
|
+
let rowAlign = calculateRowAlignment(segments, actualBaseCol, row$1, box.bounds);
|
|
671
709
|
return {
|
|
672
710
|
TAG: "Row",
|
|
673
711
|
children: rowChildren,
|
|
674
|
-
align:
|
|
712
|
+
align: rowAlign
|
|
675
713
|
};
|
|
676
714
|
}
|
|
677
715
|
if (segments.length === 1) {
|
|
678
|
-
let trimmedStart = line.trimStart();
|
|
679
|
-
let leadingSpaces = line.length - trimmedStart.length | 0;
|
|
716
|
+
let trimmedStart$1 = line.trimStart();
|
|
717
|
+
let leadingSpaces$1 = line.length - trimmedStart$1.length | 0;
|
|
680
718
|
let match = segments[0];
|
|
681
719
|
if (match !== undefined) {
|
|
682
720
|
switch (match.TAG) {
|
|
683
721
|
case "TextSegment" :
|
|
684
722
|
break;
|
|
685
723
|
case "ButtonSegment" :
|
|
686
|
-
let actualCol = (baseCol$1 + leadingSpaces | 0) + match._1 | 0;
|
|
724
|
+
let actualCol = (baseCol$1 + leadingSpaces$1 | 0) + match._1 | 0;
|
|
687
725
|
let position$1 = Types.Position.make(row$1, actualCol);
|
|
688
726
|
let buttonContent = "[ " + match._0 + " ]";
|
|
689
727
|
return ParserRegistry.parse(registry, buttonContent, position$1, box.bounds);
|
|
690
728
|
case "LinkSegment" :
|
|
691
|
-
let actualCol$1 = (baseCol$1 + leadingSpaces | 0) + match._1 | 0;
|
|
729
|
+
let actualCol$1 = (baseCol$1 + leadingSpaces$1 | 0) + match._1 | 0;
|
|
692
730
|
let position$2 = Types.Position.make(row$1, actualCol$1);
|
|
693
731
|
let linkContent = "\"" + match._0 + "\"";
|
|
694
732
|
return ParserRegistry.parse(registry, linkContent, position$2, box.bounds);
|
|
695
733
|
}
|
|
696
734
|
}
|
|
697
|
-
let position$3 = Types.Position.make(row$1, baseCol$1 + leadingSpaces | 0);
|
|
735
|
+
let position$3 = Types.Position.make(row$1, baseCol$1 + leadingSpaces$1 | 0);
|
|
698
736
|
return ParserRegistry.parse(registry, trimmed, position$3, box.bounds);
|
|
699
737
|
}
|
|
700
|
-
let trimmedStart$
|
|
701
|
-
let leadingSpaces$
|
|
702
|
-
let position$4 = Types.Position.make(row$1, baseCol$1 + leadingSpaces$
|
|
738
|
+
let trimmedStart$2 = line.trimStart();
|
|
739
|
+
let leadingSpaces$2 = line.length - trimmedStart$2.length | 0;
|
|
740
|
+
let position$4 = Types.Position.make(row$1, baseCol$1 + leadingSpaces$2 | 0);
|
|
703
741
|
return ParserRegistry.parse(registry, trimmed, position$4, box.bounds);
|
|
704
742
|
}
|
|
705
743
|
|
|
@@ -1003,6 +1041,9 @@ export {
|
|
|
1003
1041
|
extractSectionName,
|
|
1004
1042
|
isSectionFooter,
|
|
1005
1043
|
stripSectionBorders,
|
|
1044
|
+
getSegmentWidth,
|
|
1045
|
+
getSegmentOffset,
|
|
1046
|
+
calculateRowAlignment,
|
|
1006
1047
|
segmentToElement,
|
|
1007
1048
|
isNoiseText,
|
|
1008
1049
|
parseContentLine,
|
|
@@ -955,6 +955,76 @@ let stripSectionBorders = (line: string): string => {
|
|
|
955
955
|
*
|
|
956
956
|
* Requirements: REQ-15 (Element parsing and AST generation)
|
|
957
957
|
*/
|
|
958
|
+
/**
|
|
959
|
+
* Get the visual width of an inline segment.
|
|
960
|
+
* - ButtonSegment: "[ text ]" = text length + 4
|
|
961
|
+
* - LinkSegment: just the text length
|
|
962
|
+
* - TextSegment: just the text length
|
|
963
|
+
*/
|
|
964
|
+
let getSegmentWidth = (segment: inlineSegment): int => {
|
|
965
|
+
switch segment {
|
|
966
|
+
| TextSegment(text, _) => String.length(text)
|
|
967
|
+
| ButtonSegment(text, _) => String.length(text) + 4 // "[ " + text + " ]"
|
|
968
|
+
| LinkSegment(text, _) => String.length(text)
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Get the column offset of an inline segment.
|
|
974
|
+
*/
|
|
975
|
+
let getSegmentOffset = (segment: inlineSegment): int => {
|
|
976
|
+
switch segment {
|
|
977
|
+
| TextSegment(_, offset) => offset
|
|
978
|
+
| ButtonSegment(_, offset) => offset
|
|
979
|
+
| LinkSegment(_, offset) => offset
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Calculate the alignment for a Row element based on the combined span
|
|
985
|
+
* of all its segments within the container bounds.
|
|
986
|
+
*
|
|
987
|
+
* Algorithm:
|
|
988
|
+
* 1. Find the start position of the first segment
|
|
989
|
+
* 2. Find the end position of the last segment (offset + width)
|
|
990
|
+
* 3. Calculate the total content span
|
|
991
|
+
* 4. Use AlignmentCalc to determine alignment based on left/right space
|
|
992
|
+
*
|
|
993
|
+
* @param segments - Array of inline segments in the row
|
|
994
|
+
* @param baseCol - The base column (left border + 1)
|
|
995
|
+
* @param row - The row number in the grid
|
|
996
|
+
* @param bounds - The container bounds
|
|
997
|
+
* @returns The calculated alignment for the Row
|
|
998
|
+
*/
|
|
999
|
+
let calculateRowAlignment = (
|
|
1000
|
+
segments: array<inlineSegment>,
|
|
1001
|
+
baseCol: int,
|
|
1002
|
+
row: int,
|
|
1003
|
+
bounds: Bounds.t,
|
|
1004
|
+
): Types.alignment => {
|
|
1005
|
+
switch (segments->Array.get(0), segments->Array.get(Array.length(segments) - 1)) {
|
|
1006
|
+
| (Some(firstSegment), Some(lastSegment)) => {
|
|
1007
|
+
// Calculate the start position (first segment's offset)
|
|
1008
|
+
let firstOffset = getSegmentOffset(firstSegment)
|
|
1009
|
+
let startCol = baseCol + firstOffset
|
|
1010
|
+
|
|
1011
|
+
// Calculate the end position (last segment's offset + width)
|
|
1012
|
+
let lastOffset = getSegmentOffset(lastSegment)
|
|
1013
|
+
let lastWidth = getSegmentWidth(lastSegment)
|
|
1014
|
+
let endCol = baseCol + lastOffset + lastWidth
|
|
1015
|
+
|
|
1016
|
+
// Create a virtual content string with length equal to the row span
|
|
1017
|
+
let rowWidth = endCol - startCol
|
|
1018
|
+
let virtualContent = String.repeat("x", rowWidth)
|
|
1019
|
+
|
|
1020
|
+
// Use AlignmentCalc to calculate alignment based on position
|
|
1021
|
+
let position = Position.make(row, startCol)
|
|
1022
|
+
AlignmentCalc.calculate(virtualContent, position, bounds)
|
|
1023
|
+
}
|
|
1024
|
+
| _ => Left // Fallback to Left if no segments
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
958
1028
|
/**
|
|
959
1029
|
* Convert inline segment to element.
|
|
960
1030
|
* Creates appropriate element type based on segment variant.
|
|
@@ -1119,13 +1189,22 @@ let parseContentLine = (
|
|
|
1119
1189
|
|
|
1120
1190
|
if segments->Array.length > 1 {
|
|
1121
1191
|
// Multiple segments - create a Row with all elements
|
|
1122
|
-
//
|
|
1192
|
+
// Issue #21: Account for leading spaces in the original line for correct alignment
|
|
1193
|
+
let leadingSpaces = {
|
|
1194
|
+
let original = line
|
|
1195
|
+
let trimmedStart = original->String.trimStart
|
|
1196
|
+
original->String.length - trimmedStart->String.length
|
|
1197
|
+
}
|
|
1198
|
+
// Calculate actual column including leading spaces
|
|
1199
|
+
let actualBaseCol = baseCol + leadingSpaces
|
|
1123
1200
|
let rowChildren = segments->Array.map(segment => {
|
|
1124
|
-
segmentToElement(segment, basePosition,
|
|
1201
|
+
segmentToElement(segment, basePosition, actualBaseCol, box.bounds)
|
|
1125
1202
|
})
|
|
1203
|
+
// Issue #21: Calculate Row alignment based on combined segment positions
|
|
1204
|
+
let rowAlign = calculateRowAlignment(segments, actualBaseCol, row, box.bounds)
|
|
1126
1205
|
Some(Row({
|
|
1127
1206
|
children: rowChildren,
|
|
1128
|
-
align:
|
|
1207
|
+
align: rowAlign,
|
|
1129
1208
|
}))
|
|
1130
1209
|
} else if segments->Array.length === 1 {
|
|
1131
1210
|
// Single segment - check if it's a special element
|