Unlayered CSS beats Tailwind's @layer utilities regardless of specificity,
so base.css (and consumer stylesheets) overrode classes passed to
components. Element defaults now live in @layer base and twui-* rules in
@layer components. Third-party TinyMCE/Ace overrides stay unlayered. README
documents wrapping consumer overrides in layers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Radios: each radio stays on one line, with its label clamped to a single
line (full text on hover) like Checkbox. Pass `wrap` to let labels flow
onto multiple lines. The native radio is replaced by a custom indicator
aligned to the label's first line; the input stays (visually hidden) for
forms and keyboard use, so inputProps.className now lands on a hidden
input - style .twui-radio-indicator instead. Adds twui-radios,
twui-radio, twui-radio-label and twui-radio-indicator classes.
Pagination: flex children could not shrink below their content, so many
pages pushed the component past its parent. Add min-w-0 so the page
buttons scroll inside the parent instead. Adds twui-pagination.
Playground: demos for both, and bump bunext to 1.3.5.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Labels no longer overflow their container: by default they are clamped
to a single line with an ellipsis and the full label is shown as the
wrapper's title on hover. Pass `wrap` to let the label flow onto
multiple lines. Adds the `twui-checkbox-label` class.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AIChat (elements/ai/AIChat) is a complete LLM chat UI built on new hooks:
- useAIChat: streaming conversation state (send, stop, regenerate,
edit, feedback); `send` may return a string, async iterable,
ReadableStream or fetch Response
- useAIChatHistory: saved conversations with optional localStorage
persistence and generated titles, shown in AIChatSidebar
- useAIChatSpeech: read aloud via browser speech synthesis or a
custom `speak` returning audio
- useAIChatDictation: voice input via browser recognition or a
custom `transcribe`
Also adds a model picker, message editing, thumbs up/down, markdown
export, scoped markdown styles in base.css, the twuiReadTextStream,
twuiStripMarkdown and twuiGroupByRecency utils (with tests), and a
playground page. AIPromptBlock is kept and marked deprecated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measure the popover in a hidden, out-of-flow state and position it in a
layout effect, so opening it no longer grows the page for a frame.
Centered (top/bottom) popovers are clamped to stay within the viewport.
Also ignore /test and /.tmp.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Route stepper value changes through the native input setter and dispatch
input/change events so React onChange and DOM listeners see the update.
Read the live DOM value while holding a stepper so repeats don't lag.
Input:
- honour `invalidMessage` instead of always reporting "Value mismatch"
- don't let the mount value wipe an external `validity` prop, and don't
force `isValid: true` on clear when `validity` is supplied
- sync controlled and programmatic values into `textarea` (the value
effect had an empty branch that skipped textareas entirely)
- accept `defaultValue`/`value` of `0` and `""` (nil check, not truthiness)
- clear debounce/validation/change-handler timers on unmount
- type `wrapperProps` as `ComponentProps<"div">`
- let `labelProps.htmlFor` override the name link deterministically
- drop duplicated focus outline classes and the stale buttonDown guard
that discarded edits made right after holding a stepper
NumberInputButtons:
- write through `setValue` so steppers work on controlled inputs and
fire `onChange`
- clamp to both `min` and `max`, including `0` values, and respect `step`
- round float drift, ignore steps while a trailing decimal point is typed
- instance-scoped press timers with unmount cleanup; a held press no
longer double-steps on the trailing click
Also adds a numberText stepper example to the dev playground.
Form reads the closest form on every change. A field rendered through a
portal (a Modal) has no form ancestor in the DOM, so FormData(null) threw.
Change handlers now get an empty object instead.
Runs at http://localhost:7010 via bun run dev. The playground imports the library from src through a committed symlink and tsconfig path alias, so component edits hot-reload without a build step. It has its own install root and is excluded from the published package.
- Hooks: per-instance timers in useReady; cleanup for useIntersectionObserver;
useWebSocket no longer reconnects after unmount/refresh, drains the queue
and dispatches non-JSON messages
- Modal/Popover/Dropdown: onClose only on close, Escape to close, no stray
DOM props, hooks before early return, scrollbar-aware positioning,
top-left/top-right dropdown positions
- Elements: Toast/Search/StarRating per-instance timers, Pagination labels
and empty state, Breadcrumbs titles, LinkList dividers/props, segment-wise
active-link matching (new utils/link-matches-path), Table object cells
- Form: Select with a default option is changeable, Input clear notifies
controlled parents and copy works on textareas, Checkbox random label,
NumberInputButtons runaway interval, upload prop/spread fixes
- Layout: Button disabled while loading, Stack gap and Span truncate via
inline styles, Img fallbackImageSrc, className/JSDoc fixes
- Editors/MDX/AI: TinyMCE id/cleanup/stale handler/duplicate scripts,
AceEditor debounce, markdown heights and frontmatter regex, last
assistant message, AIPromptBlock prompt ref
- Utils: fetchApi headers/FormData/fetchOptions/no mutation, base64 file
names and read errors, slugify underscore trim, serializeQuery zero values
- Tests for fetchApi, link matching, slugify and serializeQuery
- AGENTS.md updated for the package layout
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Move components/ to src/ and compile to dist/ with tsc
- Add root barrel export plus deep-import subpath exports and base.css export
- Post-compile step: copy base.css, add .js import extensions, mark client modules
- Add publish.sh and .npmrc for the Gitea npm registry
- Fix type errors, guard TinyMCE env access, drop ace-builds type dependency
- Rename clashing props type names, remove Next.js scaffolding and unused deps
Co-Authored-By: Claude Opus 5.5 <[email protected]>