Middle truncation with Pretext

Pixel-perfect middle truncation using the Pretext library for accurate text measurement, no DOM reflow required.

react #ui
const STRINGS = [
  {
    label: "File path",
    text: "/Users/chase/Repositories/my-project/src/components/navigation/TopBar.tsx",
  },
  {
    label: "URL",
    text: "https://github.com/chenglou/pretext/blob/main/src/layout.ts?tab=readme-ov-file#middle-truncation",
  },
  {
    label: "Git commit",
    text: "feat(auth): implement OAuth2 PKCE flow with refresh token rotation and silent renewal",
  },
  {
    label: "Email",
    text: "very.long.email.address.with.many.parts@some-extremely-long-domain-name.organization.com",
  },
]

const FONT = "13px 'SF Mono', 'Fira Code', 'Cascadia Code', monospace"
const LINE_HEIGHT = 20

const CDN_URL =
  "https://cdn.jsdelivr.net/npm/@chenglou/[email protected]/dist/layout.js"

function middleTruncate(text, maxWidth, { prepare, layout }) {
  function fits(str) {
    return layout(prepare(str, FONT), maxWidth, LINE_HEIGHT).lineCount === 1
  }

  if (fits(text)) return { prefix: text, suffix: null }

  const ellipsis = "…"
  if (!fits(ellipsis)) return { prefix: "", suffix: null }

  let lo = 0,
    hi = text.length
  while (lo < hi) {
    const mid = Math.floor((lo + hi + 1) / 2)
    const half = Math.floor(mid / 2)
    const candidate =
      text.slice(0, half) + ellipsis + text.slice(text.length - (mid - half))
    if (fits(candidate)) lo = mid
    else hi = mid - 1
  }

  const half = Math.floor(lo / 2)
  return {
    prefix: text.slice(0, half).trimEnd(),
    suffix:
      lo - half > 0 ? text.slice(text.length - (lo - half)).trimStart() : "",
  }
}

const styles = (
  <style>{`
    * { box-sizing: border-box; }

    .body {
      margin: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }

    .card {
      overflow: hidden;
      width: 100%;
      background: white;
    }

    .demo-area {
      padding: 1.25rem;
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
    }

    .slider-row {
      width: 100%;
      height: 10px;
      display: flex;
      align-items: center;
      margin-bottom: 0.25rem;
    }

    .track {
      width: 100%;
      height: 2px;
      background: #e7e5e4;
      border-radius: 1px;
      position: relative;
    }

    .fill {
      position: absolute;
      left: 0;
      top: 0;
      height: 100%;
      background: #a8a29e;
      border-radius: 1px;
      pointer-events: none;
    }

    .thumb {
      position: absolute;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 14px;
      height: 14px;
      border-radius: 50%;
      background: white;
      border: 2px solid #78716c;
      cursor: ew-resize;
      transition: border-color 0.1s, box-shadow 0.1s;
    }

    .thumb:hover, .thumb.dragging {
      border-color: #44403c;
      box-shadow: 0 0 0 3px rgba(68,64,60,0.12);
    }

    .item {
      padding: 0.5rem 0;
      border-bottom: 1px solid #f5f5f4;
      display: flex;
      flex-direction: column;
      gap: 0.2rem;
    }

    .item:last-child { border-bottom: none; padding-bottom: 0; }
    .item:first-child { padding-top: 0; }

    .item-label {
      font-size: 10px;
      font-weight: 500;
      color: #d6d3d1;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    .item-text {
      font-size: 13px;
      color: #44403c;
      white-space: nowrap;
      overflow: hidden;
      font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
      line-height: 1.5;
    }
  `}</style>
)

function MiddleTruncatedText({ text, maxWidth }) {
  const [pretext, setPretext] = React.useState(null)

  React.useEffect(() => {
    import(/* @vite-ignore */ CDN_URL).then(setPretext)
  }, [])

  const result =
    pretext && maxWidth > 0 ? middleTruncate(text, maxWidth, pretext) : null

  if (!result || result.suffix === null) return result ? result.prefix : text
  return (
    <>
      {result.prefix}…{result.suffix}
    </>
  )
}

export default function Demo() {
  const trackRef = React.useRef(null)
  const [ratio, setRatio] = React.useState(0.65)
  const [maxWidth, setMaxWidth] = React.useState(0)
  const [dragging, setDragging] = React.useState(false)

  React.useEffect(() => {
    function updateMaxWidth() {
      if (trackRef.current) {
        setMaxWidth(trackRef.current.getBoundingClientRect().width * ratio)
      }
    }

    updateMaxWidth()

    window.addEventListener("resize", updateMaxWidth)
    return () => window.removeEventListener("resize", updateMaxWidth)
  }, [ratio])

  React.useEffect(() => {
    if (!dragging) return

    function onMove(e) {
      const clientX = e.touches ? e.touches[0].clientX : e.clientX
      const rect = trackRef.current.getBoundingClientRect()
      setRatio(Math.min(1, Math.max(0.1, (clientX - rect.left) / rect.width)))
    }

    function onUp() {
      setDragging(false)
    }

    window.addEventListener("mousemove", onMove)
    window.addEventListener("mouseup", onUp)
    window.addEventListener("touchmove", onMove)
    window.addEventListener("touchend", onUp)

    return () => {
      window.removeEventListener("mousemove", onMove)
      window.removeEventListener("mouseup", onUp)
      window.removeEventListener("touchmove", onMove)
      window.removeEventListener("touchend", onUp)
    }
  }, [dragging])

  return (
    <div>
      {styles}

      <div className="body">
        <div className="card">
          <div className="demo-area">
            <div className="slider-row">
              <div className="track" ref={trackRef}>
                <div className="fill" style={{ width: `${ratio * 100}%` }} />
                <div
                  className={`thumb${dragging ? " dragging" : ""}`}
                  style={{ left: `${ratio * 100}%` }}
                  onMouseDown={(e) => {
                    setDragging(true)
                    e.preventDefault()
                  }}
                  onTouchStart={(e) => {
                    setDragging(true)
                    e.preventDefault()
                  }}
                />
              </div>
            </div>

            <div style={{ overflow: "hidden", maxWidth }}>
              {STRINGS.map(({ label, text }) => (
                <div className="item" key={label}>
                  <div className="item-label">{label}</div>
                  <div className="item-text">
                    <MiddleTruncatedText text={text} maxWidth={maxWidth} />
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export const meta = {
  title: "Middle truncation with Pretext",
  description:
    "Pixel-perfect middle truncation using the [Pretext](https://github.com/chenglou/pretext) library for accurate text measurement, no DOM reflow required.",
  tags: ["ui"],
}