# @helpers4/string

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

## Installation

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

## Usage

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

## Functions

| Function | Description |
|---|---|
| `camelCase` | Converts a string to camelCase. Handles PascalCase, kebab-case, snake_case, spaces, and mixed format |
| `capitalize` | Capitalizes the first letter of a string. By default, lowercases the remaining characters. Pass `{ l |
| `dedent` | Strips the common leading whitespace from every line of a multi-line string, and trims a single lead |
| `escapeHtml` | Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.  Use this to safely emb |
| `escapeRegExp` | Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`) in a string so it can be s |
| `extractErrorMessage` | Convert an error to a readable message. |
| `formatProgressBar` | Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width` cells prop |
| `injectWordBreaks` | Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as sid |
| `isBlank` | Checks if a string is blank — empty or contains only whitespace characters. `null` and `undefined` a |
| `isEmpty` | Checks if a string is empty (`""`), `null`, or `undefined`.  This is a strict emptiness check — whit |
| `isNonEmpty` | Checks if a string is non-empty (has at least one character). `null` and `undefined` are considered  |
| `isNotBlank` | Checks if a string is not blank — non-empty and contains at least one non-whitespace character. `nul |
| `kebabCase` | Converts a string to kebab-case. Handles camelCase, PascalCase, snake_case, spaces, and mixed format |
| `leadingSentence` | Extracts the leading sentence from a string. `null` and `undefined` are passed through unchanged.  A |
| `pascalCase` | Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed format |
| `removeDiacritics` | Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`.  Works by Unicode-decom |
| `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 |
| `unescapeHtml` | Unescapes the HTML entities `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` back to `&`, `<`, `>`, `" |
| `words` | Splits a string into an array of words. `null` and `undefined` return `[]`.  Handles camelCase, Pasc |

---

## API Reference

### `camelCase`

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

An embedded run of capitals is treated as an acronym boundary: only its last letter starts
the next word, the rest are lowercased (matching lodash's `camelCase` convention) — so an
already-camelCase identifier containing an acronym is not left untouched.

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

camelCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

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

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

camelCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

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

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

camelCase(str: null): null
```

**Parameters:**

- `str: null` — The 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'
```

---

### `dedent`

Strips the common leading whitespace from every line of a multi-line
string, and trims a single leading/trailing blank line if present.

Lets you write readable, indented multi-line strings in source code
(typically template literals) without that indentation leaking into
the output.

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

dedent(str: string): string
```

**Parameters:**

- `str: string` — The string to dedent

**Returns:** `string` — The dedented string

**Examples:**

*Write readable multi-line strings without leaking source indentation*

Strips the common leading whitespace and the wrapping blank lines.

```typescript
dedent(`
  Hello
    World
`)
// => 'Hello\n  World'
```

*The minimum indentation across all lines is what gets removed*

Lines with more indentation than the minimum keep their relative indent.

```typescript
dedent('    a\n  b\n      c')
// => '  a\nb\n    c'
```

---

### `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;'
```

---

### `escapeRegExp`

Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`)
in a string so it can be safely embedded in a `RegExp` pattern.

Use this before building a `RegExp` from untrusted or dynamic input — without
it, characters like `.` or `(` change the pattern's meaning instead of being
matched literally.

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

escapeRegExp(str: string): string
```

**Parameters:**

- `str: string` — The string to escape

**Returns:** `string` — The escaped string, safe to embed in a `RegExp` pattern

**Examples:**

*Escape metacharacters before building a RegExp*

Without escaping, "." and "?" would change the pattern's meaning.

```typescript
escapeRegExp('1 + 1 = 2?')
// => '1 \\+ 1 = 2\\?'
```

*Safely search for untrusted user input*

Escaping lets user-provided text be matched literally instead of as a pattern.

```typescript
const userInput = 'a.b';
new RegExp(escapeRegExp(userInput)).test('a.b')   // => true
new RegExp(escapeRegExp(userInput)).test('axb')   // => false (literal '.', not "any char")
```

---

### `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'
```

---

### `formatProgressBar`

Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width`
cells proportional to `value / max`.

