@visulima/string 1.0.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.
Files changed (170) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE.md +77 -0
  3. package/README.md +880 -0
  4. package/dist/case/camel-case.cjs +59 -0
  5. package/dist/case/camel-case.d.cts +7 -0
  6. package/dist/case/camel-case.d.mts +7 -0
  7. package/dist/case/camel-case.d.ts +7 -0
  8. package/dist/case/camel-case.mjs +57 -0
  9. package/dist/case/capital-case.cjs +51 -0
  10. package/dist/case/capital-case.d.cts +7 -0
  11. package/dist/case/capital-case.d.mts +7 -0
  12. package/dist/case/capital-case.d.ts +7 -0
  13. package/dist/case/capital-case.mjs +49 -0
  14. package/dist/case/constant-case.cjs +11 -0
  15. package/dist/case/constant-case.d.cts +7 -0
  16. package/dist/case/constant-case.d.mts +7 -0
  17. package/dist/case/constant-case.d.ts +7 -0
  18. package/dist/case/constant-case.mjs +9 -0
  19. package/dist/case/dot-case.cjs +11 -0
  20. package/dist/case/dot-case.d.cts +7 -0
  21. package/dist/case/dot-case.d.mts +7 -0
  22. package/dist/case/dot-case.d.ts +7 -0
  23. package/dist/case/dot-case.mjs +9 -0
  24. package/dist/case/flat-case.cjs +11 -0
  25. package/dist/case/flat-case.d.cts +7 -0
  26. package/dist/case/flat-case.d.mts +7 -0
  27. package/dist/case/flat-case.d.ts +7 -0
  28. package/dist/case/flat-case.mjs +9 -0
  29. package/dist/case/flip-case.cjs +61 -0
  30. package/dist/case/flip-case.d.cts +8 -0
  31. package/dist/case/flip-case.d.mts +8 -0
  32. package/dist/case/flip-case.d.ts +8 -0
  33. package/dist/case/flip-case.mjs +57 -0
  34. package/dist/case/identify-case.cjs +30 -0
  35. package/dist/case/identify-case.d.cts +7 -0
  36. package/dist/case/identify-case.d.mts +7 -0
  37. package/dist/case/identify-case.d.ts +7 -0
  38. package/dist/case/identify-case.mjs +28 -0
  39. package/dist/case/index.cjs +47 -0
  40. package/dist/case/index.d.cts +26 -0
  41. package/dist/case/index.d.mts +26 -0
  42. package/dist/case/index.d.ts +26 -0
  43. package/dist/case/index.mjs +20 -0
  44. package/dist/case/kebab-case.cjs +61 -0
  45. package/dist/case/kebab-case.d.cts +11 -0
  46. package/dist/case/kebab-case.d.mts +11 -0
  47. package/dist/case/kebab-case.d.ts +11 -0
  48. package/dist/case/kebab-case.mjs +57 -0
  49. package/dist/case/lower-first.cjs +13 -0
  50. package/dist/case/lower-first.d.cts +7 -0
  51. package/dist/case/lower-first.d.mts +7 -0
  52. package/dist/case/lower-first.d.ts +7 -0
  53. package/dist/case/lower-first.mjs +11 -0
  54. package/dist/case/no-case.cjs +11 -0
  55. package/dist/case/no-case.d.cts +7 -0
  56. package/dist/case/no-case.d.mts +7 -0
  57. package/dist/case/no-case.d.ts +7 -0
  58. package/dist/case/no-case.mjs +9 -0
  59. package/dist/case/pascal-case.cjs +52 -0
  60. package/dist/case/pascal-case.d.cts +7 -0
  61. package/dist/case/pascal-case.d.mts +7 -0
  62. package/dist/case/pascal-case.d.ts +7 -0
  63. package/dist/case/pascal-case.mjs +50 -0
  64. package/dist/case/pascal-snake-case.cjs +32 -0
  65. package/dist/case/pascal-snake-case.d.cts +7 -0
  66. package/dist/case/pascal-snake-case.d.mts +7 -0
  67. package/dist/case/pascal-snake-case.d.ts +7 -0
  68. package/dist/case/pascal-snake-case.mjs +30 -0
  69. package/dist/case/path-case.cjs +11 -0
  70. package/dist/case/path-case.d.cts +7 -0
  71. package/dist/case/path-case.d.mts +7 -0
  72. package/dist/case/path-case.d.ts +7 -0
  73. package/dist/case/path-case.mjs +9 -0
  74. package/dist/case/sentence-case.cjs +58 -0
  75. package/dist/case/sentence-case.d.cts +7 -0
  76. package/dist/case/sentence-case.d.mts +7 -0
  77. package/dist/case/sentence-case.d.ts +7 -0
  78. package/dist/case/sentence-case.mjs +56 -0
  79. package/dist/case/snake-case.cjs +11 -0
  80. package/dist/case/snake-case.d.cts +7 -0
  81. package/dist/case/snake-case.d.mts +7 -0
  82. package/dist/case/snake-case.d.ts +7 -0
  83. package/dist/case/snake-case.mjs +9 -0
  84. package/dist/case/split-by-case.cjs +638 -0
  85. package/dist/case/split-by-case.d.cts +16 -0
  86. package/dist/case/split-by-case.d.mts +16 -0
  87. package/dist/case/split-by-case.d.ts +16 -0
  88. package/dist/case/split-by-case.mjs +634 -0
  89. package/dist/case/title-case.cjs +47 -0
  90. package/dist/case/title-case.d.cts +7 -0
  91. package/dist/case/title-case.d.mts +7 -0
  92. package/dist/case/title-case.d.ts +7 -0
  93. package/dist/case/title-case.mjs +45 -0
  94. package/dist/case/train-case.cjs +44 -0
  95. package/dist/case/train-case.d.cts +7 -0
  96. package/dist/case/train-case.d.mts +7 -0
  97. package/dist/case/train-case.d.ts +7 -0
  98. package/dist/case/train-case.mjs +42 -0
  99. package/dist/case/upper-first.cjs +13 -0
  100. package/dist/case/upper-first.d.cts +7 -0
  101. package/dist/case/upper-first.d.mts +7 -0
  102. package/dist/case/upper-first.d.ts +7 -0
  103. package/dist/case/upper-first.mjs +11 -0
  104. package/dist/get-string-truncated-width.cjs +386 -0
  105. package/dist/get-string-truncated-width.d.cts +25 -0
  106. package/dist/get-string-truncated-width.d.mts +25 -0
  107. package/dist/get-string-truncated-width.d.ts +25 -0
  108. package/dist/get-string-truncated-width.mjs +382 -0
  109. package/dist/get-string-width.cjs +11 -0
  110. package/dist/get-string-width.d.cts +6 -0
  111. package/dist/get-string-width.d.mts +6 -0
  112. package/dist/get-string-width.d.ts +6 -0
  113. package/dist/get-string-width.mjs +7 -0
  114. package/dist/index.cjs +60 -0
  115. package/dist/index.d.cts +43 -0
  116. package/dist/index.d.mts +43 -0
  117. package/dist/index.d.ts +43 -0
  118. package/dist/index.mjs +26 -0
  119. package/dist/native-string-types.d.cts +128 -0
  120. package/dist/native-string-types.d.mts +128 -0
  121. package/dist/native-string-types.d.ts +128 -0
  122. package/dist/outdent.cjs +122 -0
  123. package/dist/outdent.d.cts +15 -0
  124. package/dist/outdent.d.mts +15 -0
  125. package/dist/outdent.d.ts +15 -0
  126. package/dist/outdent.mjs +118 -0
  127. package/dist/packem_shared/LRUCache-Crb-m7cw.cjs +53 -0
  128. package/dist/packem_shared/LRUCache-udNErhWw.mjs +51 -0
  129. package/dist/packem_shared/WrapMode-BgOOthJV.mjs +545 -0
  130. package/dist/packem_shared/WrapMode-ZqTeXK8X.cjs +550 -0
  131. package/dist/packem_shared/constants-BnVGLrDr.mjs +99 -0
  132. package/dist/packem_shared/constants-HevSYUjU.cjs +149 -0
  133. package/dist/packem_shared/generate-cache-key-D8f1auDB.cjs +10 -0
  134. package/dist/packem_shared/generate-cache-key-YSju5pj2.mjs +8 -0
  135. package/dist/packem_shared/joinSegments-BH1CT4dK.cjs +48 -0
  136. package/dist/packem_shared/joinSegments-BHY8PPnk.mjs +46 -0
  137. package/dist/packem_shared/normalize-german-eszett-DJK0S2qj.cjs +11 -0
  138. package/dist/packem_shared/normalize-german-eszett-fpJ4lZL6.mjs +9 -0
  139. package/dist/packem_shared/types-BAd3WDHo.d.mts +59 -0
  140. package/dist/packem_shared/types-BNbQJg02.d.cts +41 -0
  141. package/dist/packem_shared/types-BNbQJg02.d.mts +41 -0
  142. package/dist/packem_shared/types-BNbQJg02.d.ts +41 -0
  143. package/dist/packem_shared/types-BttWSSIk.d.ts +59 -0
  144. package/dist/packem_shared/types-D2-6p7t1.d.cts +59 -0
  145. package/dist/slice.cjs +413 -0
  146. package/dist/slice.d.cts +10 -0
  147. package/dist/slice.d.mts +10 -0
  148. package/dist/slice.d.ts +10 -0
  149. package/dist/slice.mjs +409 -0
  150. package/dist/test/utils.cjs +77 -0
  151. package/dist/test/utils.d.cts +30 -0
  152. package/dist/test/utils.d.mts +30 -0
  153. package/dist/test/utils.d.ts +30 -0
  154. package/dist/test/utils.mjs +71 -0
  155. package/dist/test/vitest.cjs +11 -0
  156. package/dist/test/vitest.d.cts +8 -0
  157. package/dist/test/vitest.d.mts +8 -0
  158. package/dist/test/vitest.d.ts +8 -0
  159. package/dist/test/vitest.mjs +7 -0
  160. package/dist/truncate.cjs +90 -0
  161. package/dist/truncate.d.cts +12 -0
  162. package/dist/truncate.d.mts +12 -0
  163. package/dist/truncate.d.ts +12 -0
  164. package/dist/truncate.mjs +86 -0
  165. package/dist/utils.cjs +9 -0
  166. package/dist/utils.d.cts +14 -0
  167. package/dist/utils.d.mts +14 -0
  168. package/dist/utils.d.ts +14 -0
  169. package/dist/utils.mjs +1 -0
  170. package/package.json +468 -0
