npm.io
1.5.1 • Published 1 month ago

vue-clamp

Licence
MIT
Version
1.5.1
Deps
0
Size
84 kB
Vulns
0
Weekly
0
Stars
811

vue-clamp

GitHub stars npmx version

Clamping primitives for Vue 3. vue-clamp measures real browser layout so text, inline content, and wrapped items fit the space they are actually rendered into.

Install

pnpm add vue-clamp

vue-clamp has a peer dependency on Vue ^3.5.0. Install Vue too if your project does not already depend on it.

Components

Component Use it for
<LineClamp> Multiline plain text with optional start, middle, or end ellipsis.
<RichLineClamp> Trusted inline HTML that should keep formatting while clamping.
<InlineClamp> One-line strings with fixed affixes and configurable ellipsis.
<WrapClamp> Wrapped atomic items such as tags, filters, chips, and breadcrumbs.

The package has named exports only:

import { InlineClamp, LineClamp, RichLineClamp, WrapClamp } from "vue-clamp";

Quick start

<script setup lang="ts">
import { ref } from "vue";
import { LineClamp } from "vue-clamp";

const expanded = ref(false);
const text = "Ship review-ready notes with browser-fit text truncation and keep the toggle inline.";
</script>

<template>
  <LineClamp v-model:expanded="expanded" :text="text" :max-lines="2">
    <template #after="{ clamped, expanded, toggle }">
      <button v-if="clamped" type="button" @click="toggle">
        {{ expanded ? "Less" : "More" }}
      </button>
    </template>
  </LineClamp>
</template>

Plain text

Use <LineClamp> when the source is plain text and the browser should decide line wrapping.

<LineClamp :text="title" :max-lines="2" location="middle" boundary="word" ellipsis="..." />

Useful props:

  • text: source text. Defaults to "".
  • max-lines: maximum visible line count.
  • max-height: maximum visible height. Numbers are treated as pixels.
  • ellipsis: string inserted into clamped output. Defaults to .
  • location: start, middle, end, or a number from 0 to 1. Defaults to end.
  • boundary: grapheme or word. Defaults to grapheme. Use word to avoid partial words; single-line word-boundary clamping uses the measured JS path instead of native text-overflow.
  • expanded: show the full text. Supports v-model:expanded.

before and after slots render inline with the text and receive { expand, collapse, toggle, clamped, expanded }.

Trusted rich text

Use <RichLineClamp> for trusted or already-sanitized inline markup.

<RichLineClamp v-model:expanded="expanded" :html="html" :max-lines="2">
  <template #after="{ clamped, expanded, toggle }">
    <button v-if="clamped" type="button" @click="toggle">
      {{ expanded ? "Less" : "More" }}
    </button>
  </template>
</RichLineClamp>

Rich clamping is intentionally scoped:

  • html is rendered as HTML. Sanitize untrusted input before passing it in.
  • Rich content clamps from the end only.
  • boundary can be grapheme or word. Defaults to grapheme; word avoids partial words inside supported text runs.
  • Inline elements can participate when they can be cloned back into the DOM and stay in inline flow.
  • Leaf elements without light DOM content are treated as atomic inline units, including custom elements.
  • br, wbr, img, and outer svg elements have explicit handling when they stay in inline flow.
  • Inline rich images must have deterministic rendered dimensions before loading, set by attributes or CSS.
  • Unsupported markup falls back to the original HTML unchanged.

before and after slots receive the same control props as <LineClamp>.

Single-line strings

Use <InlineClamp> for one-line text where part of the string should remain fixed while the body shrinks. The location prop controls how body text is kept around the ellipsis; in tight spaces, the body can become just the ellipsis.

<script setup lang="ts">
import { InlineClamp } from "vue-clamp";

const file = "summer-campaign-panorama-final.jpeg";

function splitFileName(text: string) {
  const extension = text.match(/\.[^.]+$/)?.[0];

  return extension ? { body: text.slice(0, -extension.length), end: extension } : { body: text };
}
</script>

<template>
  <InlineClamp :text="file" :split="splitFileName" location="middle" />
</template>

Useful props:

  • text: required source string.
  • ellipsis: string inserted into the rewritten body. Defaults to .
  • location: how body text is kept around the ellipsis: start, middle, end, or a number from 0 to 1. Defaults to end.
  • boundary: grapheme or word. Defaults to grapheme; word avoids partial words in the rewritten body, falling back to grapheme cuts when no whole word can fit.
  • split: optional function returning { start?: string, body: string, end?: string }.
  • as: root tag name. Defaults to span.

<InlineClamp> has no slots or expansion API.

Wrapped items

Use <WrapClamp> when each item must stay whole.

<script setup lang="ts">
import { ref } from "vue";
import { WrapClamp } from "vue-clamp";

const expanded = ref(false);
const labels = [
  { id: "perf", label: "Performance" },
  { id: "a11y", label: "Accessibility" },
  { id: "docs", label: "Docs" },
  { id: "qa", label: "Needs QA" },
];
</script>

<template>
  <WrapClamp v-model:expanded="expanded" :items="labels" item-key="id" :max-lines="2">
    <template #item="{ item }">
      <span class="tag">{{ item.label }}</span>
    </template>

    <template #after="{ clamped, expanded, hiddenItems, toggle }">
      <button v-if="expanded || clamped" type="button" @click="toggle">
        {{ expanded ? "Less" : `+${hiddenItems.length} more` }}
      </button>
    </template>
  </WrapClamp>
</template>

Useful props:

  • items: ordered source items. Defaults to [].
  • item-key: string field name or (item, index) => string | number key resolver.
  • max-lines: maximum visible wrapped line count.
  • max-height: maximum visible height. Numbers are treated as pixels.
  • expanded: show the full item list. Supports v-model:expanded.

The required item slot receives { item, index }. The before and after slots receive { expand, collapse, toggle, clamped, expanded, hiddenItems }.

Events and instance methods

<LineClamp>, <RichLineClamp>, and <WrapClamp> emit:

  • clampchange: (clamped: boolean), emitted when truncation turns on or off.
  • update:expanded: (expanded: boolean), emitted for v-model:expanded.

They also expose expand(), collapse(), toggle(), clamped, and expanded through a template ref.

Styling hooks

Stable styling hooks use data-part attributes:

Component Parts
<LineClamp> root, content, before, body, after
<RichLineClamp> root, content, before, body, after
<InlineClamp> root, start, body, end
<WrapClamp> root, content, before, item, after

Do not rely on internal DOM nesting as a styling contract.

Notes

  • 1.x is the Vue 3 line. See the migration guide when upgrading from 0.x.
  • ResizeObserver is part of the browser baseline.

Keywords