`value` is clamped to `[0, max]` before computing the ratio — out-of-range values (negative,
above `max`) produce an empty or fully-filled bar instead of throwing. Non-finite `max`
(`NaN`, `Infinity`) is treated as `0`, yielding an empty bar.

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

formatProgressBar(value: number, options: ProgressBarOptions): string
```

**Parameters:**

- `value: number` — The current value
- `options: ProgressBarOptions` (default: `{}`) — Bar rendering options

**Returns:** `string` — A string of `width` repeated filled/empty characters

**Examples:**

*Default 20-cell bar*

Renders a percentage as a filled/empty block bar using the default width and characters.

```typescript
formatProgressBar(65)
// => '▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░'
```

*Custom width, characters, and scale*

Renders a value against a custom max, width, and fill characters — useful for anything scored out of a different total than 100.

```typescript
formatProgressBar(3, { width: 10, max: 5, filledChar: '#', emptyChar: '-' })
// => '######----'
```

---

### `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)
```

---

### `isBlank`

Checks if a string is blank — empty or contains only whitespace characters.
`null` and `undefined` are considered blank and return `true`.

Uses `String.prototype.trim()` internally, which covers all ECMAScript
whitespace: standard ASCII whitespace (`\t`, `\n`, `\r`, `\f`, `\v`),
non-breaking space (U+00A0), BOM (U+FEFF), and all Unicode "Space_Separator"
category characters (en space, em space, thin space, ideographic space, etc.).

**Zero-width characters** (U+200B zero-width space, U+200C, U+200D, U+2060)
are **not** treated as whitespace — they are Unicode "Format" (Cf) characters,
not spaces. Strip them explicitly if needed:
`isBlank(value.replace(/[​-‍⁠]/g, ''))`

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

isBlank(value: string | null | undefined): boolean
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `boolean` — `true` if the string is empty, contains only whitespace, or is `null`/`undefined`

**Examples:**

*Detect empty or whitespace-only strings*

Returns true for "" and for any string made entirely of whitespace — including non-breaking space (U+00A0), en/em spaces, ideographic space, and BOM.

```typescript
isBlank('')      // => true
isBlank('   ')   // => true
isBlank('\t\n')  // => true
isBlank(' ')     // => true   (non-breaking space U+00A0)
isBlank('foo')   // => false
isBlank(' x ')   // => false
```

*Form validation — reject blank input*

Use isBlank to reject fields that contain only whitespace.

```typescript
function validateName(name: string): string | null {
  if (isBlank(name)) return 'Name is required';
  return null;
}
validateName('')    // => 'Name is required'
validateName('   ') // => 'Name is required'
validateName('Ada') // => null
```

---

### `isEmpty`

Checks if a string is empty (`""`), `null`, or `undefined`.

This is a strict emptiness check — whitespace-only strings are **not** considered
empty. Use `isEmpty(value?.trim())` if you need to treat blank strings as empty.

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

isEmpty(value: string | null | undefined): value is "" | null | undefined
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `value is "" | null | undefined` — `true` if the string is `""`, `null`, or `undefined`

**Examples:**

*Check if a string is empty*

Returns true only for `""`. Whitespace-only strings are not considered empty.

```typescript
isEmpty('')    // => true
isEmpty(' ')   // => false  (whitespace is content)
isEmpty('foo') // => false
```

*Treat blank strings as empty by trimming first*

Compose with .trim() when whitespace-only should also be considered empty.

```typescript
isEmpty(''.trim())   // => true
isEmpty('   '.trim()) // => true
isEmpty('hi'.trim())  // => false
```

---

### `isNonEmpty`

Checks if a string is non-empty (has at least one character).
`null` and `undefined` are considered empty and return `false`.

Whitespace-only strings are considered non-empty.
Use `isNonEmpty(value?.trim())` if you need to exclude blank strings.

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

isNonEmpty(value: string | null | undefined): value is string
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `value is string` — `true` if the string has at least one character; `false` for `""`, `null`, or `undefined`.
Acts as a type guard: the `if` branch narrows `string | null | undefined` to `string`.

**Examples:**

*Check if a string has content*

Returns true for any string with at least one character, including whitespace.

```typescript
isNonEmpty('hello') // => true
isNonEmpty(' ')     // => true  (whitespace is content)
isNonEmpty('')      // => false
```

*Exclude blank strings by trimming first*

Compose with .trim() when whitespace-only strings should be treated as empty.

```typescript
isNonEmpty('hello'.trim()) // => true
isNonEmpty('   '.trim())   // => false
isNonEmpty(''.trim())      // => false
```

---

### `isNotBlank`

Checks if a string is not blank — non-empty and contains at least one
non-whitespace character.
`null` and `undefined` are considered blank and return `false`.

Uses `String.prototype.trim()` internally. See `isBlank` for the full list
of characters considered whitespace (includes non-breaking space, en/em space,
ideographic space, etc.).

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

isNotBlank(value: string | null | undefined): boolean
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `boolean` — `true` if the string has at least one non-whitespace character; `false` for blank, `null`, or `undefined`

**Examples:**

*Check that a string has real content*

Returns true only when the string contains at least one non-whitespace character.

```typescript
isNotBlank('foo')  // => true
isNotBlank(' x ')  // => true
isNotBlank('')     // => false
isNotBlank('   ')  // => false
isNotBlank('\t')   // => false
```

*Filter out blank strings from an array*

Use as a predicate in .filter() to keep only strings with real content.

```typescript
const tags = ['typescript', '  ', '', 'helpers'];
tags.filter(isNotBlank)
// => ['typescript', 'helpers']
```

---

### `kebabCase`

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

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

kebabCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

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

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

kebabCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

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

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

kebabCase(str: null): null
```

