@vritti/quantum-ui 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,5 +3,6 @@ export declare function parseSlug(segment: string): {
3
3
  slug: string;
4
4
  id: string;
5
5
  } | null;
6
+ export declare function unslugify(slug: string): string;
6
7
  export declare function slugify(text: string): string;
7
8
  //# sourceMappingURL=slug.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../lib/utils/slug.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE1D;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI9E;AAGD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C"}
1
+ {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../lib/utils/slug.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE1D;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI9E;AAID,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C;AAGD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C"}
package/dist/useTimer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
2
  import { w as we } from './index4.js';
3
3
  import { useParams } from 'react-router-dom';
4
- import { parseSlug } from './utils/slug.js';
4
+ import { parseSlug, unslugify } from './utils/slug.js';
5
5
  import { g as getConfig } from './index8.js';
6
6
 
7
7
  const DEFAULT_MIN_LENGTH = 3;
@@ -83,7 +83,7 @@ function useBarcodeScanner({
83
83
  function parseParam(value) {
84
84
  const v = value ?? "";
85
85
  const parsed = parseSlug(v);
86
- return { slug: v, name: parsed?.slug ?? "", id: parsed?.id ?? v };
86
+ return { slug: v, name: unslugify(parsed?.slug ?? ""), id: parsed?.id ?? v };
87
87
  }
88
88
  function useSlugParams(...params) {
89
89
  const routeParams = useParams();
@@ -1 +1 @@
1
- {"version":3,"file":"useTimer.js","sources":["../lib/hooks/useBarcodeScanner.ts","../lib/hooks/useSlugParams.ts","../lib/hooks/useSSE.ts","../lib/hooks/useTimer.ts"],"sourcesContent":["import type { UseMutationResult } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport { useHotkeys } from '../utils/hotkeys';\n\nconst DEFAULT_MIN_LENGTH = 3;\nconst DEFAULT_MAX_INTERVAL_MS = 50;\nconst MAX_SCAN_DURATION_MS = 500;\n\nexport interface UseBarcodeScannerOptions<TData, TError, TVariables> {\n mutation: UseMutationResult<TData, TError, TVariables>;\n toVariables: (code: string) => TVariables;\n enabled?: boolean;\n toggleShortcut?: string;\n exitShortcut?: string;\n minLength?: number;\n maxIntervalMs?: number;\n}\n\nexport interface UseBarcodeScannerResult {\n isActive: boolean;\n isPending: boolean;\n enable: () => void;\n disable: () => void;\n toggle: () => void;\n toggleShortcut: string;\n exitShortcut: string;\n}\n\nexport function useBarcodeScanner<TData, TError, TVariables>({\n mutation,\n toVariables,\n enabled = true,\n toggleShortcut = 'alt+s',\n exitShortcut = 'escape',\n minLength = DEFAULT_MIN_LENGTH,\n maxIntervalMs = DEFAULT_MAX_INTERVAL_MS,\n}: UseBarcodeScannerOptions<TData, TError, TVariables>): UseBarcodeScannerResult {\n const [isActive, setIsActive] = useState(false);\n\n const enable = () => setIsActive(true);\n const disable = () => setIsActive(false);\n const toggle = () => setIsActive((v) => !v);\n\n // Auto-disable when the gating condition turns false\n useEffect(() => {\n if (!enabled) setIsActive(false);\n }, [enabled]);\n\n // Cross-platform toggle shortcut\n useHotkeys(toggleShortcut, toggle, { enabled: enabled });\n // Escape exits only when active\n useHotkeys(exitShortcut, disable, { enabled: isActive });\n\n // Keyboard-wedge burst detector\n const bufferRef = useRef('');\n const lastKeyTimeRef = useRef(0);\n const scanStartTimeRef = useRef(0);\n const mutateRef = useRef(mutation.mutate);\n mutateRef.current = mutation.mutate;\n const toVariablesRef = useRef(toVariables);\n toVariablesRef.current = toVariables;\n\n useEffect(() => {\n if (!isActive || !enabled) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const target = event.target as Element | null;\n if (\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n target?.hasAttribute('contenteditable')\n )\n return;\n\n const now = Date.now();\n const gap = now - lastKeyTimeRef.current;\n\n if (event.key === 'Enter') {\n const elapsed = now - scanStartTimeRef.current;\n const code = bufferRef.current;\n bufferRef.current = '';\n lastKeyTimeRef.current = 0;\n scanStartTimeRef.current = 0;\n if (code.length >= minLength && elapsed <= MAX_SCAN_DURATION_MS) {\n event.preventDefault();\n mutateRef.current(toVariablesRef.current(code));\n }\n return;\n }\n\n if (event.key.length !== 1) return;\n\n if (gap > maxIntervalMs && bufferRef.current.length > 0) {\n bufferRef.current = '';\n scanStartTimeRef.current = 0;\n }\n\n if (bufferRef.current.length === 0) scanStartTimeRef.current = now;\n\n bufferRef.current += event.key;\n lastKeyTimeRef.current = now;\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n bufferRef.current = '';\n lastKeyTimeRef.current = 0;\n scanStartTimeRef.current = 0;\n };\n }, [isActive, enabled, minLength, maxIntervalMs]);\n\n return {\n isActive,\n isPending: mutation.isPending,\n enable,\n disable,\n toggle,\n toggleShortcut,\n exitShortcut,\n };\n}\n","import { useParams } from 'react-router-dom';\nimport { parseSlug } from '../utils/slug';\n\nexport interface SlugParam {\n slug: string;\n name: string;\n id: string;\n}\n\ntype SlugParamMap<T extends string[]> = { [K in T[number]]: SlugParam };\n\nfunction parseParam(value: string | undefined): SlugParam {\n const v = value ?? '';\n const parsed = parseSlug(v);\n return { slug: v, name: parsed?.slug ?? '', id: parsed?.id ?? v };\n}\n\n// Single param — returns SlugParam directly for clean destructuring\nexport function useSlugParams<T extends string>(param: T): SlugParam;\n// Multiple params — returns { paramA: SlugParam; paramB: SlugParam }\nexport function useSlugParams<T extends string[]>(...params: T): SlugParamMap<T>;\nexport function useSlugParams(...params: string[]): SlugParam | SlugParamMap<string[]> {\n const routeParams = useParams();\n\n if (params.length === 1) {\n return parseParam(routeParams[params[0]]);\n }\n\n const result: Record<string, SlugParam> = {};\n for (const param of params) {\n result[param] = parseParam(routeParams[param]);\n }\n return result;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { getConfig } from '../config';\n\nexport interface UseSSEOptions<EventMap extends Record<string, unknown> = Record<string, unknown>> {\n path: string;\n events: (keyof EventMap & string)[];\n enabled?: boolean;\n autoReconnect?: boolean;\n withCredentials?: boolean;\n onError?: (event: Event) => void;\n onOpen?: () => void;\n}\n\nexport interface UseSSEReturn<EventMap extends Record<string, unknown>> {\n eventType: keyof EventMap | null;\n data: EventMap[keyof EventMap] | null;\n eventTypes: { [K in keyof EventMap as Uppercase<K & string>]: K };\n isConnected: boolean;\n error: string | null;\n disconnect: () => void;\n}\n\n// Generic Server-Sent Events hook that returns the last event as `eventType` + `data`.\nexport function useSSE<EventMap extends Record<string, unknown> = Record<string, unknown>>(\n options: UseSSEOptions<EventMap>,\n): UseSSEReturn<EventMap> {\n const { path, events: eventNames = [], enabled = true, withCredentials = true } = options;\n\n const eventSourceRef = useRef<EventSource | null>(null);\n const [eventType, setEventType] = useState<keyof EventMap | null>(null);\n const [data, setData] = useState<EventMap[keyof EventMap] | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Store options in a ref for stable access inside event handlers\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Stable serialized key for the events array\n const eventsKey = eventNames.join(',');\n\n const disconnect = useCallback(() => {\n if (eventSourceRef.current) {\n eventSourceRef.current.close();\n eventSourceRef.current = null;\n setIsConnected(false);\n }\n }, []);\n\n useEffect(() => {\n if (!enabled) {\n disconnect();\n setEventType(null);\n setData(null);\n return;\n }\n\n // Build full URL from quantum-ui config, letting the app attach whatever an axios request would carry\n // in a header — EventSource cannot set headers, so that context has to ride the query string.\n const config = getConfig();\n const baseUrl = `${config.axios.baseURL}${path}`;\n const sseUrl = config.sse.onRequest?.(baseUrl) ?? baseUrl;\n\n const eventSource = new EventSource(sseUrl, { withCredentials });\n eventSourceRef.current = eventSource;\n\n eventSource.onopen = () => {\n setIsConnected(true);\n setError(null);\n optionsRef.current.onOpen?.();\n };\n\n eventSource.onerror = (event) => {\n setIsConnected(false);\n\n // When autoReconnect is false, close on any error to prevent browser reconnect\n if (!optionsRef.current.autoReconnect && optionsRef.current.autoReconnect !== undefined) {\n eventSource.close();\n eventSourceRef.current = null;\n return;\n }\n\n if (eventSource.readyState === EventSource.CLOSED) {\n setError('Connection lost.');\n optionsRef.current.onError?.(event);\n }\n };\n\n // Register one addEventListener per event name\n const handlers: { name: string; handler: EventListener }[] = [];\n const names = eventsKey.split(',').filter(Boolean);\n\n for (const name of names) {\n const handler = (event: Event) => {\n try {\n const parsed = JSON.parse((event as MessageEvent).data as string);\n setEventType(name as keyof EventMap);\n setData(parsed);\n } catch (e) {\n console.error(`Failed to parse SSE event '${name}':`, e);\n }\n };\n\n eventSource.addEventListener(name, handler);\n handlers.push({ name, handler });\n }\n\n return () => {\n for (const { name, handler } of handlers) {\n eventSource.removeEventListener(name, handler);\n }\n disconnect();\n };\n }, [enabled, path, eventsKey, withCredentials, disconnect]);\n\n // Build UPPER_CASE enum from event names: { INITIATED: 'initiated', VERIFIED: 'verified', ... }\n type EventTypesMap = { [K in keyof EventMap as Uppercase<K & string>]: K };\n const eventTypes = useMemo(() => {\n const map = {} as EventTypesMap;\n for (const name of eventsKey.split(',').filter(Boolean)) {\n (map as Record<string, string>)[name.toUpperCase()] = name;\n }\n return map;\n }, [eventsKey]);\n\n return { eventType, data, eventTypes, isConnected, error, disconnect };\n}\n","import { useEffect, useState } from 'react';\n\nexport interface TimerHandle {\n timer: number;\n startTimer: (seconds?: number) => void;\n}\n\n// Generic countdown timer — decrements every second until 0\nexport function useTimer(initial = 0): TimerHandle {\n const [timer, setTimer] = useState(initial);\n\n useEffect(() => {\n if (timer > 0) {\n const t = setTimeout(() => setTimer(timer - 1), 1000);\n return () => clearTimeout(t);\n }\n }, [timer]);\n\n return { timer, startTimer: (seconds = 45) => setTimer(seconds) };\n}\n"],"names":["useHotkeys"],"mappings":"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"}
1
+ {"version":3,"file":"useTimer.js","sources":["../lib/hooks/useBarcodeScanner.ts","../lib/hooks/useSlugParams.ts","../lib/hooks/useSSE.ts","../lib/hooks/useTimer.ts"],"sourcesContent":["import type { UseMutationResult } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport { useHotkeys } from '../utils/hotkeys';\n\nconst DEFAULT_MIN_LENGTH = 3;\nconst DEFAULT_MAX_INTERVAL_MS = 50;\nconst MAX_SCAN_DURATION_MS = 500;\n\nexport interface UseBarcodeScannerOptions<TData, TError, TVariables> {\n mutation: UseMutationResult<TData, TError, TVariables>;\n toVariables: (code: string) => TVariables;\n enabled?: boolean;\n toggleShortcut?: string;\n exitShortcut?: string;\n minLength?: number;\n maxIntervalMs?: number;\n}\n\nexport interface UseBarcodeScannerResult {\n isActive: boolean;\n isPending: boolean;\n enable: () => void;\n disable: () => void;\n toggle: () => void;\n toggleShortcut: string;\n exitShortcut: string;\n}\n\nexport function useBarcodeScanner<TData, TError, TVariables>({\n mutation,\n toVariables,\n enabled = true,\n toggleShortcut = 'alt+s',\n exitShortcut = 'escape',\n minLength = DEFAULT_MIN_LENGTH,\n maxIntervalMs = DEFAULT_MAX_INTERVAL_MS,\n}: UseBarcodeScannerOptions<TData, TError, TVariables>): UseBarcodeScannerResult {\n const [isActive, setIsActive] = useState(false);\n\n const enable = () => setIsActive(true);\n const disable = () => setIsActive(false);\n const toggle = () => setIsActive((v) => !v);\n\n // Auto-disable when the gating condition turns false\n useEffect(() => {\n if (!enabled) setIsActive(false);\n }, [enabled]);\n\n // Cross-platform toggle shortcut\n useHotkeys(toggleShortcut, toggle, { enabled: enabled });\n // Escape exits only when active\n useHotkeys(exitShortcut, disable, { enabled: isActive });\n\n // Keyboard-wedge burst detector\n const bufferRef = useRef('');\n const lastKeyTimeRef = useRef(0);\n const scanStartTimeRef = useRef(0);\n const mutateRef = useRef(mutation.mutate);\n mutateRef.current = mutation.mutate;\n const toVariablesRef = useRef(toVariables);\n toVariablesRef.current = toVariables;\n\n useEffect(() => {\n if (!isActive || !enabled) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const target = event.target as Element | null;\n if (\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n target?.hasAttribute('contenteditable')\n )\n return;\n\n const now = Date.now();\n const gap = now - lastKeyTimeRef.current;\n\n if (event.key === 'Enter') {\n const elapsed = now - scanStartTimeRef.current;\n const code = bufferRef.current;\n bufferRef.current = '';\n lastKeyTimeRef.current = 0;\n scanStartTimeRef.current = 0;\n if (code.length >= minLength && elapsed <= MAX_SCAN_DURATION_MS) {\n event.preventDefault();\n mutateRef.current(toVariablesRef.current(code));\n }\n return;\n }\n\n if (event.key.length !== 1) return;\n\n if (gap > maxIntervalMs && bufferRef.current.length > 0) {\n bufferRef.current = '';\n scanStartTimeRef.current = 0;\n }\n\n if (bufferRef.current.length === 0) scanStartTimeRef.current = now;\n\n bufferRef.current += event.key;\n lastKeyTimeRef.current = now;\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n bufferRef.current = '';\n lastKeyTimeRef.current = 0;\n scanStartTimeRef.current = 0;\n };\n }, [isActive, enabled, minLength, maxIntervalMs]);\n\n return {\n isActive,\n isPending: mutation.isPending,\n enable,\n disable,\n toggle,\n toggleShortcut,\n exitShortcut,\n };\n}\n","import { useParams } from 'react-router-dom';\nimport { parseSlug, unslugify } from '../utils/slug';\n\nexport interface SlugParam {\n slug: string;\n name: string;\n id: string;\n}\n\ntype SlugParamMap<T extends string[]> = { [K in T[number]]: SlugParam };\n\nfunction parseParam(value: string | undefined): SlugParam {\n const v = value ?? '';\n const parsed = parseSlug(v);\n return { slug: v, name: unslugify(parsed?.slug ?? ''), id: parsed?.id ?? v };\n}\n\n// Single param — returns SlugParam directly for clean destructuring\nexport function useSlugParams<T extends string>(param: T): SlugParam;\n// Multiple params — returns { paramA: SlugParam; paramB: SlugParam }\nexport function useSlugParams<T extends string[]>(...params: T): SlugParamMap<T>;\nexport function useSlugParams(...params: string[]): SlugParam | SlugParamMap<string[]> {\n const routeParams = useParams();\n\n if (params.length === 1) {\n return parseParam(routeParams[params[0]]);\n }\n\n const result: Record<string, SlugParam> = {};\n for (const param of params) {\n result[param] = parseParam(routeParams[param]);\n }\n return result;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { getConfig } from '../config';\n\nexport interface UseSSEOptions<EventMap extends Record<string, unknown> = Record<string, unknown>> {\n path: string;\n events: (keyof EventMap & string)[];\n enabled?: boolean;\n autoReconnect?: boolean;\n withCredentials?: boolean;\n onError?: (event: Event) => void;\n onOpen?: () => void;\n}\n\nexport interface UseSSEReturn<EventMap extends Record<string, unknown>> {\n eventType: keyof EventMap | null;\n data: EventMap[keyof EventMap] | null;\n eventTypes: { [K in keyof EventMap as Uppercase<K & string>]: K };\n isConnected: boolean;\n error: string | null;\n disconnect: () => void;\n}\n\n// Generic Server-Sent Events hook that returns the last event as `eventType` + `data`.\nexport function useSSE<EventMap extends Record<string, unknown> = Record<string, unknown>>(\n options: UseSSEOptions<EventMap>,\n): UseSSEReturn<EventMap> {\n const { path, events: eventNames = [], enabled = true, withCredentials = true } = options;\n\n const eventSourceRef = useRef<EventSource | null>(null);\n const [eventType, setEventType] = useState<keyof EventMap | null>(null);\n const [data, setData] = useState<EventMap[keyof EventMap] | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Store options in a ref for stable access inside event handlers\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Stable serialized key for the events array\n const eventsKey = eventNames.join(',');\n\n const disconnect = useCallback(() => {\n if (eventSourceRef.current) {\n eventSourceRef.current.close();\n eventSourceRef.current = null;\n setIsConnected(false);\n }\n }, []);\n\n useEffect(() => {\n if (!enabled) {\n disconnect();\n setEventType(null);\n setData(null);\n return;\n }\n\n // Build full URL from quantum-ui config, letting the app attach whatever an axios request would carry\n // in a header — EventSource cannot set headers, so that context has to ride the query string.\n const config = getConfig();\n const baseUrl = `${config.axios.baseURL}${path}`;\n const sseUrl = config.sse.onRequest?.(baseUrl) ?? baseUrl;\n\n const eventSource = new EventSource(sseUrl, { withCredentials });\n eventSourceRef.current = eventSource;\n\n eventSource.onopen = () => {\n setIsConnected(true);\n setError(null);\n optionsRef.current.onOpen?.();\n };\n\n eventSource.onerror = (event) => {\n setIsConnected(false);\n\n // When autoReconnect is false, close on any error to prevent browser reconnect\n if (!optionsRef.current.autoReconnect && optionsRef.current.autoReconnect !== undefined) {\n eventSource.close();\n eventSourceRef.current = null;\n return;\n }\n\n if (eventSource.readyState === EventSource.CLOSED) {\n setError('Connection lost.');\n optionsRef.current.onError?.(event);\n }\n };\n\n // Register one addEventListener per event name\n const handlers: { name: string; handler: EventListener }[] = [];\n const names = eventsKey.split(',').filter(Boolean);\n\n for (const name of names) {\n const handler = (event: Event) => {\n try {\n const parsed = JSON.parse((event as MessageEvent).data as string);\n setEventType(name as keyof EventMap);\n setData(parsed);\n } catch (e) {\n console.error(`Failed to parse SSE event '${name}':`, e);\n }\n };\n\n eventSource.addEventListener(name, handler);\n handlers.push({ name, handler });\n }\n\n return () => {\n for (const { name, handler } of handlers) {\n eventSource.removeEventListener(name, handler);\n }\n disconnect();\n };\n }, [enabled, path, eventsKey, withCredentials, disconnect]);\n\n // Build UPPER_CASE enum from event names: { INITIATED: 'initiated', VERIFIED: 'verified', ... }\n type EventTypesMap = { [K in keyof EventMap as Uppercase<K & string>]: K };\n const eventTypes = useMemo(() => {\n const map = {} as EventTypesMap;\n for (const name of eventsKey.split(',').filter(Boolean)) {\n (map as Record<string, string>)[name.toUpperCase()] = name;\n }\n return map;\n }, [eventsKey]);\n\n return { eventType, data, eventTypes, isConnected, error, disconnect };\n}\n","import { useEffect, useState } from 'react';\n\nexport interface TimerHandle {\n timer: number;\n startTimer: (seconds?: number) => void;\n}\n\n// Generic countdown timer — decrements every second until 0\nexport function useTimer(initial = 0): TimerHandle {\n const [timer, setTimer] = useState(initial);\n\n useEffect(() => {\n if (timer > 0) {\n const t = setTimeout(() => setTimer(timer - 1), 1000);\n return () => clearTimeout(t);\n }\n }, [timer]);\n\n return { timer, startTimer: (seconds = 45) => setTimer(seconds) };\n}\n"],"names":["useHotkeys"],"mappings":"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"}
@@ -7,9 +7,12 @@ function parseSlug(segment) {
7
7
  if (idx < 0) return null;
8
8
  return { slug: segment.slice(0, idx), id: segment.slice(idx + 1) };
9
9
  }
10
+ function unslugify(slug) {
11
+ return slug.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
12
+ }
10
13
  function slugify(text) {
11
14
  return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
12
15
  }
13
16
 
14
- export { buildSlug, parseSlug, slugify };
17
+ export { buildSlug, parseSlug, slugify, unslugify };
15
18
  //# sourceMappingURL=slug.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"slug.js","sources":["../../lib/utils/slug.ts"],"sourcesContent":["const SLUG_SEPARATOR = '~';\n\n// Builds a slug segment: \"acme-corp~230aa33f-7f07-4dd7-...\"\nexport function buildSlug(name: string, id: string): string {\n return `${slugify(name)}${SLUG_SEPARATOR}${id}`;\n}\n\n// Parses a slug segment: returns { slug, id } or null if no separator\nexport function parseSlug(segment: string): { slug: string; id: string } | null {\n const idx = segment.indexOf(SLUG_SEPARATOR);\n if (idx < 0) return null;\n return { slug: segment.slice(0, idx), id: segment.slice(idx + 1) };\n}\n\n// Converts \"Acme Corp\" → \"acme-corp\", passes through already-slugified text\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n"],"names":[],"mappings":"AAAA,MAAM,cAAA,GAAiB,GAAA;AAGhB,SAAS,SAAA,CAAU,MAAc,EAAA,EAAoB;AAC1D,EAAA,OAAO,GAAG,OAAA,CAAQ,IAAI,CAAC,CAAA,EAAG,cAAc,GAAG,EAAE,CAAA,CAAA;AAC/C;AAGO,SAAS,UAAU,OAAA,EAAsD;AAC9E,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAA;AAC1C,EAAA,IAAI,GAAA,GAAM,GAAG,OAAO,IAAA;AACpB,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,EAAG,EAAA,EAAI,OAAA,CAAQ,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA,EAAE;AACnE;AAGO,SAAS,QAAQ,IAAA,EAAsB;AAC5C,EAAA,OAAO,IAAA,CACJ,WAAA,EAAY,CACZ,IAAA,EAAK,CACL,OAAA,CAAQ,aAAA,EAAe,GAAG,CAAA,CAC1B,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AAC3B;;;;"}
1
+ {"version":3,"file":"slug.js","sources":["../../lib/utils/slug.ts"],"sourcesContent":["const SLUG_SEPARATOR = '~';\n\n// Builds a slug segment: \"acme-corp~230aa33f-7f07-4dd7-...\"\nexport function buildSlug(name: string, id: string): string {\n return `${slugify(name)}${SLUG_SEPARATOR}${id}`;\n}\n\n// Parses a slug segment: returns { slug, id } or null if no separator\nexport function parseSlug(segment: string): { slug: string; id: string } | null {\n const idx = segment.indexOf(SLUG_SEPARATOR);\n if (idx < 0) return null;\n return { slug: segment.slice(0, idx), id: segment.slice(idx + 1) };\n}\n\n// Converts \"acme-corp\" → \"Acme Corp\", so a page can title itself from the URL instead of awaiting a request.\n// Lossy by nature: slugify folds away case and punctuation, so this restores word breaks only.\nexport function unslugify(slug: string): string {\n return slug\n .split('-')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n// Converts \"Acme Corp\" → \"acme-corp\", passes through already-slugified text\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n"],"names":[],"mappings":"AAAA,MAAM,cAAA,GAAiB,GAAA;AAGhB,SAAS,SAAA,CAAU,MAAc,EAAA,EAAoB;AAC1D,EAAA,OAAO,GAAG,OAAA,CAAQ,IAAI,CAAC,CAAA,EAAG,cAAc,GAAG,EAAE,CAAA,CAAA;AAC/C;AAGO,SAAS,UAAU,OAAA,EAAsD;AAC9E,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAA;AAC1C,EAAA,IAAI,GAAA,GAAM,GAAG,OAAO,IAAA;AACpB,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,EAAG,EAAA,EAAI,OAAA,CAAQ,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA,EAAE;AACnE;AAIO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,IAAA,CACJ,MAAM,GAAG,CAAA,CACT,OAAO,OAAO,CAAA,CACd,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,OAAO,CAAC,CAAA,CAAE,aAAY,GAAI,IAAA,CAAK,MAAM,CAAC,CAAC,CAAA,CAC1D,IAAA,CAAK,GAAG,CAAA;AACb;AAGO,SAAS,QAAQ,IAAA,EAAsB;AAC5C,EAAA,OAAO,IAAA,CACJ,WAAA,EAAY,CACZ,IAAA,EAAK,CACL,OAAA,CAAQ,aAAA,EAAe,GAAG,CAAA,CAC1B,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AAC3B;;;;"}
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
- "version": "0.5.5",
8
+ "version": "0.5.6",
9
9
  "type": "module",
10
10
  "sideEffects": false,
11
11
  "repository": {