@servicetitan/dte-pdf-editor 1.94.0 → 1.95.0
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/README.md +37 -2
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +4 -6
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js +27 -4
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/shared/char-limit-indicator.d.ts +8 -0
- package/dist/components/shared/char-limit-indicator.d.ts.map +1 -0
- package/dist/components/shared/char-limit-indicator.js +9 -0
- package/dist/components/shared/char-limit-indicator.js.map +1 -0
- package/dist/components/shared/index.d.ts +1 -0
- package/dist/components/shared/index.d.ts.map +1 -1
- package/dist/components/shared/index.js +1 -0
- package/dist/components/shared/index.js.map +1 -1
- package/dist/styles/pdf-field-overlay.css +9 -2
- package/dist/styles/pdf-field.css +22 -1
- package/dist/utils/field/field-text-box.utils.d.ts +31 -0
- package/dist/utils/field/field-text-box.utils.d.ts.map +1 -0
- package/dist/utils/field/field-text-box.utils.js +102 -0
- package/dist/utils/field/field-text-box.utils.js.map +1 -0
- package/dist/utils/field/fit-field-text.utils.d.ts +13 -0
- package/dist/utils/field/fit-field-text.utils.d.ts.map +1 -0
- package/dist/utils/field/fit-field-text.utils.js +44 -0
- package/dist/utils/field/fit-field-text.utils.js.map +1 -0
- package/dist/utils/field/get-field-max-length.utils.d.ts +38 -0
- package/dist/utils/field/get-field-max-length.utils.d.ts.map +1 -0
- package/dist/utils/field/get-field-max-length.utils.js +166 -0
- package/dist/utils/field/get-field-max-length.utils.js.map +1 -0
- package/dist/utils/field/index.d.ts +2 -0
- package/dist/utils/field/index.d.ts.map +1 -1
- package/dist/utils/field/index.js +2 -0
- package/dist/utils/field/index.js.map +1 -1
- package/dist/utils/field/text-wrap.utils.d.ts +26 -0
- package/dist/utils/field/text-wrap.utils.d.ts.map +1 -0
- package/dist/utils/field/text-wrap.utils.js +53 -0
- package/dist/utils/field/text-wrap.utils.js.map +1 -0
- package/package.json +1 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +41 -28
- package/src/components/pdf-view/pdf-view-fillable.tsx +82 -36
- package/src/components/shared/char-limit-indicator.tsx +23 -0
- package/src/components/shared/index.ts +1 -0
- package/src/styles/pdf-field-overlay.css +9 -2
- package/src/styles/pdf-field.css +22 -1
- package/src/utils/field/__tests__/fit-field-text.utils.test.ts +224 -0
- package/src/utils/field/__tests__/get-field-max-length.utils.test.ts +751 -0
- package/src/utils/field/__tests__/text-wrap.utils.test.ts +181 -0
- package/src/utils/field/field-text-box.utils.ts +143 -0
- package/src/utils/field/fit-field-text.utils.ts +61 -0
- package/src/utils/field/get-field-max-length.utils.ts +211 -0
- package/src/utils/field/index.ts +2 -0
- package/src/utils/field/text-wrap.utils.ts +78 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { EPSILON, getFieldTextBox, getMaxLines } from './field-text-box.utils';
|
|
2
|
+
import { advanceWrap, INITIAL_WRAP_STATE, } from './text-wrap.utils';
|
|
3
|
+
/**
|
|
4
|
+
* Typical free-text form entries (names, addresses, emails, short notes) in mixed case.
|
|
5
|
+
*
|
|
6
|
+
* Capacity is estimated for this text rather than for a uniform `A-Za-z0-9` alphabet: that one
|
|
7
|
+
* averages ~0.58em per glyph against ~0.46em here (and ~0.47em for real entries), which is why
|
|
8
|
+
* the previous estimate under-reported capacity by ~20%. Contains no hyphens or newlines, so
|
|
9
|
+
* the wrap simulation in `text-wrap.utils` stays exact for it.
|
|
10
|
+
*/
|
|
11
|
+
const TEXT_SAMPLE = 'Jonathan Alexander Smith 1234 Elm Street, Apartment 5B Springfield, IL 62704 ' +
|
|
12
|
+
'jonathan.smith@example.com Maria Garcia Lopez Estimate #48213 for $1,250.00 ' +
|
|
13
|
+
'Replace the capacitor and clean the coils. Call before arriving, gate code 4471. ' +
|
|
14
|
+
'Dana Whitfield, Park Avenue Plumbing ';
|
|
15
|
+
/** Glyphs a user can type into a number field; the capacity is bounded by the widest of them. */
|
|
16
|
+
const NUMBER_GLYPHS = '0123456789.-';
|
|
17
|
+
/** Evenly spaced word-start offsets into the sample that multiline wrapping is averaged over. */
|
|
18
|
+
const MULTILINE_SAMPLE_PHASES = 8;
|
|
19
|
+
/** Hard stop for the search, so a degenerate measurer (zero-width glyphs) can't loop forever. */
|
|
20
|
+
const MAX_CAPACITY = 10000;
|
|
21
|
+
/**
|
|
22
|
+
* Largest `length` in `[0, max]` for which `fits(length)` holds; `fits` must be monotone
|
|
23
|
+
* (once a length stops fitting, longer ones don't either) and `fits(0)` is taken as true.
|
|
24
|
+
*/
|
|
25
|
+
export const findLargestFit = (fits, max = MAX_CAPACITY) => {
|
|
26
|
+
let low = 0;
|
|
27
|
+
let high = max + 1;
|
|
28
|
+
for (let probe = 1; probe < high; probe *= 2) {
|
|
29
|
+
if (!fits(probe)) {
|
|
30
|
+
high = probe;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
low = probe;
|
|
34
|
+
}
|
|
35
|
+
while (high - low > 1) {
|
|
36
|
+
const mid = Math.floor((low + high) / 2);
|
|
37
|
+
if (fits(mid)) {
|
|
38
|
+
low = mid;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
high = mid;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return low;
|
|
45
|
+
};
|
|
46
|
+
/** The sample starting at `start` and wrapping around, repeated to `length` characters. */
|
|
47
|
+
const getSampleText = (start, length) => {
|
|
48
|
+
const rotated = TEXT_SAMPLE.slice(start) + TEXT_SAMPLE.slice(0, start);
|
|
49
|
+
return rotated.repeat(Math.ceil(length / rotated.length)).slice(0, length);
|
|
50
|
+
};
|
|
51
|
+
const SAMPLE_WORD_STARTS = Array.from(TEXT_SAMPLE, (char, index) => index).filter(index => {
|
|
52
|
+
return TEXT_SAMPLE[index] !== ' ' && (index === 0 || TEXT_SAMPLE[index - 1] === ' ');
|
|
53
|
+
});
|
|
54
|
+
const SAMPLE_PHASES = Array.from({ length: MULTILINE_SAMPLE_PHASES }, (_, index) => {
|
|
55
|
+
return SAMPLE_WORD_STARTS[Math.floor((index * SAMPLE_WORD_STARTS.length) / MULTILINE_SAMPLE_PHASES)];
|
|
56
|
+
});
|
|
57
|
+
const getRemainingWidth = ({ measure, width }, value) => {
|
|
58
|
+
return Math.max(0, width - measure(value));
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Number inputs only ever hold digits, `.` and `-`, so every value of N characters is at most
|
|
62
|
+
* N widest-glyphs wide: the room left is a guaranteed fit, not an average.
|
|
63
|
+
*/
|
|
64
|
+
const estimateNumberRoom = (box, value) => {
|
|
65
|
+
const widestGlyph = Math.max(...Array.from(NUMBER_GLYPHS, glyph => box.measure(glyph)));
|
|
66
|
+
return Math.floor(getRemainingWidth(box, value) / widestGlyph + EPSILON);
|
|
67
|
+
};
|
|
68
|
+
/** Characters of typical text that fit in the width left: it over the sample's mean glyph. */
|
|
69
|
+
const estimateSingleLineRoom = (box, value) => {
|
|
70
|
+
const meanGlyphWidth = box.measure(TEXT_SAMPLE) / TEXT_SAMPLE.length;
|
|
71
|
+
return Math.floor(getRemainingWidth(box, value) / meanGlyphWidth + EPSILON);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Characters of typical text that fit in the lines left after `value`. Real wrapping is
|
|
75
|
+
* simulated, so the unused space at the end of each line is accounted for; the result is
|
|
76
|
+
* averaged over several starting points in the sample because it shifts by a few characters
|
|
77
|
+
* with where the text begins.
|
|
78
|
+
*/
|
|
79
|
+
const estimateMultilineRoom = (box, value) => {
|
|
80
|
+
const maxLines = getMaxLines(box);
|
|
81
|
+
const measureCache = new Map();
|
|
82
|
+
const measure = text => {
|
|
83
|
+
let measured = measureCache.get(text);
|
|
84
|
+
if (measured === undefined) {
|
|
85
|
+
measured = box.measure(text);
|
|
86
|
+
measureCache.set(text, measured);
|
|
87
|
+
}
|
|
88
|
+
return measured;
|
|
89
|
+
};
|
|
90
|
+
const valueState = advanceWrap(INITIAL_WRAP_STATE, value, box.width, measure);
|
|
91
|
+
if (valueState.lines > maxLines) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
// typed text goes on after a word, so continue it with a space unless it already ends in one
|
|
95
|
+
const separator = /(^|[ \n])$/.test(value) ? '' : ' ';
|
|
96
|
+
const fitsAfterValue = (state, text) => {
|
|
97
|
+
return advanceWrap(state, text, box.width, measure).lines <= maxLines;
|
|
98
|
+
};
|
|
99
|
+
const capacities = SAMPLE_PHASES.map(start => {
|
|
100
|
+
return findLargestFit(length => {
|
|
101
|
+
return fitsAfterValue(valueState, separator + getSampleText(start, length));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
return Math.floor(capacities.reduce((sum, value) => sum + value, 0) / capacities.length);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Highest character count a text box can hold, from rendered text widths: the characters
|
|
108
|
+
* already in `value` plus how many more fit after it.
|
|
109
|
+
*
|
|
110
|
+
* - `number`: guaranteed fit, bounded by the widest of the digits, `.` and `-`
|
|
111
|
+
* - `single-line`: expected fit for typical text, from the mean glyph width of a sample
|
|
112
|
+
* - `multiline`: expected fit for typical text, from simulated word wrapping into the
|
|
113
|
+
* `floor(height / lineHeight)` lines available
|
|
114
|
+
*
|
|
115
|
+
* Narrow text leaves more room than wide text, so the answer moves with what is typed. A
|
|
116
|
+
* value that already overflows has no room left: the capacity is just its own length.
|
|
117
|
+
* At least 1; `undefined` when `measure` yields no usable text width, since there is then
|
|
118
|
+
* no basis for a limit.
|
|
119
|
+
*/
|
|
120
|
+
export const estimateTextCapacity = ({ value = '', ...box }) => {
|
|
121
|
+
const sampleWidth = box.measure(TEXT_SAMPLE);
|
|
122
|
+
if (!Number.isFinite(sampleWidth) || sampleWidth <= 0) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
let room;
|
|
126
|
+
switch (box.layout) {
|
|
127
|
+
case 'number':
|
|
128
|
+
room = estimateNumberRoom(box, value);
|
|
129
|
+
break;
|
|
130
|
+
case 'multiline':
|
|
131
|
+
room = estimateMultilineRoom(box, value);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
room = estimateSingleLineRoom(box, value);
|
|
135
|
+
}
|
|
136
|
+
return Number.isNaN(room)
|
|
137
|
+
? undefined
|
|
138
|
+
: Math.max(1, value.length + Math.min(MAX_CAPACITY, room));
|
|
139
|
+
};
|
|
140
|
+
const MAX_LENGTH_CACHE_SIZE = 500;
|
|
141
|
+
const maxLengthCache = new Map();
|
|
142
|
+
/**
|
|
143
|
+
* Max character count a fillable `text`/`number` field can hold given what is typed in it, so
|
|
144
|
+
* the counter can say how much room is left. With no `value` it is the field's capacity when
|
|
145
|
+
* empty. Input is limited by what actually fits, see `fitFieldTextChange`, not by this count.
|
|
146
|
+
*
|
|
147
|
+
* See {@link estimateTextCapacity} for how each field kind is measured. Text is measured
|
|
148
|
+
* with canvas at the field's own font, so it follows family, weight and size.
|
|
149
|
+
*/
|
|
150
|
+
export const getFieldMaxLength = (field, value = '') => {
|
|
151
|
+
const box = getFieldTextBox(field);
|
|
152
|
+
if (!box) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const cacheKey = `${box.key}|${value}`;
|
|
156
|
+
if (maxLengthCache.has(cacheKey)) {
|
|
157
|
+
return maxLengthCache.get(cacheKey);
|
|
158
|
+
}
|
|
159
|
+
const maxLength = estimateTextCapacity({ ...box, value });
|
|
160
|
+
if (maxLengthCache.size >= MAX_LENGTH_CACHE_SIZE) {
|
|
161
|
+
maxLengthCache.clear();
|
|
162
|
+
}
|
|
163
|
+
maxLengthCache.set(cacheKey, maxLength);
|
|
164
|
+
return maxLength;
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=get-field-max-length.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-field-max-length.utils.js","sourceRoot":"","sources":["../../../src/utils/field/get-field-max-length.utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,10 @@ export * from './field-drag.utils';
|
|
|
5
5
|
export * from './field-placeholder-text.utils';
|
|
6
6
|
export * from './field-reading-order.utils';
|
|
7
7
|
export * from './field-resize.utils';
|
|
8
|
+
export * from './fit-field-text.utils';
|
|
8
9
|
export * from './get-field-bounds-style.utils';
|
|
9
10
|
export * from './get-field-font-size.utils';
|
|
11
|
+
export * from './get-field-max-length.utils';
|
|
10
12
|
export * from './get-field-text-clip.utils';
|
|
11
13
|
export * from './get-field-type-option.utils';
|
|
12
14
|
export * from './get-field-typography.utils';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/field/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/field/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC"}
|
|
@@ -5,8 +5,10 @@ export * from './field-drag.utils';
|
|
|
5
5
|
export * from './field-placeholder-text.utils';
|
|
6
6
|
export * from './field-reading-order.utils';
|
|
7
7
|
export * from './field-resize.utils';
|
|
8
|
+
export * from './fit-field-text.utils';
|
|
8
9
|
export * from './get-field-bounds-style.utils';
|
|
9
10
|
export * from './get-field-font-size.utils';
|
|
11
|
+
export * from './get-field-max-length.utils';
|
|
10
12
|
export * from './get-field-text-clip.utils';
|
|
11
13
|
export * from './get-field-type-option.utils';
|
|
12
14
|
export * from './get-field-typography.utils';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/field/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/field/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Returns the rendered width of `text` in pixels for one fixed font. */
|
|
2
|
+
export type TextMeasurer = (text: string) => number;
|
|
3
|
+
/** Where text wrapping has got to: the line being filled, and how wide it is so far. */
|
|
4
|
+
export interface WrapState {
|
|
5
|
+
lines: number;
|
|
6
|
+
lineWidth: number;
|
|
7
|
+
}
|
|
8
|
+
/** An empty box still occupies one line. */
|
|
9
|
+
export declare const INITIAL_WRAP_STATE: WrapState;
|
|
10
|
+
/**
|
|
11
|
+
* Continues laying out `text` after `state` in a box `maxWidth` wide, simulating browser layout
|
|
12
|
+
* for `white-space: pre-wrap; overflow-wrap: break-word` (the multiline fillable textarea):
|
|
13
|
+
*
|
|
14
|
+
* - `\n` forces a line break; a trailing `\n` still opens a line, as in a textarea
|
|
15
|
+
* - words wrap at spaces; runs of spaces are preserved and hang past the edge instead of wrapping
|
|
16
|
+
* - a word wider than a whole line is broken between characters (code points)
|
|
17
|
+
*
|
|
18
|
+
* `text` must start at a word boundary of the text already laid out, i.e. not continue a word.
|
|
19
|
+
*
|
|
20
|
+
* Not modelled: break opportunities after hyphens and other punctuation, which only ever let
|
|
21
|
+
* browsers pack lines more tightly, so the line count is never optimistic.
|
|
22
|
+
*/
|
|
23
|
+
export declare const advanceWrap: (state: WrapState, text: string, maxWidth: number, measure: TextMeasurer) => WrapState;
|
|
24
|
+
/** Counts the lines `text` occupies in a box `maxWidth` wide; see {@link advanceWrap}. */
|
|
25
|
+
export declare const countWrappedLines: (text: string, maxWidth: number, measure: TextMeasurer) => number;
|
|
26
|
+
//# sourceMappingURL=text-wrap.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-wrap.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/field/text-wrap.utils.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAEpD,wFAAwF;AACxF,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,EAAE,SAAsC,CAAC;AAExE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GACpB,OAAO,SAAS,EAChB,MAAM,MAAM,EACZ,UAAU,MAAM,EAChB,SAAS,YAAY,KACtB,SAsCF,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,GAC1B,MAAM,MAAM,EACZ,UAAU,MAAM,EAChB,SAAS,YAAY,KACtB,MAEF,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** An empty box still occupies one line. */
|
|
2
|
+
export const INITIAL_WRAP_STATE = { lines: 1, lineWidth: 0 };
|
|
3
|
+
/**
|
|
4
|
+
* Continues laying out `text` after `state` in a box `maxWidth` wide, simulating browser layout
|
|
5
|
+
* for `white-space: pre-wrap; overflow-wrap: break-word` (the multiline fillable textarea):
|
|
6
|
+
*
|
|
7
|
+
* - `\n` forces a line break; a trailing `\n` still opens a line, as in a textarea
|
|
8
|
+
* - words wrap at spaces; runs of spaces are preserved and hang past the edge instead of wrapping
|
|
9
|
+
* - a word wider than a whole line is broken between characters (code points)
|
|
10
|
+
*
|
|
11
|
+
* `text` must start at a word boundary of the text already laid out, i.e. not continue a word.
|
|
12
|
+
*
|
|
13
|
+
* Not modelled: break opportunities after hyphens and other punctuation, which only ever let
|
|
14
|
+
* browsers pack lines more tightly, so the line count is never optimistic.
|
|
15
|
+
*/
|
|
16
|
+
export const advanceWrap = (state, text, maxWidth, measure) => {
|
|
17
|
+
var _a;
|
|
18
|
+
let { lineWidth, lines } = state;
|
|
19
|
+
for (const token of (_a = text.replace(/\r\n?/g, '\n').match(/\n| +|[^ \n]+/g)) !== null && _a !== void 0 ? _a : []) {
|
|
20
|
+
if (token === '\n') {
|
|
21
|
+
lines += 1;
|
|
22
|
+
lineWidth = 0;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const tokenWidth = measure(token);
|
|
26
|
+
if (token[0] === ' ' || lineWidth + tokenWidth <= maxWidth) {
|
|
27
|
+
lineWidth += tokenWidth;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (lineWidth > 0) {
|
|
31
|
+
lines += 1;
|
|
32
|
+
lineWidth = 0;
|
|
33
|
+
}
|
|
34
|
+
if (tokenWidth <= maxWidth) {
|
|
35
|
+
lineWidth = tokenWidth;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const char of token) {
|
|
39
|
+
const charWidth = measure(char);
|
|
40
|
+
if (lineWidth > 0 && lineWidth + charWidth > maxWidth) {
|
|
41
|
+
lines += 1;
|
|
42
|
+
lineWidth = 0;
|
|
43
|
+
}
|
|
44
|
+
lineWidth += charWidth;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { lines, lineWidth };
|
|
48
|
+
};
|
|
49
|
+
/** Counts the lines `text` occupies in a box `maxWidth` wide; see {@link advanceWrap}. */
|
|
50
|
+
export const countWrappedLines = (text, maxWidth, measure) => {
|
|
51
|
+
return advanceWrap(INITIAL_WRAP_STATE, text, maxWidth, measure).lines;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=text-wrap.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-wrap.utils.js","sourceRoot":"","sources":["../../../src/utils/field/text-wrap.utils.ts"],"names":[],"mappings":"AASA,4CAA4C;AAC5C,MAAM,CAAC,MAAM,kBAAkB,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAExE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACvB,KAAgB,EAChB,IAAY,EACZ,QAAgB,EAChB,OAAqB,EACZ,EAAE;;IACX,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,MAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,mCAAI,EAAE,EAAE,CAAC;QAC7E,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjB,KAAK,IAAI,CAAC,CAAC;YACX,SAAS,GAAG,CAAC,CAAC;YACd,SAAS;QACb,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAElC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,SAAS,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC;YACzD,SAAS,IAAI,UAAU,CAAC;YACxB,SAAS;QACb,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAChB,KAAK,IAAI,CAAC,CAAC;YACX,SAAS,GAAG,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;YACzB,SAAS,GAAG,UAAU,CAAC;YACvB,SAAS;QACb,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;gBACpD,KAAK,IAAI,CAAC,CAAC;gBACX,SAAS,GAAG,CAAC,CAAC;YAClB,CAAC;YACD,SAAS,IAAI,SAAS,CAAC;QAC3B,CAAC;IACL,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC7B,IAAY,EACZ,QAAgB,EAChB,OAAqB,EACf,EAAE;IACR,OAAO,WAAW,CAAC,kBAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;AAC1E,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -3,12 +3,14 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
3
3
|
import { type PdfField } from '../../interface/types';
|
|
4
4
|
import {
|
|
5
5
|
getDropdownFieldItems,
|
|
6
|
+
getFieldMaxLength,
|
|
6
7
|
getFieldPlaceholderText,
|
|
7
8
|
getFieldTextClipClassName,
|
|
8
9
|
getFillablePaintedLabel,
|
|
9
10
|
getTodayDateInputValue,
|
|
10
11
|
isRadioDefaultSelected,
|
|
11
12
|
} from '../../utils';
|
|
13
|
+
import { CharLimitIndicator } from '../shared';
|
|
12
14
|
import { AddHandle } from './add-handle';
|
|
13
15
|
|
|
14
16
|
interface PdfOverlayFieldFillableProps {
|
|
@@ -130,14 +132,20 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
|
|
|
130
132
|
case 'number': {
|
|
131
133
|
const defaultNumberValue = field.default?.type === 'number' ? field.default.value : '';
|
|
132
134
|
return (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
<Fragment>
|
|
136
|
+
<input
|
|
137
|
+
key={defaultNumberValue}
|
|
138
|
+
type="number"
|
|
139
|
+
readOnly
|
|
140
|
+
defaultValue={defaultNumberValue}
|
|
141
|
+
className={`dte-pdf-field-fillable ${clipClassName}`}
|
|
142
|
+
placeholder={placeholderText}
|
|
143
|
+
/>
|
|
144
|
+
<CharLimitIndicator
|
|
145
|
+
maxLength={getFieldMaxLength(field, String(defaultNumberValue))}
|
|
146
|
+
value={String(defaultNumberValue)}
|
|
147
|
+
/>
|
|
148
|
+
</Fragment>
|
|
141
149
|
);
|
|
142
150
|
}
|
|
143
151
|
|
|
@@ -166,27 +174,32 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
|
|
|
166
174
|
|
|
167
175
|
default: {
|
|
168
176
|
const defaultTextValue = field.default?.type === 'text' ? field.default.value : '';
|
|
169
|
-
if (field.multiline) {
|
|
170
|
-
return (
|
|
171
|
-
<textarea
|
|
172
|
-
key={defaultTextValue}
|
|
173
|
-
readOnly
|
|
174
|
-
defaultValue={defaultTextValue}
|
|
175
|
-
style={{ font: 'inherit', lineHeight: 'inherit', outline: 'none' }}
|
|
176
|
-
placeholder={placeholderText}
|
|
177
|
-
className={`dte-pdf-field-fillable ${clipClassName}`}
|
|
178
|
-
/>
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
177
|
return (
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
<Fragment>
|
|
179
|
+
{field.multiline ? (
|
|
180
|
+
<textarea
|
|
181
|
+
key={defaultTextValue}
|
|
182
|
+
readOnly
|
|
183
|
+
defaultValue={defaultTextValue}
|
|
184
|
+
style={{ font: 'inherit', lineHeight: 'inherit', outline: 'none' }}
|
|
185
|
+
placeholder={placeholderText}
|
|
186
|
+
className={`dte-pdf-field-fillable ${clipClassName}`}
|
|
187
|
+
/>
|
|
188
|
+
) : (
|
|
189
|
+
<input
|
|
190
|
+
key={defaultTextValue}
|
|
191
|
+
type="text"
|
|
192
|
+
readOnly
|
|
193
|
+
defaultValue={defaultTextValue}
|
|
194
|
+
placeholder={placeholderText}
|
|
195
|
+
className={`dte-pdf-field-fillable ${clipClassName}`}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
<CharLimitIndicator
|
|
199
|
+
maxLength={getFieldMaxLength(field, defaultTextValue)}
|
|
200
|
+
value={defaultTextValue}
|
|
201
|
+
/>
|
|
202
|
+
</Fragment>
|
|
190
203
|
);
|
|
191
204
|
}
|
|
192
205
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC } from 'react';
|
|
1
|
+
import { ChangeEvent, FC, Fragment } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type DataChangePayload,
|
|
4
4
|
type DataModelValues,
|
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
} from '../../interface/types';
|
|
8
8
|
import {
|
|
9
9
|
dateToUtcZero,
|
|
10
|
+
fitFieldTextChange,
|
|
10
11
|
getDropdownFieldItems,
|
|
12
|
+
getFieldMaxLength,
|
|
11
13
|
getFieldPlaceholderText,
|
|
12
14
|
getFieldTextClipClassName,
|
|
13
15
|
getFillablePaintedLabel,
|
|
14
16
|
toDateInputValue,
|
|
15
17
|
} from '../../utils';
|
|
18
|
+
import { CharLimitIndicator } from '../shared';
|
|
16
19
|
|
|
17
20
|
interface PdfViewFillableProps {
|
|
18
21
|
field: PdfField;
|
|
@@ -43,6 +46,33 @@ const fillableTextStyles = {
|
|
|
43
46
|
margin: 0,
|
|
44
47
|
} as const;
|
|
45
48
|
|
|
49
|
+
type FillableTextChangeEvent = ChangeEvent<HTMLInputElement | HTMLTextAreaElement>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Passes on a text change, cut back to what fits the field. A change that adds only overflow is
|
|
53
|
+
* dropped; either way the caret stays where the user was typing, as with a native `maxLength`.
|
|
54
|
+
*/
|
|
55
|
+
const applyFittedChange = (
|
|
56
|
+
field: PdfField,
|
|
57
|
+
value: string,
|
|
58
|
+
event: FillableTextChangeEvent,
|
|
59
|
+
onChange: (value: string) => void,
|
|
60
|
+
) => {
|
|
61
|
+
const { selectionStart, value: nextValue } = event.target;
|
|
62
|
+
const fittedValue = fitFieldTextChange(field, value, nextValue);
|
|
63
|
+
|
|
64
|
+
if (fittedValue !== value) {
|
|
65
|
+
onChange(fittedValue);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (fittedValue !== nextValue && selectionStart != null) {
|
|
69
|
+
const caret = fittedValue.length - (nextValue.length - selectionStart);
|
|
70
|
+
const input = event.target;
|
|
71
|
+
// after React has put the fitted value back into the control, which moves the caret
|
|
72
|
+
queueMicrotask(() => input.setSelectionRange(caret, caret));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
46
76
|
export const FillableTextInput: FC<FillableTextInputProps> = ({
|
|
47
77
|
disabled,
|
|
48
78
|
field,
|
|
@@ -50,19 +80,24 @@ export const FillableTextInput: FC<FillableTextInputProps> = ({
|
|
|
50
80
|
readOnly,
|
|
51
81
|
value,
|
|
52
82
|
}) => {
|
|
83
|
+
const maxLength = getFieldMaxLength(field, value);
|
|
84
|
+
|
|
53
85
|
return (
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
<Fragment>
|
|
87
|
+
<input
|
|
88
|
+
readOnly={readOnly}
|
|
89
|
+
type="text"
|
|
90
|
+
className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
|
|
91
|
+
style={fillableTextStyles}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
id={field.path}
|
|
94
|
+
name={field.path}
|
|
95
|
+
value={value}
|
|
96
|
+
onChange={e => applyFittedChange(field, value, e, onChange)}
|
|
97
|
+
placeholder={getFieldPlaceholderText(field)}
|
|
98
|
+
/>
|
|
99
|
+
<CharLimitIndicator maxLength={maxLength} value={value} />
|
|
100
|
+
</Fragment>
|
|
66
101
|
);
|
|
67
102
|
};
|
|
68
103
|
|
|
@@ -73,18 +108,23 @@ export const FillableTextareaInput: FC<FillableTextInputProps> = ({
|
|
|
73
108
|
readOnly,
|
|
74
109
|
value,
|
|
75
110
|
}) => {
|
|
111
|
+
const maxLength = getFieldMaxLength(field, value);
|
|
112
|
+
|
|
76
113
|
return (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
<Fragment>
|
|
115
|
+
<textarea
|
|
116
|
+
readOnly={readOnly}
|
|
117
|
+
className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
|
|
118
|
+
style={{ ...fillableTextStyles, resize: 'none' }}
|
|
119
|
+
disabled={disabled}
|
|
120
|
+
id={field.path}
|
|
121
|
+
name={field.path}
|
|
122
|
+
value={value}
|
|
123
|
+
onChange={e => applyFittedChange(field, value, e, onChange)}
|
|
124
|
+
placeholder={getFieldPlaceholderText(field)}
|
|
125
|
+
/>
|
|
126
|
+
<CharLimitIndicator maxLength={maxLength} value={value} />
|
|
127
|
+
</Fragment>
|
|
88
128
|
);
|
|
89
129
|
};
|
|
90
130
|
|
|
@@ -193,19 +233,25 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
|
|
|
193
233
|
}
|
|
194
234
|
|
|
195
235
|
if (field.subType === 'number') {
|
|
236
|
+
const numberValue = resolvedValue == null ? '' : String(resolvedValue);
|
|
237
|
+
const maxLength = getFieldMaxLength(field, numberValue);
|
|
238
|
+
|
|
196
239
|
return (
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
<Fragment>
|
|
241
|
+
<input
|
|
242
|
+
readOnly={readOnly}
|
|
243
|
+
type="number"
|
|
244
|
+
className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
|
|
245
|
+
style={fillableTextStyles}
|
|
246
|
+
disabled={!isFillable}
|
|
247
|
+
id={field.path}
|
|
248
|
+
name={field.path}
|
|
249
|
+
value={numberValue}
|
|
250
|
+
onChange={e => applyFittedChange(field, numberValue, e, handleDataChange)}
|
|
251
|
+
placeholder={getFieldPlaceholderText(field)}
|
|
252
|
+
/>
|
|
253
|
+
<CharLimitIndicator maxLength={maxLength} value={numberValue} />
|
|
254
|
+
</Fragment>
|
|
209
255
|
);
|
|
210
256
|
}
|
|
211
257
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface CharLimitIndicatorProps {
|
|
4
|
+
maxLength?: number;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const CharLimitIndicator: FC<CharLimitIndicatorProps> = ({ maxLength, value }) => {
|
|
9
|
+
if (maxLength == null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const filledCount = value.length;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
className={`dte-pdf-field-char-count${filledCount >= maxLength ? ' --at-limit' : ''}`}
|
|
18
|
+
aria-live="polite"
|
|
19
|
+
>
|
|
20
|
+
{filledCount}/{maxLength}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
.dte-pdf-field-error-message {
|
|
15
|
-
|
|
15
|
+
bottom: -4px;
|
|
16
|
+
transform: translateY(100%);
|
|
16
17
|
min-width: 100px;
|
|
17
18
|
position: absolute;
|
|
18
|
-
margin-top: var(--spacing-1, 8px);
|
|
19
19
|
color: var(--danger);
|
|
20
20
|
font-size: var(--typescale-small, 10px);
|
|
21
21
|
}
|
|
@@ -105,6 +105,13 @@
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
textarea.dte-pdf-field-fillable,
|
|
109
|
+
input[type='text'].dte-pdf-field-fillable,
|
|
110
|
+
input[type='number'].dte-pdf-field-fillable {
|
|
111
|
+
font-family: inherit;
|
|
112
|
+
font-weight: inherit;
|
|
113
|
+
}
|
|
114
|
+
|
|
108
115
|
.dte-pdf-field-generic-table {
|
|
109
116
|
padding: var(--spacing-0, 0);
|
|
110
117
|
margin: var(--spacing-0, 0);
|