# @helpers4/string

> Tree-shakable TypeScript utility functions for the `string` domain.
> Package: `@helpers4/string` — Version: 2.0.4
> 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 |
| `isBlank` | Checks if a string is blank — empty or contains only whitespace characters.  Uses `String.prototype. |
| `isEmpty` | Checks if a string is empty (`""`).  This is a strict emptiness check — whitespace-only strings are  |
| `isNonEmpty` | Checks if a string is non-empty (has at least one character).  Whitespace-only strings are considere |
| `isNotBlank` | Checks if a string is not blank — non-empty and contains at least one non-whitespace character.  Use |
| `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)
```

---

### `isBlank`

Checks if a string is blank — empty or contains only whitespace characters.

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): boolean
```

**Parameters:**

- `value: string` — The string to check

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

**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 (`""`).

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): value is ""
```

**Parameters:**

- `value: string` — The string to check

**Returns:** `value is ""` — `true` if the string is `""`

**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).

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): boolean
```

**Parameters:**

- `value: string` — The string to check

**Returns:** `boolean` — `true` if the string has at least one character

**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.

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): boolean
```

**Parameters:**

- `value: string` — The string to check

**Returns:** `boolean` — `true` if the string has at least one non-whitespace character

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

---
