# @helpers4/string

> Tree-shakable TypeScript utility functions for the `string` domain.
> Package: `@helpers4/string` — Version: 2.0.0
> License: LGPL-3.0-or-later

## Installation

```sh
npm install @helpers4/string
# or
pnpm add @helpers4/string
```

## Usage

```typescript
import { camelCase, capitalize, escapeHtml, ... } from '@helpers4/string';
```

## Functions

| Function | Description |
|---|---|
| `camelCase` | Converts kebab-case to camelCase |
| `capitalize` | Capitalizes the first letter of a string. By default, lowercases the remaining characters. Pass `{ l |
| `escapeHtml` | Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.  Use this to safely emb |
| `extractErrorMessage` | Convert an error to a readable message. |
| `injectWordBreaks` | Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as sid |
| `kebabCase` | Converts camelCase to kebab-case |
| `leadingSentence` | Extracts the leading sentence from a string.  A sentence boundary is detected at the first occurrenc |
| `pascalCase` | Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed format |
| `slugify` | Converts a string into a URL-friendly slug. |
| `snakeCase` | Converts a string to snake_case. Handles camelCase, PascalCase, kebab-case, spaces, and mixed format |
| `template` | Interpolates `{{key}}` placeholders in a template string with values from a data record. Unknown key |
| `titleCase` | Converts a string to Title Case. Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and  |
| `truncate` | Truncates a string to `maxLength` characters, appending an ellipsis when cut.  The ellipsis counts t |
| `words` | Splits a string into an array of words.  Handles camelCase, PascalCase, SCREAMING_SNAKE_CASE, kebab- |

---

## API Reference

### `camelCase`

Converts kebab-case to camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: string): string
```

**Parameters:**

- `str: string` — The kebab-case string to convert

**Returns:** `string` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The kebab-case string to convert

**Returns:** `undefined` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: null): null
```

**Parameters:**

- `str: null` — The kebab-case string to convert

**Returns:** `null` — String in camelCase

**Examples:**

*Convert kebab-case to camelCase*

Converts a kebab-case string to camelCase.

```typescript
camelCase('my-component-name')
// => 'myComponentName'
```

---

### `capitalize`

Capitalizes the first letter of a string.
By default, lowercases the remaining characters.
Pass `{ lowercaseRest: false }` to only uppercase the first character.

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: string, options?: CapitalizeOptions): string
```

**Parameters:**

- `str: string` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `string` — String with first letter uppercased

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: undefined, options?: CapitalizeOptions): undefined
```

**Parameters:**

- `str: undefined` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `undefined` — String with first letter uppercased

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: null, options?: CapitalizeOptions): null
```

**Parameters:**

- `str: null` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `null` — String with first letter uppercased

**Examples:**

*Capitalize a word*

Uppercases the first letter and lowercases the rest.

```typescript
capitalize('hello')
// => 'Hello'
```

*Handle mixed case*

Lowercases all letters except the first one (default behaviour).

```typescript
capitalize('hELLO')
// => 'Hello'
```

*Uppercase first only — leave rest untouched*

Use { lowercaseRest: false } to preserve the original casing of the remaining characters.

```typescript
capitalize('hELLO', { lowercaseRest: false })
// => 'HELLO'
```

---

### `escapeHtml`

Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.

Use this to safely embed untrusted content into HTML attribute values or
text nodes without risk of XSS injection.

```typescript
import { escapeHtml } from '@helpers4/string';

escapeHtml(str: string): string
```

**Parameters:**

- `str: string` — The string to escape.

**Returns:** `string` — The escaped string.

**Examples:**

*Escape script tags*

Converts < > " ' & to their HTML entities to prevent XSS.

```typescript
escapeHtml('<script>alert("xss")</script>')
// => '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
```

*Safe interpolation in templates*

Safe for inserting untrusted strings into HTML.

```typescript
const userInput = '<b>bold</b>';
escapeHtml(userInput) // => '&lt;b&gt;bold&lt;/b&gt;'
```

---

### `extractErrorMessage`

Convert an error to a readable message.

```typescript
import { extractErrorMessage } from '@helpers4/string';

extractErrorMessage(error: unknown, stringify: string | true): string
```

**Parameters:**

- `error: unknown` — an error
- `stringify: string | true` — stringifies the error if no extractable message is found

**Returns:** `string` — a readable message or a stringified error if stringify is true, otherwise undefined

```typescript
import { extractErrorMessage } from '@helpers4/string';

extractErrorMessage(error?: unknown, stringify?: string | boolean): string | undefined
```

**Parameters:**

- `error?: unknown` — an error
- `stringify?: string | boolean` — stringifies the error if no extractable message is found

**Returns:** `string | undefined` — a readable message or a stringified error if stringify is true, otherwise undefined

**Examples:**

*Extract message from Error object*

Returns the stringified Error, including the class prefix.

```typescript
extractErrorMessage(new Error('Something went wrong'))
// => 'Error: Something went wrong'
```