package/README.md ADDED
@@ -0,0 +1,880 @@
1
+ <div align="center">
2
+ <h3>visulima string</h3>
3
+ <p>
4
+ A robust string manipulation library providing utilities for common string operations with support for multiple languages.
5
+ </p>
6
+ </div>
7
+
8
+ <br />
9
+
10
+ <div align="center">
11
+
12
+ [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ <div align="center">
19
+ <p>
20
+ <sup>
21
+ Daniel Bannert's open source work is supported by the community on <a href="https://github.com/sponsors/prisis">GitHub Sponsors</a>
22
+ </sup>
23
+ </p>
24
+ </div>
25
+
26
+ ## Features
27
+
28
+ ### Case Conversion
29
+
30
+ - **Multiple Case Styles**:
31
+ - `camelCase`: Convert to camelCase style
32
+ - `PascalCase`: Convert to PascalCase style
33
+ - `snake_case`: Convert to snake_case style
34
+ - `kebab-case`: Convert to kebab-case style
35
+ - `CONSTANT_CASE`: Convert to CONSTANT_CASE style
36
+ - `dot.case`: Convert to dot.case style
37
+ - `path/case`: Convert to path/case style
38
+ - `Sentence case`: Convert to Sentence case style
39
+ - `Title Case`: Convert to Title Case with smart minor word handling
40
+
41
+ ### String Manipulation
42
+
43
+ - **Smart String Splitting**:
44
+ - Split by case transitions (camelCase → ["camel", "Case"])
45
+ - Split by script boundaries (日本語Text → ["日本語", "Text"])
46
+ - Split by separators (foo-bar → ["foo", "bar"])
47
+ - Preserve known acronyms (XMLHttpRequest → ["XML", "Http", "Request"])
48
+ - **Text Indentation**:
49
+ - `outdent`: Remove leading indentation while preserving relative indentation
50
+ - Handles template literals and string inputs
51
+ - Normalizes newlines across platforms
52
+ - Configurable trimming behavior
53
+ - **String Width Calculation**:
54
+ - `getStringWidth`: Calculate visual width of strings with Unicode support
55
+ - `getStringTruncatedWidth`: Width calculation with smart truncation
56
+ - Handles CJK characters, emojis, ANSI codes, and more
57
+ - Configurable character width settings
58
+ - Support for zero-width and combining characters
59
+ - Customizable truncation with ellipsis
60
+
61
+ ### Multi-Script Support
62
+
63
+ - **CJK Scripts**:
64
+ - Japanese (Hiragana, Katakana, Kanji)
65
+ - Korean (Hangul)
66
+ - Chinese (Han characters)
67
+ - **European Scripts**:
68
+ - Cyrillic (Russian, Ukrainian, etc.)
69
+ - Greek
70
+ - Latin (with extended characters)
71
+ - **RTL Scripts**:
72
+ - Arabic
73
+ - Hebrew
74
+ - **Indic Scripts**:
75
+ - Devanagari
76
+ - Bengali
77
+ - Tamil
78
+ - And more...
79
+ - **Southeast Asian**:
80
+ - Thai
81
+ - Lao
82
+ - Khmer
83
+
84
+ ### Enhanced Type Safety
85
+
86
+ - **Native String Type Extensions**:
87
+ - Type-safe string operations
88
+ - Compile-time type checking
89
+ - Improved IDE support
90
+ - **Generic Type Parameters**:
91
+ - Flexible type constraints
92
+ - Template literal type support
93
+ - Conditional type inference
94
+
95
+ ### Performance Features
96
+
97
+ - **Optimized Processing**:
98
+ - Precompiled regex patterns
99
+ - Efficient string manipulation
100
+ - **Caching Mechanisms**:
101
+ - Smart caching for repeated operations
102
+ - WeakMap-based caching for template literals
103
+ - Configurable cache options
104
+ - Fast paths for common cases
105
+ - **Memory Efficiency**:
106
+ - Minimal string allocations
107
+ - Efficient string splitting
108
+ - Optimized concatenation
109
+
110
+ ### Developer Experience
111
+
112
+ - **Comprehensive API**:
113
+ - Consistent method signatures
114
+ - Chainable operations
115
+ - Flexible configuration options
116
+ - **Robust Error Handling**:
117
+ - Graceful handling of edge cases
118
+ - Clear error messages
119
+ - Type-safe error prevention
120
+ - **Full TypeScript Support**:
121
+ - Complete type definitions
122
+ - IntelliSense support
123
+ - Type inference
124
+
125
+ ---
126
+
127
+ ## Install
128
+
129
+ ```sh
130
+ npm install @visulima/string
131
+ ```
132
+
133
+ ```sh
134
+ yarn add @visulima/string
135
+ ```
136
+
137
+ ```sh
138
+ pnpm add @visulima/string
139
+ ```
140
+
141
+ ## Usage
142
+
143
+ ### Outdent (Remove Indentation)
144
+
145
+ The `outdent` function removes leading indentation from multi-line strings while preserving the relative indentation structure.
146
+
147
+ ```typescript
148
+ import { outdent } from '@visulima/string';
149
+
150
+ // Basic usage with template literals
151
+ const text = outdent`
152
+ This text will have its indentation removed
153
+ while preserving relative indentation.
154
+ This line will still be indented relative to the others.
155
+ `;
156
+
157
+ // Output:
158
+ // This text will have its indentation removed
159
+ // while preserving relative indentation.
160
+ // This line will still be indented relative to the others.
161
+
162
+ // With string input
163
+ const result = outdent.string('
164
+ Hello
165
+ World
166
+ ');
167
+ // Output: "Hello\nWorld"
168
+
169
+ // With custom options
170
+ const customOutdent = outdent({
171
+ trimLeadingNewline: false, // Keep the leading newline
172
+ trimTrailingNewline: false, // Keep the trailing newline
173
+ newline: '\r\n', // Normalize all newlines to CRLF
174
+ cache: true // Enable caching (default)
175
+ });
176
+
177
+ // Using with interpolation
178
+ const name = 'World';
179
+ const greeting = outdent`
180
+ Hello ${name}!
181
+ Welcome to outdent.
182
+ `;
183
+ ```
184
+
185
+ #### Performance Optimization with Caching
186
+
187
+ The `outdent` function supports caching to improve performance when the same template is used multiple times:
188
+
189
+ ```typescript
190
+ // Default behavior - caching enabled
191
+ const dedent = outdent();
192
+
193
+ // Disable caching if memory usage is a concern
194
+ const noCacheDedent = outdent({ cache: false });
195
+
196
+ // Use a custom cache store (advanced usage)
197
+ const customCache = new WeakMap();
198
+ const customCacheDedent = outdent({ cacheStore: customCache });
199
+ ```
200
+
201
+ ### Word Wrapping
202
+
203
+ The `wordWrap` function provides flexible text wrapping with support for ANSI color codes and Unicode.
204
+
205
+ ```typescript
206
+ import { wordWrap, WrapMode } from "@visulima/string";
207
+
208
+ // Basic usage with default options (80 character width, preserve words)
209
+ const wrapped = wordWrap("This is a long text that will be wrapped to fit within the specified width limit.");
210
+
211
+ // With custom width (40 characters)
212
+ const narrowWrapped = wordWrap("This text will be wrapped to fit within a 40-character width.", { width: 40 });
213
+
214
+ // Different wrapping modes
215
+ const preserveWords = wordWrap("Long words will stay intact but may exceed the width limit.", {
216
+ width: 20,
217
+ wrapMode: WrapMode.PRESERVE_WORDS, // Default - keeps words intact
218
+ });
219
+
220
+ const breakAtCharacters = wordWrap("Words will be broken at character boundaries to fit width.", {
221
+ width: 20,
222
+ wrapMode: WrapMode.BREAK_AT_CHARACTERS, // Breaks words to fit width exactly
223
+ });
224
+
225
+ const strictWidth = wordWrap("Text will be broken exactly at the width limit.", {
226
+ width: 20,
227
+ wrapMode: WrapMode.STRICT_WIDTH, // Forces strict adherence to width
228
+ });
229
+
230
+ // Handling ANSI color codes
231
+ const coloredText = "\u001B[31mThis red text\u001B[0m will be wrapped while preserving the color codes.";
232
+ const wrappedColored = wordWrap(coloredText, { width: 20 });
233
+ // Color codes are preserved across line breaks
234
+
235
+ // Additional options
236
+ const customWrapped = wordWrap("Text with\u200Bzero-width characters and\u200Btrailing spaces.", {
237
+ width: 30,
238
+ trim: false, // Don't trim whitespace from lines (default: true)
239
+ removeZeroWidthCharacters: false, // Don't remove zero-width characters (default: true)
240
+ });
241
+ ```
242
+
243
+ ### String Splitting
244
+
245
+ The `splitByCase` function is a powerful utility that splits strings based on various patterns:
246
+
247
+ ```typescript
248
+ import { splitByCase } from "@visulima/string";
249
+
250
+ // Basic Case Transitions
251
+ splitByCase("camelCase"); // ['camel', 'Case']
252
+ splitByCase("PascalCase"); // ['Pascal', 'Case']
253
+ splitByCase("snake_case"); // ['snake', 'case']
254
+ splitByCase("kebab-case"); // ['kebab', 'case']
255
+
256
+ // Numbers and Acronyms
257
+ splitByCase("XMLHttpRequest"); // ['XML', 'Http', 'Request']
258
+ splitByCase("iOS8"); // ['i', 'OS', '8']
259
+ splitByCase("IPv6Address"); // ['IP', 'v6', 'Address']
260
+
261
+ // Multi-Script Support
262
+
263
+ // Japanese
264
+ splitByCase("ひらがなカタカナABC", { locale: "ja" });
265
+ // ['ひらがな', 'カタカナ', 'ABC']
266
+
267
+ // Korean
268
+ splitByCase("한글Text", { locale: "ko" });
269
+ // ['한글', 'Text']
270
+
271
+ // Chinese
272
+ splitByCase("中文Text", { locale: "zh" });
273
+ // ['中文', 'Text']
274
+
275
+ // Cyrillic
276
+ splitByCase("русскийText", { locale: "ru" });
277
+ // ['русский', 'Text']
278
+
279
+ // Greek
280
+ splitByCase("ελληνικάText", { locale: "el" });
281
+ // ['ελληνικά', 'Text']
282
+
283
+ // Advanced Options
284
+ splitByCase("MyXMLParser", {
285
+ knownAcronyms: ["XML"], // Preserve known acronyms
286
+ normalize: true, // Normalize case
287
+ locale: "en", // Specify locale
288
+ });
289
+ // ['My', 'XML', 'Parser']
290
+
291
+ // ANSI and Emoji Handling
292
+ splitByCase("🎉HappyBirthday🎂", {
293
+ handleEmoji: true, // Handle emoji boundaries
294
+ });
295
+ // ['🎉', 'Happy', 'Birthday', '🎂']
296
+ ```
297
+
298
+ ### Case Conversion Functions
299
+
300
+ #### camelCase
301
+
302
+ Converts a string to camelCase.
303
+
304
+ ```typescript
305
+ camelCase("foo bar"); // 'fooBar'
306
+ camelCase("foo-bar"); // 'fooBar'
307
+ camelCase("foo_bar"); // 'fooBar'
308
+ camelCase("XMLHttpRequest"); // 'xmlHttpRequest'
309
+ camelCase("AJAXRequest"); // 'ajaxRequest'
310
+ camelCase("QueryXML123String"); // 'queryXml123String'
311
+ ```
312
+
313
+ #### pascalCase
314
+
315
+ Converts a string to PascalCase.
316
+
317
+ ```typescript
318
+ pascalCase("foo bar"); // 'FooBar'
319
+ pascalCase("foo-bar"); // 'FooBar'
320
+ pascalCase("foo_bar"); // 'FooBar'
321
+ pascalCase("XMLHttpRequest"); // 'XmlHttpRequest'
322
+ pascalCase("AJAXRequest"); // 'AjaxRequest'
323
+ pascalCase("QueryXML123String"); // 'QueryXml123String'
324
+ ```
325
+
326
+ #### snakeCase
327
+
328
+ Converts a string to snake_case.
329
+
330
+ ```typescript
331
+ snakeCase("fooBar"); // 'foo_bar'
332
+ snakeCase("foo bar"); // 'foo_bar'
333
+ snakeCase("foo-bar"); // 'foo_bar'
334
+ snakeCase("XMLHttpRequest"); // 'xml_http_request'
335
+ snakeCase("AJAXRequest"); // 'ajax_request'
336
+ snakeCase("QueryXML123String"); // 'query_xml_123_string'
337
+ ```
338
+
339
+ #### kebabCase
340
+
341
+ Converts a string to kebab-case.
342
+
343
+ ```typescript
344
+ kebabCase("fooBar"); // 'foo-bar'
345
+ kebabCase("foo bar"); // 'foo-bar'
346
+ kebabCase("foo_bar"); // 'foo-bar'
347
+ kebabCase("XMLHttpRequest"); // 'xml-http-request'
348
+ kebabCase("AJAXRequest"); // 'ajax-request'
349
+ kebabCase("QueryXML123String"); // 'query-xml-123-string'
350
+ ```
351
+
352
+ #### titleCase
353
+
354
+ Converts a string to Title Case, with smart handling of minor words.
355
+
356
+ ```typescript
357
+ titleCase("this-IS-aTitle"); // 'This is a Title'
358
+ titleCase("XMLHttpRequest"); // 'XML Http Request'
359
+ titleCase("AJAXRequest"); // 'AJAX Request'
360
+ titleCase("QueryXML123String"); // 'Query XML 123 String'
361
+ ```
362
+
363
+ #### pathCase
364
+
365
+ Converts a string to path/case.
366
+
367
+ ```typescript
368
+ pathCase("foo bar"); // 'foo/bar'
369
+ pathCase("foo-bar"); // 'foo/bar'
370
+ pathCase("foo_bar"); // 'foo/bar'
371
+ pathCase("XMLHttpRequest"); // 'xml/http/request'
372
+ pathCase("AJAXRequest"); // 'ajax/request'
373
+ pathCase("QueryXML123String"); // 'query/xml/123/string'
374
+ ```
375
+
376
+ #### dotCase
377
+
378
+ Converts a string to dot.case.
379
+
380
+ ```typescript
381
+ dotCase("foo bar"); // 'foo.bar'
382
+ dotCase("foo-bar"); // 'foo.bar'
383
+ dotCase("foo_bar"); // 'foo.bar'
384
+ dotCase("XMLHttpRequest"); // 'xml.http.request'
385
+ dotCase("AJAXRequest"); // 'ajax.request'
386
+ dotCase("QueryXML123String"); // 'query.xml.123.string'
387
+ ```
388
+
389
+ #### constantCase
390
+
391
+ Converts a string to CONSTANT_CASE.
392
+
393
+ ```typescript
394
+ constantCase("foo bar"); // 'FOO_BAR'
395
+ constantCase("foo-bar"); // 'FOO_BAR'
396
+ constantCase("foo_bar"); // 'FOO_BAR'
397
+ constantCase("XMLHttpRequest"); // 'XML_HTTP_REQUEST'
398
+ constantCase("AJAXRequest"); // 'AJAX_REQUEST'
399
+ constantCase("QueryXML123String"); // 'QUERY_XML_123_STRING'
400
+ ```
401
+
402
+ #### sentenceCase
403
+
404
+ Converts a string to Sentence case.
405
+
406
+ ```typescript
407
+ sentenceCase("foo bar"); // 'Foo bar'
408
+ sentenceCase("foo-bar"); // 'Foo bar'
409
+ sentenceCase("foo_bar"); // 'Foo bar'
410
+ sentenceCase("XMLHttpRequest"); // 'Xml http request'
411
+ sentenceCase("AJAXRequest"); // 'Ajax request'
412
+ sentenceCase("QueryXML123String"); // 'Query xml 123 string'
413
+ ```
414
+
415
+ ### String Width Calculation
416
+
417
+ The package provides two functions for calculating string widths: `getStringWidth` for basic width calculation and `getStringTruncatedWidth` for width calculation with truncation support.
418
+
419
+ #### Basic Width Calculation
420
+
421
+ The `getStringWidth` function calculates the visual width of strings, taking into account various Unicode characters, emojis, ANSI escape codes, and more:
422
+
423
+ ```typescript
424
+ import { getStringWidth } from "@visulima/string";
425
+
426
+ // Basic usage
427
+ getStringWidth("hello"); // => 5
428
+ getStringWidth("👋 hello"); // => 7 (emoji is width 2)
429
+ getStringWidth("あいう"); // => 6 (each character is width 2)
430
+
431
+ // With custom options
432
+ getStringWidth("hello", { regularWidth: 2 }); // => 10
433
+ getStringWidth("あいう", { ambiguousIsNarrow: true }); // => 3
434
+
435
+ // ANSI escape codes
436
+ getStringWidth("\u001B[31mRed\u001B[39m"); // => 3
437
+ getStringWidth("\u001B[31mRed\u001B[39m", { countAnsiEscapeCodes: true }); // => 11
438
+
439
+ // Advanced Unicode support
440
+ getStringWidth("한글"); // => 4 (Korean characters)
441
+ getStringWidth("你好"); // => 4 (Chinese characters)
442
+ getStringWidth("👨‍👩‍👧‍👦"); // => 2 (family emoji with ZWJ sequences)
443
+ ```
444
+
445
+ #### Configuration Options
446
+
447
+ ```typescript
448
+ interface StringWidthOptions {
449
+ ambiguousIsNarrow?: boolean; // Treat ambiguous-width characters as narrow
450
+ ambiguousWidth?: number; // Width of ambiguous-width characters (default: 1)
451
+ ansiWidth?: number; // Width of ANSI escape sequences (default: 0)
452
+ controlWidth?: number; // Width of control characters (default: 0)
453
+ countAnsiEscapeCodes?: boolean; // Include ANSI escape codes in width (default: false)
454
+ emojiWidth?: number; // Width of emoji characters (default: 2)
455
+ fullWidth?: number; // Width of full-width characters (default: 2)
456
+ regularWidth?: number; // Width of regular characters (default: 1)
457
+ tabWidth?: number; // Width of tab characters (default: 8)
458
+ wideWidth?: number; // Width of wide characters (default: 2)
459
+ }
460
+ ```
461
+
462
+ #### Width Calculation with Truncation
463
+
464
+ The `getStringTruncatedWidth` function extends the basic width calculation with truncation support:
465
+
466
+ ```typescript
467
+ import { getStringTruncatedWidth } from "@visulima/string";
468
+
469
+ // Basic truncation
470
+ getStringTruncatedWidth("hello world", {
471
+ limit: 8,
472
+ ellipsis: "...",
473
+ }); // => { width: 8, truncated: true, ellipsed: true, index: 5 }
474
+
475
+ // Custom character widths with truncation
476
+ getStringTruncatedWidth("あいうえお", {
477
+ limit: 6,
478
+ ellipsis: "...",
479
+ fullWidth: 2,
480
+ }); // => { width: 6, truncated: true, ellipsed: true, index: 2 }
481
+
482
+ // ANSI codes with truncation
483
+ getStringTruncatedWidth("\u001B[31mRed Text\u001B[39m", {
484
+ limit: 5,
485
+ ellipsis: "...",
486
+ countAnsiEscapeCodes: true,
487
+ }); // => { width: 5, truncated: true, ellipsed: true, index: 4 }
488
+
489
+ // Complex Unicode with truncation
490
+ getStringTruncatedWidth("👨‍👩‍👧‍👦 Family", {
491
+ limit: 7,
492
+ ellipsis: "...",
493
+ }); // => { width: 7, truncated: true, ellipsed: true, index: 11 }
494
+ ```
495
+
496
+ ### String Truncation
497
+
498
+ The `truncate` function provides a convenient way to truncate strings with support for different positions, Unicode characters, ANSI escape codes, and more.
499
+
500
+ ```typescript
501
+ import { truncate } from "@visulima/string";
502
+
503
+ // Basic truncation (end position)
504
+ truncate("unicorn", 4); // => 'un…'
505
+ truncate("unicorn", 4, { position: "end" }); // => 'un…'
506
+
507
+ // Different positions
508
+ truncate("unicorn", 5, { position: "start" }); // => '…orn'
509
+ truncate("unicorn", 5, { position: "middle" }); // => 'un…n'
510
+
511
+ // With custom ellipsis
512
+ truncate("unicorns", 5, { ellipsis: "." }); // => 'unic.'
513
+ truncate("unicorns", 5, { ellipsis: " ." }); // => 'uni .'
514
+
515
+ // Smart truncation on spaces
516
+ truncate("dragons are awesome", 15, { position: "end", preferTruncationOnSpace: true }); // => 'dragons are…'
517
+ truncate("unicorns rainbow dragons", 20, { position: "middle", preferTruncationOnSpace: true }); // => 'unicorns…dragons'
518
+
519
+ // With ANSI escape codes
520
+ truncate("\u001B[31municorn\u001B[39m", 4); // => '\u001B[31mun\u001B[39m…'
521
+
522
+ // With Unicode characters
523
+ truncate("안녕하세요", 3, { width: { fullWidth: 2 } }); // => '안…'
524
+ ```
525
+
526
+ #### Truncation Options
527
+
528
+ ```typescript
529
+ interface TruncateOptions {
530
+ // String to append when truncation occurs
531
+ ellipsis?: string; // default: ''
532
+
533
+ // Width of the ellipsis string
534
+ // If not provided, it will be calculated using getStringTruncatedWidth
535
+ ellipsisWidth?: number;
536
+
537
+ // The position to truncate the string
538
+ position?: "end" | "middle" | "start"; // default: 'end'
539
+
540
+ // Truncate the string from a whitespace if it is within 3 characters
541
+ // from the actual breaking point
542
+ preferTruncationOnSpace?: boolean; // default: false
543
+
544
+ // Width calculation options
545
+ width?: Omit<StringTruncatedWidthOptions, "ellipsis" | "ellipsisWidth" | "limit">;
546
+ }
547
+
548
+ interface StringTruncatedWidthOptions extends StringWidthOptions {
549
+ // Truncation-specific options
550
+ ellipsis?: string; // String to append when truncation occurs (default: '')
551
+ ellipsisWidth?: number; // Width of ellipsis, auto-calculated if not provided
552
+ limit?: number; // Maximum width limit for the string (default: Infinity)
553
+ }
554
+
555
+ // Return value structure
556
+ interface StringTruncatedWidthResult {
557
+ width: number; // The calculated visual width of the string
558
+ truncated: boolean; // Whether the string was truncated
559
+ ellipsed: boolean; // Whether an ellipsis was added
560
+ index: number; // The index at which truncation occurred (if any)
561
+ }
562
+ ```
563
+
564
+ ### Common Options
565
+
566
+ All case conversion functions accept these common options:
567
+
568
+ ```typescript
569
+ interface CaseOptions {
570
+ // Enable caching for better performance
571
+ cache?: boolean;
572
+
573
+ // Maximum size of the cache (default: 1000)
574
+ cacheMaxSize?: number;
575
+
576
+ // Custom cache store
577
+ cacheStore?: Map<string, string>;
578
+
579
+ // Known acronyms to preserve
580
+ knownAcronyms?: ReadonlyArray<string>;
581
+
582
+ // Locale for script-aware case conversion
583
+ locale?: string;
584
+ }
585
+ ```
586
+
587
+ ### String Splitting
588
+
589
+ The `splitByCase` function accepts these configuration options:
590
+
591
+ ```typescript
592
+ interface SplitOptions {
593
+ // Locale for script-aware splitting (e.g., 'ja', 'ko', 'zh')
594
+ locale?: string;
595
+
596
+ // Known acronyms to preserve (e.g., ['XML', 'HTTP'])
597
+ knownAcronyms?: ReadonlyArray<string>;
598
+
599
+ // Handle ANSI escape sequences
600
+ handleAnsi?: boolean;
601
+
602
+ // Handle emoji sequences
603
+ handleEmoji?: boolean;
604
+
605
+ // Normalize case (convert all-upper tokens to title case)
606
+ normalize?: boolean;
607
+
608
+ // Custom separators (string[] or RegExp)
609
+ separators?: ReadonlyArray<string> | RegExp;
610
+
611
+ // Strip ANSI sequences
612
+ stripAnsi?: boolean;
613
+
614
+ // Strip emoji sequences
615
+ stripEmoji?: boolean;
616
+ }
617
+ ```
618
+
619
+ ### Supported Scripts
620
+
621
+ The library provides comprehensive support for various scripts and writing systems:
622
+
623
+ - **Latin**: Standard ASCII and extended Latin characters
624
+ - **CJK**:
625
+ - Japanese (Hiragana, Katakana, Kanji)
626
+ - Korean (Hangul)
627
+ - Chinese (Han characters)
628
+ - **Cyrillic**: Russian, Ukrainian, Bulgarian, etc.
629
+ - **Greek**: Modern Greek script
630
+ - **RTL Scripts**: Arabic, Hebrew
631
+ - **Indic Scripts**: Devanagari, Bengali, Tamil, etc.
632
+ - **Southeast Asian**: Thai, Lao, Khmer
633
+
634
+ ### Performance Optimization
635
+
636
+ The library includes several optimizations:
637
+
638
+ - Precompiled regex patterns for script detection
639
+ - Fast paths for single-script strings
640
+ - Efficient handling of case transitions
641
+ - Smart caching of character type checks
642
+
643
+ ### Error Handling
644
+
645
+ The library handles various edge cases gracefully:
646
+
647
+ ```typescript
648
+ // Empty strings
649
+ splitByCase(""); // []
650
+
651
+ // Invalid input
652
+ splitByCase(null); // []
653
+ splitByCase(undefined); // []
654
+
655
+ // Single characters
656
+ splitByCase("A"); // ['A']
657
+
658
+ // All uppercase
659
+ splitByCase("URL", { knownAcronyms: ["URL"] }); // ['URL']
660
+ ```
661
+
662
+ ### Native String Types
663
+
664
+ The library provides enhanced TypeScript type definitions for native string methods. These types provide better type inference and compile-time checks.
665
+
666
+ #### Configuration
667
+
668
+ Configure your `tsconfig.json` file to include the types:
669
+
670
+ ```json
671
+ {
672
+ "compilerOptions": {
673
+ "types": ["@visulima/string/native-string-types"]
674
+ }
675
+ }
676
+ ```
677
+
678
+ Alternatively, you can add a triple-slash reference in your TypeScript files:
679
+
680
+ ```typescript
681
+ /// <reference types="@visulima/string/native-string-types" />
682
+ ```
683
+
684
+ #### Usage Examples
685
+
686
+ ```typescript
687
+ // Type-safe string operations
688
+ const str = "Hello, World!";
689
+
690
+ // charAt with type inference
691
+ str.charAt<typeof str, 0>(); // type: 'H'
692
+ str.charAt<typeof str, 1>(); // type: 'e'
693
+
694
+ // concat with type inference
695
+ str.concat<typeof str, "Hi">(); // type: 'Hello, World!Hi'
696
+
697
+ // endsWith with literal type checking
698
+ str.endsWith<typeof str, "World!">(); // type: true
699
+ str.endsWith<typeof str, "Hello">(); // type: false
700
+
701
+ // includes with position
702
+ str.includes<typeof str, "World", 0>(); // type: true
703
+ str.includes<typeof str, "World", 7>(); // type: false
704
+
705
+ // length with type inference
706
+ type Length = (typeof str)["length"]; // type: 13
707
+
708
+ // padStart/padEnd with type inference
709
+ str.padStart<typeof str, 15, "_">(); // type: '_Hello, World!'
710
+ str.padEnd<typeof str, 15, "_">(); // type: 'Hello, World!__'
711
+
712
+ // replace with type inference
713
+ str.replace<typeof str, "World", "TypeScript">(); // type: 'Hello, TypeScript!'
714
+
715
+ // replaceAll with type inference
716
+ str.replaceAll<typeof str, "l", "L">(); // type: 'HeLLo, WorLd!'
717
+
718
+ // slice with type inference
719
+ str.slice<typeof str, 0, 5>(); // type: 'Hello'
720
+
721
+ // split with type inference
722
+ str.split<typeof str, ", ">(); // type: ['Hello', 'World!']
723
+
724
+ // startsWith with type inference
725
+ str.startsWith<typeof str, "Hello">(); // type: true
726
+
727
+ // toLowerCase/toUpperCase with type inference
728
+ str.toLowerCase<typeof str>(); // type: 'hello, world!'
729
+ str.toUpperCase<typeof str>(); // type: 'HELLO, WORLD!'
730
+
731
+ // trim/trimStart/trimEnd with type inference
732
+ const paddedStr = " hello ";
733
+ paddedStr.trim<typeof paddedStr>(); // type: 'hello'
734
+ paddedStr.trimStart<typeof paddedStr>(); // type: 'hello '
735
+ paddedStr.trimEnd<typeof paddedStr>(); // type: ' hello'
736
+ ```
737
+
738
+ These enhanced types provide several benefits:
739
+
740
+ 1. **Compile-Time Type Safety**:
741
+
742
+ - Catch type errors before runtime
743
+ - Get accurate type inference for method results
744
+ - Validate string operations at compile time
745
+
746
+ 2. **Better IDE Support**:
747
+
748
+ - Improved autocompletion
749
+ - More accurate type hints
750
+ - Better refactoring support
751
+
752
+ 3. **Type-Level String Manipulation**:
753
+
754
+ - Perform string operations at the type level
755
+ - Get literal type results for string operations
756
+ - Chain operations with type safety
757
+
758
+ 4. **Advanced Type Features**:
759
+ - Generic type parameters for flexible usage
760
+ - Conditional type inference
761
+ - Template literal type support
762
+
763
+ ```typescript
764
+ // Example of chaining operations with type safety
765
+ const result = "Hello, World!".toLowerCase<string>().replace<string, "hello", "hi">().split<string, " ">().join("-");
766
+
767
+ // TypeScript knows the exact type at each step
768
+ ```
769
+
770
+ ## Testing Utilities
771
+
772
+ The package includes specialized utilities for testing ANSI colored strings, making it easier to write tests for terminal output and colored text.
773
+
774
+ ### ANSI String Formatting and Comparison
775
+
776
+ The `formatAnsiString` function helps format ANSI strings for test output, providing multiple representations:
777
+
778
+ ```typescript
779
+ import { formatAnsiString } from "@visulima/string/test/utils";
780
+ import { red } from "@visulima/colorize";
781
+
782
+ const coloredText = red("Error message");
783
+ const formatted = formatAnsiString(coloredText);
784
+
785
+ // Returns an object with:
786
+ // - ansi: Original string with ANSI codes
787
+ // - stripped: String with ANSI codes removed
788
+ // - visible: String with escape codes shown as visible characters
789
+ // - json: JSON stringified version
790
+ // - lengthDifference: Difference between ANSI and stripped length
791
+ ```
792
+
793
+ ### Comparing ANSI Strings
794
+
795
+ The `compareAnsiStrings` function provides detailed comparison between two ANSI strings:
796
+
797
+ ```typescript
798
+ import { compareAnsiStrings } from "@visulima/string/test/utils";
799
+ import { red, blue } from "@visulima/colorize";
800
+
801
+ const string1 = red("Error");
802
+ const string2 = blue("Error");
803
+
804
+ const result = compareAnsiStrings(string1, string2);
805
+ // Returns comparison details including:
806
+ // - ansiEqual: Whether the strings are identical including ANSI codes
807
+ // - strippedEqual: Whether the visible content is the same
808
+ // - summary: Length information and comparison results
809
+ // - actual/expected: Formatted representations of both strings
810
+ ```
811
+
812
+ ### Vitest Integration
813
+
814
+ The package includes a custom matcher for [Vitest](https://vitest.dev/) that makes testing ANSI strings straightforward:
815
+
816
+ ```typescript
817
+ import { expect, describe, it } from "vitest";
818
+ import { toEqualAnsi } from "@visulima/string/test/vitest";
819
+ import { red, green } from "@visulima/colorize";
820
+
821
+ // Extend Vitest with the custom matcher
822
+ expect.extend({ toEqualAnsi });
823
+
824
+ describe("colored output tests", () => {
825
+ it("should display the correct error message", () => {
826
+ const actual = getErrorMessage(); // Returns colored string
827
+ const expected = red("Error: ") + green("File not found");
828
+
829
+ // Compare ANSI strings with detailed error messages on failure
830
+ expect(actual).toEqualAnsi(expected);
831
+ });
832
+ });
833
+ ```
834
+
835
+ The custom matcher provides detailed error messages when tests fail, showing:
836
+
837
+ - The visible content of both strings
838
+ - The ANSI escape codes in both strings
839
+ - Whether the visible content matches but the colors differ
840
+ - Length information for both strings
841
+
842
+ ## Related
843
+
844
+ - [change-case](https://github.com/blakeembrey/change-case) - Simple string case utilities
845
+ - [lodash](https://lodash.com/) - Comprehensive utility library with string manipulation
846
+ - [scule](https://github.com/unjs/scule) - 🧵 String Case Utils
847
+ - [case-anything](https://github.com/mesqueeb/case-anything) - camelCase, kebab-case, PascalCase... a simple integration with nano package size. (SMALL footprint!)
848
+ - [cli-truncate](https://github.com/sindresorhus/cli-truncate) - Truncate strings for terminal output
849
+ - [string-width](https://github.com/sindresorhus/string-width) - Measure string width
850
+ - [ansi-slice](https://github.com/sindresorhus/ansi-slice) - Slice strings with ANSI escape codes
851
+ - [fast-string-truncated-width](https://github.com/fabiospampinato/fast-string-truncated-width) - Fast string truncated width
852
+ - [ansi-truncate](https://github.com/fabiospampinato/ansi-truncate) - Truncate strings with ANSI escape codes
853
+ - [string-ts](https://github.com/gustavoguichard/string-ts) - Strongly typed string functions
854
+
855
+ ## Supported Node.js Versions
856
+
857
+ Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule).
858
+ Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).
859
+
860
+ ## Contributing
861
+
862
+ If you would like to help, take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines.
863
+
864
+ > **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
865
+
866
+ ## Credits
867
+
868
+ - [Daniel Bannert](https://github.com/prisis)
869
+ - [All Contributors](https://github.com/visulima/visulima/graphs/contributors)
870
+
871
+ ## License
872
+
873
+ The visulima string is open-sourced software licensed under the [MIT][license-url]
874
+
875
+ [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
876
+ [typescript-url]: https://www.typescriptlang.org/ "TypeScript"
877
+ [license-image]: https://img.shields.io/npm/l/@visulima/string?color=blueviolet&style=for-the-badge
878
+ [license-url]: LICENSE.md "license"
879
+ [npm-image]: https://img.shields.io/npm/v/@visulima/string/latest.svg?style=for-the-badge&logo=npm
880
+ [npm-url]: https://www.npmjs.com/package/@visulima/string/v/latest "npm"