**Parameters:**

- `str: null` — The 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.
`null` and `undefined` are passed through unchanged.

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; or `null`/`undefined` if input is nullish

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

leadingSentence(input: null): null
```

**Parameters:**

- `input: null` — The source string

**Returns:** `null` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

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

leadingSentence(input: undefined): undefined
```

**Parameters:**

- `input: undefined` — The source string

**Returns:** `undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

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

leadingSentence(input: string | null | undefined): string | null | undefined
```

**Parameters:**

- `input: string | null | undefined` — The source string

**Returns:** `string | null | undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

**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'
```

---

### `removeDiacritics`

Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`.

Works by Unicode-decomposing each character into its base letter plus
combining marks (`'é'` → `'e'` + a combining acute accent), then
stripping the marks. Same technique already used internally by `slugify`.

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

removeDiacritics(str: string): string
```

**Parameters:**

- `str: string` — The string to strip diacritics from

**Returns:** `string` — The string with diacritics removed

**Examples:**

*Strip accents from a string*

Useful for accent-insensitive search or generating ASCII-safe identifiers.

```typescript
removeDiacritics('café')
// => 'cafe'
```

*Plain ASCII text is left unchanged*

Only characters with combining diacritical marks are affected.

```typescript
removeDiacritics('hello world')
// => 'hello world'
```

---

### `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'
```

---

### `unescapeHtml`

Unescapes the HTML entities `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;`
back to `&`, `<`, `>`, `"`, and `'`.

This is the exact inverse of escapeHtml — it only recognizes the
five entities that function produces, not the full HTML entity set (no
`&nbsp;`, no numeric code points beyond `&#39;`, etc.).

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

unescapeHtml(str: string): string
```

**Parameters:**

- `str: string` — The string to unescape

**Returns:** `string` — The unescaped string

**Examples:**

*Unescape HTML entities back to their original characters*

Inverse of escapeHtml — turns &lt;, &gt;, &amp;, &quot;, &#39; back into < > & " '.

```typescript
unescapeHtml('&lt;b&gt;bold&lt;/b&gt;')
// => '<b>bold</b>'
```

*Unrecognized entities are left untouched*

Only the five entities escapeHtml produces are unescaped — not the full HTML entity set.

```typescript
unescapeHtml('a&nbsp;b')
// => 'a&nbsp;b'  (unchanged)
```

---

### `words`

Splits a string into an array of words.
`null` and `undefined` return `[]`.

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 | null | undefined): string[]
```

**Parameters:**

- `str: string | null | undefined` — 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'
```

---