*Handle string errors*

Returns the string directly when the error is a plain string.

```typescript
extractErrorMessage('plain error')
// => 'plain error'
```

*Stringify unknown errors*

When stringify is true, falls back to JSON.stringify for unrecognized errors.

```typescript
extractErrorMessage(42, true)
// => '42'
```

---

### `injectWordBreaks`

Adds word-break opportunities to a string so it can wrap cleanly in narrow
UI containers such as side panels or table cells.

Invisible zero-width spaces (`\u200B`) are inserted at meaningful
boundaries — camelCase splits, path separators, token edges — while
protected spans (URLs, emails, HTML) and atomic numeric values (`-0.1%`,
`12ms`, `1e-3`) are never broken. The visible text content is unchanged.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: string): string
```

**Parameters:**

- `str: string` — The string to process.

**Returns:** `string` — The string with word-break opportunities injected at meaningful
boundaries.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to process.

**Returns:** `undefined` — The string with word-break opportunities injected at meaningful
boundaries.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: null): null
```

**Parameters:**

- `str: null` — The string to process.

**Returns:** `null` — The string with word-break opportunities injected at meaningful
boundaries.

**Examples:**

*camelCase identifier*

Inserts ZWS at each camelCase boundary so a long identifier can wrap in a narrow column.

```typescript
injectWordBreaks('getUserProfileData')
// => 'get\u200BUser\u200BProfile\u200BData'
```

*Comma-separated tokens*

The comma attaches to the left token so a line never starts with a comma.

```typescript
injectWordBreaks('foo,bar')
// => 'foo,\u200Bbar'
```

*Atomic numeric value*

Signed decimals and numbers with units (-0.1%, 12ms, -2.4E+6) are never split.

```typescript
injectWordBreaks('-0.1%')
// => '-0.1%'   (unchanged — atomic value)
```

*File path*

Slashes become wrap points so a long path can break at each component.

```typescript
injectWordBreaks('path/to/my_file')
// => 'path\u200B/\u200Bto\u200B/\u200Bmy_file'
```

*URL is preserved intact*

URLs are D0-protected spans — no ZWS is inserted inside or adjacent to them.

```typescript
injectWordBreaks('https://example.com/foo/bar')
// => 'https://example.com/foo/bar'   (unchanged)
```

---

### `kebabCase`

Converts camelCase to kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: string): string
```

**Parameters:**

- `str: string` — The camelCase string to convert

**Returns:** `string` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The camelCase string to convert

**Returns:** `undefined` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: null): null
```

**Parameters:**

- `str: null` — The camelCase string to convert

**Returns:** `null` — String in kebab-case

**Examples:**

*Convert camelCase to kebab-case*

Converts a camelCase string to kebab-case.

```typescript
kebabCase('myComponentName')
// => 'my-component-name'
```

---

### `leadingSentence`

Extracts the leading sentence from a string.

A sentence boundary is detected at the first occurrence of `.`, `?`, `!`,
`…`, or `;` followed by whitespace or end of string. Newlines are collapsed
to spaces before matching.

If no boundary is found the entire (cleaned) string is returned.

To cap the result at a maximum length, combine with truncate:
```ts
truncate(leadingSentence(input), 120)
```

```typescript
import { leadingSentence } from '@helpers4/string';

leadingSentence(input: string): string
```

**Parameters:**

- `input: string` — The source string

**Returns:** `string` — The first sentence, including its terminal character

**Examples:**

*Extract the leading sentence*

Returns the first sentence, terminated by . ? or !.

```typescript
leadingSentence('Returns the sum of an array. Works with any numbers.')
// => 'Returns the sum of an array.'
```

*Works with ? and !*

Recognises question marks and exclamation marks as sentence terminators.

```typescript
leadingSentence('Is it done? Yes it is!')
// => 'Is it done?'
```

*Cap length by combining with truncate*

Use truncate to limit the result to a fixed number of characters.

```typescript
truncate(leadingSentence(input), 120)
```

---

### `pascalCase`

Converts a string to PascalCase.
Handles camelCase, kebab-case, snake_case, spaces, and mixed formats.

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in PascalCase

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in PascalCase

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in PascalCase

**Examples:**

*Convert kebab-case to PascalCase*

Converts a kebab-case string to PascalCase.

```typescript
pascalCase('my-component')
// => 'MyComponent'
```

*Convert snake_case to PascalCase*

Also handles snake_case and other formats.

```typescript
pascalCase('user_first_name')
// => 'UserFirstName'
```

---

### `slugify`

Converts a string into a URL-friendly slug.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: string): string
```

**Parameters:**

- `str: string` — The string to convert into a slug.

**Returns:** `string` — A lowercase, hyphen-separated slug safe for URLs.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert into a slug.

**Returns:** `undefined` — A lowercase, hyphen-separated slug safe for URLs.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: null): null
```

