# @reykjavik/hanna-utils

> A collection of vanilla JavaScript functions and constants, that tend to be helpful when working with (or within) the Hanna design system.

Latest version **0.2.22** (published 2026-03-11) · MIT license · 0 weekly downloads

## Install

```sh
npm install @reykjavik/hanna-utils
pnpm add @reykjavik/hanna-utils
yarn add @reykjavik/hanna-utils
bun add @reykjavik/hanna-utils
```

## Health

**Score 60/100 (C)** — status: stable.

Positive: has types; esm support; no vulnerabilities; high maintenance score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.2.22 |
| Published | 2026-03-11 |
| First published | 2022-05-30 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=16 |
| Dependencies | 4 |
| Unpacked size | 150.8 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1 |
| Author | Reykjavík |
| Maintainers | reykjavik |

## Links

- npm: https://www.npmjs.com/package/@reykjavik/hanna-utils
- Repository: https://github.com/rvk-utd/hanna
- Homepage: https://github.com/rvk-utd/hanna/blob/main/modules/hanna-utils/README.md
- Issues: https://github.com/rvk-utd/hanna/issues
- npm.io page: https://npm.io/package/@reykjavik/hanna-utils

## Dependencies (4)

- [tslib](https://npm.io/package/tslib.md) ^2.4.0
- [formatchange](https://npm.io/package/formatchange.md) ^2.2.0
- [focus-visible](https://npm.io/package/focus-visible.md) ^5.2.0
- [@hugsmidjan/qj](https://npm.io/package/@hugsmidjan/qj.md) ^4.22.1

## Recent versions

- 0.2.22 (latest) — 2026-03-11
- 0.2.21 — 2025-05-23
- 0.2.20 — 2025-03-28
- 0.2.19 — 2024-11-21
- 0.2.18 — 2024-11-20
- 0.2.17 — 2024-10-17
- 0.2.16 — 2024-09-03
- 0.2.15 — 2024-04-05
- 0.2.14 — 2024-01-25
- 0.2.13 — 2023-12-08
- 0.2.12 — 2023-09-26
- 0.2.11 — 2023-09-26
- 0.2.10 — 2023-09-25
- 0.2.9 — 2023-09-04
- 0.2.8 — 2023-07-25
- … 27 more at https://npm.io/package/@reykjavik/hanna-utils/versions

## README

# @reykjavik/hanna-utils <!-- omit from toc -->

A collection of vanilla JavaScript functions and constants, that tend to be
helpful when working with (or within) the Hanna design system.

This library is also a core dependency of most other packages in this repo,
including [hanna-react](../hanna-react), [hanna-css](../hanna-css), and more.

```sh
yarn add @reykjavik/hanna-utils
```

**Table of Contents:**

<!-- prettier-ignore-start -->

- [Misc Utilities](#misc-utilities)
  - [`getSVGtext`](#getsvgtext)
  - [`getFormatMonitor`](#getformatmonitor)
  - [`printDate`](#printdate)
  - [`getStableRandomItem`](#getstablerandomitem)
  - [`capitalize`](#capitalize)
  - [`dumbId`](#dumbid)
  - [`classes`](#classes)
  - [`modifiedClass`](#modifiedclass)
- [Asset helpers](#asset-helpers)
  - [Reykjavík Logo](#reykjavík-logo)
  - [Favicons](#favicons)
  - [Illustrations](#illustrations)
  - [Efnistákn Icons](#efnistákn-icons)
  - [Formheimur Shapes](#formheimur-shapes)
  - [Bling Shapes](#bling-shapes)
  - [Misc. Style Server Assets](#misc-style-server-assets)
  - [Style-Server Info](#style-server-info)
    - [`styleServerUrl`](#styleserverurl)
    - [`setStyleServerUrl`](#setstyleserverurl)
- [I18N helpers](#i18n-helpers)
  - [`getTexts`](#gettexts)
  - [`DEFAULT_LANG`](#default_lang)
  - [`ensureHannaLang`](#ensurehannalang)
  - [`setDefaultLanguage`](#setdefaultlanguage)
  - [`setDefaultLanguage.push()`](#setdefaultlanguagepush)
- [Social Media Sharing](#social-media-sharing)
- [Branded types](#branded-types)
  - [`ensurePosInt`](#ensureposint)
- [TypeScript helpers](#typescript-helpers)
  - [`notNully`](#notnully)
  - [`notFalsy`](#notfalsy)
  - [`ObjectKeys`, `ObjectEntries`, `ObjectFromEntries`](#objectkeys-objectentries-objectfromentries)
  - [Type `OpenRecord`](#type-openrecord)
  - [Type `OpenStringMap`](#type-openstringmap)
  - [Type `AllowKeys`](#type-allowkeys)
  - [Type `EitherObj`](#type-eitherobj)
  - [Type `OmitDistributive`](#type-omitdistributive)
  - [Type `PickDistributive`](#type-pickdistributive)
  - [Type `RequireExplicitUndefined`](#type-requireexplicitundefined)
  - [Type Testing Helpers](#type-testing-helpers)
    - [Type `Expect<T>`](#type-expectt)
    - [Type `NotExpect<T>`](#type-notexpectt)
    - [Type `Equals<A, B>`](#type-equalsa-b)
    - [Type `Extends<A, B>`](#type-extendsa-b)
    - [Type `NotExtends<A, B>`](#type-notextendsa-b)
- [Changelog](#changelog)

<!-- prettier-ignore-end -->

## Misc Utilities

### `getSVGtext`

**Syntax:**
`getSVGtext(url: string | undefined, altText?: string): Promise<string>`

Fetches a remote SVG file and returns its markup contents — excluding any
leading `<?xml />` directives or "Generator" comments.

If you pass the optional `altText` parameter, it will attempt to inject a
`<title/>` element into the SVG string. (First removing existing `<title/>`.)

```ts
import { getSVGtext } from '@reykjavik/hanna-utils';

const svgUrl = 'https://styles.reykjavik.is/assets/reykjavik-logo.svg';

getSVGtext(svgUrl).then((svgMarkup) => {
  document.body.insertAdjacentHTML('beforeend', svgMarkup);
});
```

To check if file is svg:

```ts
import { getSVGtext } from '@reykjavik/hanna-utils';

const isSVG: true = getSVGtext.isSvgUrl(
  'https://styles.reykjavik.is/assets/reykjavik-logo.svg'
);
```

### `getFormatMonitor`

Returns an object that contains info about the currently active screen media
format and a way to subscribe/unsubscribe callbacks to whenever the window
switches over to another "media-format"

The Hanna CSS module/token `-basics` configures certain media-query
breakpoints with human-friendly names (i.e. "phone", "phablet", "tablet",
"netbook", "wide")

NOTE: In server-side environments (without `window` and `document` objects)
the exported "formatMonitor" object will not "start" and remains completely
inactive.

```ts
import { getFormatMonitor } from '@reykjavik/hanna-utils';
import type { MediaFormat } from '@reykjavik/hanna-utils';

formatMonitor = getFormatMonitor();

formatMonitor.media.is; // e.g. 'wide';

formatMonitor.subscribe((media: MediaFormat) => {
  media === formatMonitor.media; // true;
  // Do something because `media.is` has changed,
});
```

See
https://github.com/maranomynet/formatchange#3-getting-the-current-media-format
for more info.

(This utility is, for example, utilized by the
[`@reykjavik/hanna-react`](https://www.npmjs.com/package/@reykjavik/hanna-react)
package to implement its `useFormatMonitor` hook.)

### `printDate`

**Syntax:** `printDate(date: string | Date, lang?: string): string`

Very simple, very stupid, standalone date formatter for Icelandic (default),
English and Polish.

Just prints the full date (day, month, year). No bells. No whistles. No
options.

```ts
import { printDate } from '@reykjavik/hanna-utils';

printDate('2022-04-30', 'is'); // 30. apríl 2022
printDate(new Date('2022-04-30'), 'en'); // April 30, 2022
printDate('2022-04-30', 'pl'); // 30. kwietnia 2022
```

### `getStableRandomItem`

**Syntax:**
`getStableRandomItem<T>(items: ReadonlyArray<T> | Record<string, T>, seed: string): T`

Returns a pseudo random item from a collection of `items` (array or record),
based on a given `seed` key/id. The function always returns the same item for
a given collection and `seed`.

You might want to use this helper inside React components to get a stable
randomness, without having to resort to hooks.

```ts
import { getStableRandomItem } from '@reykjavik/hanna-utils';
import { blingTypes } from '@reykjavik/hanna-utils/assets';

// ...inside a component

const randomBling = getStableRandomItem(blingTypes, props.newsHeadline);
```

### `capitalize`

**Syntax:**
`capitalize<Str extends string>(str: Str, locale?: string): Capitalize<Str>`

Simple 'foo bar' --> 'Foo bar' mapper.

Default locale: `"IS"` (effectively same as `"EN"` and vanilla
`toUpperCase()`)

```ts
import { capitalize } from '@reykjavik/hanna-utils';

capitalize('hello world'); // "Hello world"
capitalize('istanbul', 'TR'); // "İstanbul"
```

### `dumbId`

**Syntax:** `dumbId(): string`

Returns a fast, short, **locally-unique**, append/prend-friendly, DOM-safe ID
string.

**NOTE:** The generated IDs are very predictable and should only be used for
temporary/frivolous purposes, e.g. as `id` attributes for DOM elements, etc.

```ts
import { dumbId } from '@reykjavik/hanna-utils';

const myDomId = dumbId(); // "_30828007-1_"
```

### `classes`

**Syntax:**
`classes(...args: Array<string | Falsy | Array<string | Falsy>>): string`

Filters and joins a messy list of CSS classNames, neatly skipping falsy
values.

```ts
import { classes } from '@reykjavik/hanna-utils';

const className = classes(
  'A',
  false,
  '',
  'B',
  ['C', null, [undefined, 'D']],
  null
);

console.log(className);
// 'A B C D'
```

### `modifiedClass`

**Syntax:**
`modifiedClass(base: string, modifiers: string | falsy | Array<string | Falsy>, extraClass?: string): string`

Constructs a BEM class-name with one or more optional "--modifier" flags.

```ts
import { modifiedClass } from '@reykjavik/hanna-utils';

const className = modifiedClass('MyComponent', 'primary', 'extra-class');
// 'MyComponent MyComponent--primary extra-class'

const className2 = modifiedClass('MyComponent', ['primary', 'large']);
// 'MyComponent MyComponent--primary MyComponent--large'

const className3 = modifiedClass('MyComponent', null);
// 'MyComponent'

const className4 = modifiedClass('MyComponent', [false, '', 'error']);
// 'MyComponent MyComponent--error'
```

<!--
### `focusElement`

**Syntax:** `focusElement(target: string | HTMLElement): ReturnType<typeof setTimeout>`

Simplistic helper to move keyboard `.focus()` to a given element.

If the element is not focusable (i.e. a `<div/>` without a `tabindex` attribute)
then this function has no effect.

```ts
import { focusElement } from '@reykjavik/hanna-utils';

focusElement('.TextBlock a');
// same as:
// focusElement(document.querySelector('.TextBlock a'));
```
-->

## Asset helpers

### Reykjavík Logo

**Syntax:** `getRvkLogoUrl(logoFile: RvkLogo): string`

Helper to generate URLs to Reyjavík's official coat of arms (or "logo"), with
and without the text.

```ts
import { getRvkLogoUrl } from '@reykjavik/hanna-utils/assets';

const defaultLogoSVG = getRvkLogoUrl(); // default is 'reykjavik-logo.svg'
const defaultLogoPNG = getRvkLogoUrl('reykjavik-logo.png'); // PNG version
const notextLogoSVG = getRvkLogoUrl('reykjavik-logo-notext.svg');
// etc...
```

Here's a list of available logo files:
[reykjavik-logo.json](../hanna-css/src/assets/reykjavik-logo.json)

### Favicons

**Syntax:** `getFavicon(faviconFile: Favicon): string`

Helper to generate URLs for various types of "favicons" or "webmanifest
icons", etc...

```ts
import { getFavicon } from '@reykjavik/hanna-utils/assets';

const url = getFavicon('favicon.svg');
```

The function is typed to provide auto-completion of all the available icon
types.

Here's a list of available logo files:
[reykjavik-logo.json](../hanna-css/src/assets/favicons.json)

### Illustrations

**Syntax:**
`getIllustrationUrl(illustration: Illustration, variant?: IllustrationVariant): string`

Utilities to work with the
[Illustrations](https://styles.reykjavik.is/assets/illustrations) on the asset
server.

```ts
import {
  illustrations,
  Illustration,
  getIllustrationUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Illustration = illustrations[0];

const url = getIllustrationUrl(assetName);
const thumbnailUrl = getIllustrationUrl(assetName, 'thumb');
```

### Efnistákn Icons

**Syntax:** `getEfnistaknUrl(icon: Efnistakn): string`

Utilities to work with the
[Efnistákn icons](https://styles.reykjavik.is/assets/efnistakn) on the asset
server.

```ts
import {
  efnistakn,
  Efnistakn,
  getEfnistaknUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Efnistakn = efnistakn[0];

const url = getEfnistaknUrl(assetName);
```

### Formheimur Shapes

**Syntax:** `getFormheimurUrl(shape: Formheimur): string`

Utilities to work with the
[Formheimur shapes](https://styles.reykjavik.is/assets/formheimur) on the
asset server.

```ts
import {
  formheimur,
  Formheimur,
  getFormheimurUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Formheimur = formheimur[0];

const url = getFormheimurUrl(assetName);
```

### Bling Shapes

**Syntax:** `getBlingUrl(blingType: BlingType): string`

Utilities to work with the
[Bling shapes](https://styles.reykjavik.is/assets/bling) on the asset server.

```ts
import {
  blingTypes,
  BlingType,
  getBlingUrl,
} from '@reykjavik/hanna-utils/assets';

const blingName: BlingType = blingTypes[0];

const url = getBlingUrl(blingName);
```

### Misc. Style Server Assets

**Syntax:** `getAssetUrl(filePath: string): string`

Helper to generate a URL to arbitrary asset on on the style server.

```ts
import { getAssetUrl } from '@reykjavik/hanna-utils/assets';

const url = getAssetUrl('reykjavik-logo.svg');
```

### Style-Server Info

#### `styleServerUrl`

**Syntax:** `styleServerUrl: string`

This URL is used when building links to graphic/styling assets, etc. It is
used internally by all of the above asset getter functions
(`getIllustrationUrl`, `getIllustrationUrl`).

The default value depends on `NODE_ENV`:

- Production mode:
  [`https://styles.reykjavik.is`](https://styles.reykjavik.is)
- Dev mode: [`https://styles.test.thon.is`](https://styles.test.thon.is)

#### `setStyleServerUrl`

**Syntax:** `setStyleServerUrl(url: string | URL | undefined): void`

This updates the value of `styleServerUrl` globally. Use it at the top of your
application if you want to load assets and CSS bundles from a custom
style-server instance, e.g. during testing/staging/etc.

The URLs are pushed to a simple stack, and if you want to unset a custom URL,
use the `setLinkRenderer.pop()` method to revert back to the previous one.
Example:

```js
import {
  setStyleServerUrl,
  styleServerURL,
} from '@reykjavik/hanna-utils/assets';

setStyleServerUrl('https://styles.test.thon.is/');

console.log(styleServerURL); // 'https://styles.test.thon.is'
const illustrationUrl1 = getIllustrationUrl('esjan');
// 'https://styles.test.thon.is/assets/illustrations/esjan.png'

setStyleServerUrl.pop(); // reset `styleServerUrl` to previous value

console.log(styleServerURL); // 'https://styles.reykjavik.is'
const illustrationUrl = getIllustrationUrl('esjan');
// 'https://styles.reykjavik.is/assets/illustrations/esjan.png'
```

You can explicitly switch to using the library's default `styleServerURL` by
passing `undefined` as an argument — like so:

```js
setStyleServerUrl(undefined); // pushes the default URL to the stack
console.log(styleServerURL); // 'https://styles.reykjavik.is'
```

Similarly the token `'test'` is an alias for the default test server URL.

```js
setStyleServerUrl('test'); // pushes the default test server URL to the stack
console.log(styleServerURL); // 'https://styles.test.thon.is'
```

## I18N helpers

### `getTexts`

**Syntax:**
`<Texts extends Record<string, unknown>, Lang extends string>( props: { texts?: Texts; lang?: Lang }, defaultTexts: DefaultTexts<Texts, Lang>) => Readonly<Texts>`

Helper for components that expose (optional) `texts` and `lang` props for
customizing their UI texts,

Returns `texts` when available, but otherwise it resolves the correct texts
object from within `defaultTexts` to use based on `lang` (falling back on
`DEFAULT_LANGUAGE` texts or Icelandic when all else fails).

In dev-mode it emits an error to the console if an unsupported `lang` is
passed.

```tsx
import {
  getTexts,
  type DefaultTexts,
  type HannaLang,
} from '@reykjavik/hanna-utils/i18n';

type Props = {
  isOpen: boolean;
  onToggle: () => void;
  // I18n props:
  texts?: { open: string; close: string };
  lang?: HannaLang;
};

const defaultTexts: DefaultTexts<Props['texts']> = {
  is: { open: 'Opna', close: 'Loka' },
  en: { open: 'Open', close: 'Close' },
  pl: { open: 'Otworzyć', close: 'Zamknąć' },
};

export const SillyToggler = (props: Props) => {
  const texts = getTexts(props, defaultTexts);

  return (
    <button onClick={props.onToggle}>
      {props.isOpen ? texts.open : texts.close}
    </button>
  );
};
```

### `DEFAULT_LANG`

**Syntax:** `DEFAULT_LANG: HannaLang`

All Hanna components that use `getTexts` will use this value as their default
translation language.

### `ensureHannaLang`

**Syntax:**
`ensureHannaLang(maybeLang: string|undefined): HannaLang | undefined`

Checks if the passed language is a `HannaLang`, and if so returns it.
Otherwise it returns `undefined`.

```ts
import { ensureHannaLang, type HannaLang } from '@reykjavik/hanna-utils/i18n';

const langQuery = new URLSearchParams(document.location.search).get('lang');

const lang: HannaLang | undefined = ensureHannaLang(langQuery);
```

### `setDefaultLanguage`

**Syntax:** `updateDefaultLanguage(lang: HannaLang): void`

This sets the value of Hanna `DEFAULT_LANG` variable globally. Use it at the
top of your application to match its locale.

The `DEFAULT_LANG` variable is NOT reactive, and does not trigger re-renders.

```ts
import {
  setDefaultLanguage,
  DEFAULT_LANG,
} from '@reykjavik/hanna-utils/i18n';

console.log(DEFAULT_LANG); // 'is' (Initial default language)

setDefaultLanguage('pl');
console.log(DEFAULT_LANG); // 'pl'
```

You can explicitly switch to using the library's initial `DEFAULT_LANG` by
passing `undefined` as an argument — like so:

```ts
setStyleServerUrl(undefined); // pushes the initial language to the stack
```

### `setDefaultLanguage.push()`

**Syntax:** `setDefaultLanguage.push(lang: HannaLang): void`

This function pushes a new language onto a simple stack. Use
`setDefaultLanguage.pop()` to revert back to the previous one.

Example:

```ts
console.log(DEFAULT_LANG); // 'pl' (the language set in previous example)

setDefaultLanguage.push('pl');
console.log(DEFAULT_LANG); // 'en'

setDefaultLanguage.pop(); // reset `DEFAULT_LANG` to previous value
console.log(DEFAULT_LANG); // 'pl'
```

## Social Media Sharing

Hanna-utils provides a small, easy to use suite of utilities to generate, GDPR
and privacy-friendly social-media sharing links.

```ts
import * from '@reykjavik/hanna-utils/shareButtonsUtils';
```

Until proper documentation is ready, see
[shareButtonsUtils.ts](https://github.com/rvk-utd/hanna/blob/main/modules/hanna-utils/src/shareButtonsUtils.ts)
(and
[ShareButtons.tsx](https://github.com/rvk-utd/hanna/blob/main/modules/hanna-react/src/ShareButtons.tsx)
for an example of how it's used in `hanna-react`).

## Branded types

### `ensurePosInt`

**Syntax:** `ensurePosInt(cand: unknown): PositiveInteger | undefined`

Checks if `cand` evaluates to a positive integer and, if so, returns a branded
`PositiveInteger` of equal value.

Returns `undefined` otherwise.

Examples:

- `1` → `1`
- `"1"` → `1`
- `0` → `undefined`
- `-1` → `undefined`
- `1.5` → `undefined`
- `"Infinity"` → `undefined`
- `"foo"` → `undefined`

## TypeScript helpers

### `notNully`

**Syntax:** `notNully(value: unknown): value is NonNullable<V>`

Simple type-guarding filter function that filters out `null`y values (`null`
and `undefined`) in a type-aware way.

```ts
import { notNully } from '@reykjavik/hanna-utils';

const mixed = ['hi', null, undefined, ''];
const strings: Array<string> = mixed.filter(notNully);
// ['hi', '']
```

### `notFalsy`

**Syntax:** `notFalsy(value: unknown): value is NonNullable<V>`

Simple type-guarding filter function that filters out "falsy" values (`""`,
`0`, `NaN`, false, `null` and `undefined`) in a type-aware way.

```ts
import { notFalsy } from '@reykjavik/hanna-utils';

const mixed = ['hi', null, undefined, '', 0, false, 'ho'] as const;
const strings: Array<'hi' | 'ho'> = mixed.filter(notFalsy);
// ['hi', 'ho']
```

### `ObjectKeys`, `ObjectEntries`, `ObjectFromEntries`

Nicer, more type-aware aliases for the native `Object.keys`, `Object.entries`
and `Object.fromEntries`.

```ts
import {
  ObjectKeys,
  ObjectEntries,
  ObjectFromEntries,
} from '@reykjavik/hanna-utils';
```

### Type `OpenRecord`

**Syntax:** `OpenRecord<Keys extends string, Values>`

A variant of `Record<string, T>` that warns if any `Keys` are missing when
it's declared.

It is useful for building enum objects to quickly validate JavaScript run-time
user inputs and applying default values and "alias" outdated keys.

```ts
import type { OpenRecord } from '@reykjavik/hanna-utils';

type SizeVariant = 'small' | 'large';
const sizes: OpenRecord<SizeVariant, number> = {
  // Required keys
  small: 12,
  large: 20,
  // Extra key
  normal: 16,
};

// ...

const sizeValue = sizes[props.size || 'normal'] || sizes.normal;
```

### Type `OpenStringMap`

**Syntax:** `OpenStringMap<Keys extends string, Values = Keys>`

A variant of `OpenRecord` for cases where you're mapping `Keys` to themselves.
It allows for shorter/simpler type signature. The second `Value` parameter is
unioned to `Keys`

```ts
import type { OpenStringMap } from '@reykjavik/hanna-utils';

type AlignVariant = 'left' | 'right';

const aligns: OpenStringMap<AlignVariant> = {
  // Required keys
  left: 'left',
  right: 'right',
  // Extra key
  default: 'left', // value must be of type AlignVariant
};

const aligns2: OpenStringMap<AlignVariant, ''> = {
  // Required keys
  left: 'left',
  right: 'right',
  // Extra key
  default: '', // '' is explicitly allowed by the type signature
};

// ...

const alignValue = aligns[props.align || 'default'] || aligns.default;
```

### Type `AllowKeys`

Return `A` with the unique keys of `B` as optionally `undefined`.

Example:

```ts
type A = { type: 'profit'; gain: number };
type B = { type: 'loss'; loss: number; panic: boolean };

type MyProps = AllowKeys<A, B>;
```

is equivalent to:

```ts
type MyProps = { type: 'profit'; gain: number; loss?: never; panic?: never };
```

The second type parameter can also be a union of strings. Thus, the above
example could be rewritten so:

```ts
type MyProps = AllowKeys<A, 'type' | 'loss' | 'panic'>;
```

NOTE: This type helper is used by `EitherObj<A,B,…>` type.

### Type `EitherObj`

Allow any one of its input types, but accept the keys from the other type(s)
as optionally `undefined`.

The `EitherObj` accepts between 2 and 4 type parameters.

Example with three inputs: `A`, `B` and `C`:

```ts
type A = { type: 'profit'; gain: number };
type B = { type: 'loss'; loss: number };
type C = { type: 'even'; panic: boolean };

type MyProps = EitherObj<A, B, C>;
```

is equivalent to:

```ts
type MyProps =
  | { type: 'profit'; gain: number; loss?: never; panic?: never };
  | { type: 'loss'; gain?: never; loss: number; panic?: never };
  | { type: 'even'; gain?: never; loss?: never; panic: boolean };
```

### Type `OmitDistributive`

A variant of `Omit` that distributes over unions.

See:
[TypeScript Playground](https://www.typescriptlang.org/play?ssl=14&ssc=1&pln=12&pc=1#code/PTAEEFQZwSwWwA4BsCmoBGBXALtg9gHagDuM2AFtHnGgMaHYoHagCGBAJm6ABRx5RsSAJ4B+AJSgOMKLQBO8GAVaMumAjEKh8UvCTKU5KKAkKwAbmnRHWAa1NLsUAFDZhCNACEc+AgAU5PAQoUABeUABvUHpmJmwALmhsBQIAcwBuUABfUAAyXmdQUAAfSNAjEzMYS1FEgDNWJCgUABoMG3s8R1rQdQ4UOqUULizCkrKK0wILFETkzBRM6xQ7B2ZEgCIoOA3xjbgOXdKNpFTd0fF05xAIUG9cLWJyGFpKTGaQijRWdDxLUAQgWCzjq6lo2E0RHuvh4gKCUES0MIAXhkgiYyM2EwciIGwAVlAAB4bK6jZyudxoADycDIABEZMkYFgIZYADwAFTaAGlQChCYxOCEUR45G5uShhAA+MKgDl8gVMDghdjCUCiUA0sicnkyxIEFCWORXNweUB+F62BmCBQs6ooHWgXn8wXK81AlBi4QS6Wy+UupUqghqjVa7CO7l60AGo1Xa5gDnPEIs3ygGTlYxTWDoVCgOp4ORppygPDEIgxQXYNpQPQcQgAchYqBYqsIaAQrCgQbVShBYIhWiRBAA6gY8DgAMIMOKwoEIzW07DWpl29lDkVQNobCtxDZStFjG7Dgu2FUhfkecHDDEoLE4u4+Qg8KKgAB077hwTaO-WoA25BQJAkDwc5LmcMkbgAZVYGhomnZhUC7DBHyId4lFSAEkFYJRQAAAzDNkpVwvsCHBSEHweEcx0neDsAAJlneFEgI9c5y3H9sD3A8ihuRNqUXQjQHITs8yw3AmGGHgJG0ADtEpNpljsdCZLQdRIRvO8oRQ58infV9P03ODYl-f9AOA0DSXJIA)

### Type `PickDistributive`

A variant of `Pick` that distributes over unions.

See:
[TypeScript Playground](https://www.typescriptlang.org/play?ssl=14&ssc=1&pln=12&pc=1#code/PTAEEFQZwSwWwA4BsCmoBGBXALtg9gHagDuM2AFtHnGgMaHYoHagCGBAJm6ABRx5RsSAJ4B+AJSgOMKLQBO8GAVaMumAjEKh8UvCTKU5KKAkKwAbmnRHWAa1NLsUAFDZhCNACEc+AgAU5PAQoUABeUABvUHpmJmwALmhsBQIAcwBuUABfUAAyXmdQUAAfSNAjEzMYS1FEgDNWJCgUABoMG3s8R1rQdQ4UOqUULizCkrKK0wILFETkzBRM6xQ7B2ZEgCIoOA3xjbgOXdKNpFTd0fF05xAIUG9cLWJyGFpKTGaQijRWdDxLUAQgWCzjq6lo2E0RHuvh4gKCUES0MIAXhkgiYyM2EwciIGwAVlAAB4bK6jZyudxoADycDIABEZMkYFgIZYADwAFTaAGlQChCYxOCEUR45G5uShhAA+MKgDl8gVMDghdjCUCiUA0sicnkyxIEFCWORXNweUB+F62BmCBQs6ooHWgXn8wXK81AlBi4QS6Wy+UupUqghqjVa7CO7l60AGo1Xa5gDnPEIs3ygGTlYxTWDoVCgOp4ORppygPDEIgxQXYNpQPQcQgAchYqBYqsIaAQrCgQbVShBYIhWiRBAA6gY8DgAMIMOKwoEIzW07DWpl29lDkVQNobCtxDZStFjG7Dgu2FUhfkecHDDEoLE4u4+Qg8KKgAB077hwTaO-WoA25BQJAkDwc5LmcMkbgAZVYGhomnZhUC7DBHyId4lFSAEkFYJRQAAAzDNkpVwvsCHBSEHweEcx0neDsAAJlneFEgI9c5y3H9sD3A8ihuRNqUXQjQHITs8yw3AmGGHgJG0ADtEpNpljsdCZLQdRIRvO8oRQ58infV9P03ODYl-f9AOA0DSXJIA)

### Type `RequireExplicitUndefined`

Converts a type so that all optional keys are required and must be explicitly
set to `undefined`.

```ts
type Foo = { a: string; b?: number };

type Bar = RequireExplicitUndefined<Foo>;
```

Is equivalent to:

```ts
type Bar = { a: string; b: number | undefined };
```

### Type Testing Helpers

#### Type `Expect<T>`

Expects `T` to be `true`

```ts
import type { Expect } from '@reykjavik/hanna-utils';

type OK = Expect<true>;
type Fails = Expect<false>; // Type Error
type Fails = Expect<'Error message'>; // Type Error
```

#### Type `NotExpect<T>`

Expects `T` to be `false` or a `string` (as returned by the `Equals`,
`Extends` and `NotExtends` helpers).

```ts
import type { NotExpect } from '@reykjavik/hanna-utils';

type OK1 = NotExpect<false>;
type OK2 = NotExpect<'Error message'>;
type Fails = NotExpect<true>; // Type Error
```

#### Type `Equals<A, B>`

Returns true if types `A` and `B` are equal (and neither is `any`)

```ts
import type { Equals, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<Equals<'same', 'same'>>;
type Fails = Expect<Equals<'not', 'same'>>; // Type Error
```

#### Type `Extends<A, B>`

Returns true if type `A` extends type `B` (and neither is `any`)

```ts
import type { Extends, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<Extends<'some', string>>;
type Fails = Expect<Extends<string, 'some'>>; // Type Error
```

#### Type `NotExtends<A, B>`

Returns true if type `A` does **NOT** extend type `B` (and neither is `any`)

```ts
import type { NotExtends, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<NotExtends<string, 'some'>>;
type Fails = Expect<NotExtends<'some', string>>; // Type Error
type FailsAlso = Expect<NotExtends<'same', 'same'>>; // Type Error
```

## Changelog

See
[CHANGELOG.md](https://github.com/rvk-utd/hanna/blob/main/modules/hanna-utils/CHANGELOG.md)

---
_Source: https://npm.io/package/@reykjavik/hanna-utils · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