**Parameters:**

- `str: null` — The string to convert into a slug.

**Returns:** `null` — A lowercase, hyphen-separated slug safe for URLs.

**Examples:**

*Create a URL-safe slug*

Converts a string into a lowercase, hyphen-separated slug.

```typescript
slugify('Hello World!')
// => 'hello-world'
```

*Handle accented characters*

Normalizes Unicode characters and strips diacritics.

```typescript
slugify('Crème brûlée')
// => 'creme-brulee'
```

---

### `snakeCase`

Converts a string to snake_case.
Handles camelCase, PascalCase, kebab-case, spaces, and mixed formats.

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in snake_case

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in snake_case

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in snake_case

**Examples:**

*Convert camelCase to snake_case*

Converts a camelCase string to snake_case.

```typescript
snakeCase('myVariableName')
// => 'my_variable_name'
```

*Convert kebab-case to snake_case*

Also handles kebab-case and other formats.

```typescript
snakeCase('my-component-name')
// => 'my_component_name'
```

---

### `template`

Interpolates `{{key}}` placeholders in a template string with values from
a data record. Unknown keys are replaced with an empty string.

No `eval` or `Function` constructor is used — substitution is purely
regex-based. Nested expressions and logic are intentionally out of scope.

```typescript
import { template } from '@helpers4/string';

template(str: string, data: Record<string, unknown>): string
```

**Parameters:**

- `str: string` — The template string containing `{{key}}` placeholders.
- `data: Record<string, unknown>` — A record mapping placeholder names to replacement values.

**Returns:** `string` — The template string with all placeholders replaced.

**Examples:**

*Simple interpolation*

Replaces {{key}} placeholders with values from the data object.

```typescript
template('Hello, {{name}}!', { name: 'Alice' })
// => 'Hello, Alice!'
```

*Multiple placeholders*

All matching placeholders are replaced in a single pass.

```typescript
template('{{greeting}}, {{name}}!', { greeting: 'Hi', name: 'Bob' })
// => 'Hi, Bob!'
```

*Missing keys become empty string*

Unknown placeholders are replaced with an empty string.

```typescript
template('Hello, {{name}}!', {})
// => 'Hello, !'
```

---

### `titleCase`

Converts a string to Title Case.
Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and mixed formats.

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in Title Case

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in Title Case

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in Title Case

**Examples:**

*Convert kebab-case to Title Case*

Transforms a delimited string into Title Case.

```typescript
titleCase('my-component-name')
// => 'My Component Name'
```

*Convert camelCase to Title Case*

Also handles camelCase by splitting on uppercase transitions.

```typescript
titleCase('queryItems')
// => 'Query Items'
```

---

### `truncate`

Truncates a string to `maxLength` characters, appending an ellipsis when cut.

The ellipsis counts toward `maxLength`, so the result is always at most
`maxLength` characters long. If the string is already within the limit, it
is returned unchanged (no ellipsis appended). `null` and `undefined` inputs
are returned as-is to align with other string helpers.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: undefined, maxLength: number, ellipsis?: string): undefined
```

**Parameters:**

- `input: undefined` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `undefined` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: null, maxLength: number, ellipsis?: string): null
```

**Parameters:**

- `input: null` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `null` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: string, maxLength: number, ellipsis?: string): string
```

**Parameters:**

- `input: string` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `string` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

**Examples:**

*Truncate with default ellipsis*

Appends … when the string exceeds the limit.

```typescript
truncate('Hello, world!', 8)
// => 'Hello, …'
```

*Truncate with custom ellipsis*

The ellipsis counts toward the maxLength.

```typescript
truncate('Hello, world!', 8, '...')
// => 'Hello...'
```

*String within limit*

Returned unchanged when already short enough.

```typescript
truncate('Hi', 10)
// => 'Hi'
```

---

### `words`

Splits a string into an array of words.

Handles camelCase, PascalCase, SCREAMING_SNAKE_CASE, kebab-case,
snake_case, and regular whitespace-separated text. Numbers are
treated as word tokens.

```typescript
import { words } from '@helpers4/string';

words(str: string): string[]
```

**Parameters:**

- `str: string` — The string to split into words.

**Returns:** `string[]` — An array of word tokens.

**Examples:**

*Split common string formats*

Splits camelCase, PascalCase, snake_case, kebab-case and space-separated words.

```typescript
words('camelCaseString') // => ['camel', 'Case', 'String']
words('snake_case')       // => ['snake', 'case']
words('kebab-case')       // => ['kebab', 'case']
words('hello world')      // => ['hello', 'world']
```

*Build camelCase from any input*

Combine with a map to convert from any naming convention.

```typescript
const toCamel = (str: string) =>
  words(str)
    .map((w, i) => i === 0 ? w.toLowerCase() : w[0].toUpperCase() + w.slice(1).toLowerCase())
    .join('');
toCamel('hello-world'); // => 'helloWorld'
```

---
