@f-ewald/components 1.1.0 → 1.2.0

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.
Files changed (41) hide show
  1. package/README.md +4 -0
  2. package/custom-elements.json +2329 -301
  3. package/dist/dropdown-button.d.ts.map +1 -1
  4. package/dist/dropdown-button.js +1 -0
  5. package/dist/dropdown-button.js.map +1 -1
  6. package/dist/form-select.d.ts +5 -4
  7. package/dist/form-select.d.ts.map +1 -1
  8. package/dist/form-select.js +6 -5
  9. package/dist/form-select.js.map +1 -1
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/kanban-board.d.ts +110 -0
  15. package/dist/kanban-board.d.ts.map +1 -0
  16. package/dist/kanban-board.js +619 -0
  17. package/dist/kanban-board.js.map +1 -0
  18. package/dist/kanban-card.d.ts +35 -0
  19. package/dist/kanban-card.d.ts.map +1 -0
  20. package/dist/kanban-card.js +163 -0
  21. package/dist/kanban-card.js.map +1 -0
  22. package/dist/kanban-column.d.ts +30 -0
  23. package/dist/kanban-column.d.ts.map +1 -0
  24. package/dist/kanban-column.js +166 -0
  25. package/dist/kanban-column.js.map +1 -0
  26. package/dist/multi-select.d.ts +142 -0
  27. package/dist/multi-select.d.ts.map +1 -0
  28. package/dist/multi-select.js +1304 -0
  29. package/dist/multi-select.js.map +1 -0
  30. package/dist/tokens.css +3 -0
  31. package/dist/tokens.d.ts.map +1 -1
  32. package/dist/tokens.js +2 -0
  33. package/dist/tokens.js.map +1 -1
  34. package/docs/design-language.md +8 -0
  35. package/docs/form-select.md +5 -4
  36. package/docs/kanban-board.md +96 -0
  37. package/docs/kanban-card.md +56 -0
  38. package/docs/kanban-column.md +58 -0
  39. package/docs/multi-select.md +120 -0
  40. package/llms.txt +182 -4
  41. package/package.json +1 -1
package/llms.txt CHANGED
@@ -399,10 +399,11 @@ a native `<select>` wherever consistent cross-browser styling and a
399
399
  picker).
400
400
 
401
401
  The trigger fills its host's width (`justify-content: space-between`
402
- pushes the chevron to the far edge), but the host itself stays
403
- `display: inline-block` so usages that never size the host (a filter
404
- bar, a status picker) keep shrink-to-fit auto-width unchanged. To make an
405
- instance full-width, size the host itself: `form-select { width: 100%; }`.
402
+ pushes the chevron to the far edge), and the host is `display: block`, so a
403
+ `form-select` fills its container's width by default matching the other
404
+ form fields. To make an instance shrink to its content instead (e.g. in a
405
+ filter bar or a status picker), constrain the host:
406
+ `form-select { display: inline-block; }` (or `width: fit-content`).
406
407
 
407
408
  Set `searchable` to replace the button trigger with an editable combobox
408
409
  that filters the predefined options by case-insensitive label infix. Typed
@@ -522,6 +523,111 @@ Example:
522
523
  </script>
523
524
  ```
524
525
 
526
+ ## <kanban-board>
527
+
528
+ A configurable kanban board: a horizontally scrolling row of columns, each
529
+ holding cards. **A card's column is its state** — moving a card to another
530
+ column (by drag-and-drop, keyboard, or the detail popover's state selector)
531
+ changes its state, and the board emits a single `card-move` for all three.
532
+
533
+ Data-driven: set the `columns` property to `KanbanColumnData[]`. The board
534
+ keeps its own working copy and mutates it optimistically on every move, so it
535
+ works standalone; re-assign `columns` at any time to stay controlled from a
536
+ store. Cards show only their ticket number and title in the overview; the
537
+ full detail (description, state, created/updated timestamps) opens in a
538
+ screen-centered `popover-panel`.
539
+
540
+ Pointer drag-and-drop supports both cross-column moves and within-column
541
+ reordering with a live drop indicator (set `reorderable` false to keep only
542
+ cross-column moves when intra-column order isn't persisted). Keyboard parity:
543
+ focus a card and press Space to pick it up, arrow keys to move it (left/right
544
+ across columns, up/down within a column), Space to drop, or Escape to cancel;
545
+ Enter opens the detail. Moves are announced in a polite live region, and the
546
+ moved card briefly flashes a warm highlight so you can see where it landed.
547
+
548
+ Set `manual` for a server-authoritative board (API + WebSocket/SSE): every
549
+ move still emits `card-move`, but the board does NOT apply it locally, so it
550
+ reflects only what you assign to `columns` — e.g. the change echoed back over
551
+ the socket. The default is optimistic local updates.
552
+
553
+ Import: `import "@f-ewald/components/kanban-board.js";`
554
+
555
+ Properties: `columns` (JS property only) : KanbanColumnData[], default []; `label` (attribute `label`) : string, default "Board"; `manual` (attribute `manual`) : boolean, default false; `reorderable` (attribute `reorderable`) : boolean, default true
556
+ Events: `card-move`, `card-open`
557
+ CSS custom properties: `--ui-font`, `--ui-font-size`, `--ui-font-size-sm`, `--ui-font-weight-medium`, `--ui-line-height-normal`, `--ui-primary`, `--ui-text`, `--ui-text-muted`
558
+
559
+ Example:
560
+ ```html
561
+ <kanban-board label="Project tasks"></kanban-board>
562
+ <script type="module">
563
+ const board = document.querySelector("kanban-board");
564
+ board.columns = [
565
+ {
566
+ id: "todo",
567
+ title: "To Do",
568
+ cards: [
569
+ {
570
+ id: "c1",
571
+ ticket: "PROJ-142",
572
+ title: "Wire up auth callback",
573
+ description: "Handle the OAuth redirect and persist the session token.",
574
+ createdAt: "2026-07-18T09:12:00Z",
575
+ updatedAt: "2026-07-21T14:03:00Z",
576
+ },
577
+ ],
578
+ },
579
+ { id: "doing", title: "In Progress", cards: [] },
580
+ { id: "done", title: "Done", cards: [] },
581
+ ];
582
+ // A card's column is its state; drag-and-drop, keyboard, and the detail
583
+ // popover state selector all emit the same card-move event.
584
+ board.addEventListener("card-move", (e) => console.log(e.detail));
585
+ board.addEventListener("card-open", (e) => console.log(e.detail.cardId));
586
+ </script>
587
+ ```
588
+
589
+ ## <kanban-card>
590
+
591
+ A single kanban card's compact overview: its ticket number and title only.
592
+ Purely presentational and metadata-only — it is created and driven by
593
+ `kanban-board`, which owns drag-and-drop, selection, and the richer detail
594
+ view (description, state, and timestamps live in the board's popover, not
595
+ here). The board sets `draggable`, toggles the `dragging`/`grabbed`
596
+ attributes for pointer and keyboard moves, and binds the open/keyboard
597
+ handlers; focus is delegated to the inner control so the board can move
598
+ keyboard focus to a card after a move.
599
+
600
+ Import: `import "@f-ewald/components/kanban-card.js";`
601
+
602
+ Properties: `ticket` (attribute `ticket`) : string, default ""; `heading` (attribute `heading`) : string, default ""
603
+ Events: none
604
+ CSS custom properties: `--ui-border`, `--ui-focus-ring`, `--ui-font`, `--ui-font-size`, `--ui-font-size-sm`, `--ui-font-weight-medium`, `--ui-highlight`, `--ui-line-height-tight`, `--ui-primary`, `--ui-radius`, `--ui-shadow`, `--ui-surface`, `--ui-text`, `--ui-text-muted`
605
+
606
+ Example:
607
+ ```html
608
+ <kanban-card></kanban-card>
609
+ ```
610
+
611
+ ## <kanban-column>
612
+
613
+ A single kanban column: a titled, vertically scrollable region that holds
614
+ its `kanban-card` children (its default `<slot>`), with a header showing the
615
+ column title and card count. Purely presentational and metadata-only —
616
+ `kanban-board` creates it, positions the cards inside it, and drives the
617
+ drop-target highlight via the reflected `dragover` attribute and the empty
618
+ hint via `empty`.
619
+
620
+ Import: `import "@f-ewald/components/kanban-column.js";`
621
+
622
+ Properties: `heading` (attribute `heading`) : string, default ""; `count` (attribute `count`) : number, default 0; `dragover` (attribute `dragover`) : boolean, default false; `empty` (attribute `empty`) : boolean, default false
623
+ Events: none
624
+ CSS custom properties: `--ui-border`, `--ui-font`, `--ui-font-size`, `--ui-font-size-sm`, `--ui-font-size-xs`, `--ui-font-weight-medium`, `--ui-font-weight-semibold`, `--ui-line-height-glyph`, `--ui-line-height-tight`, `--ui-primary`, `--ui-radius`, `--ui-radius-sm`, `--ui-surface`, `--ui-surface-muted`, `--ui-text`, `--ui-text-muted`
625
+
626
+ Example:
627
+ ```html
628
+ <kanban-column></kanban-column>
629
+ ```
630
+
525
631
  ## <kbd-hint>
526
632
 
527
633
  Renders a keyboard shortcut as one boxed keycap per `+`-separated token.
@@ -602,6 +708,78 @@ Example:
602
708
  <map-pin color="#22c55e" size="26" highlighted>🏠</map-pin>
603
709
  ```
604
710
 
711
+ ## <multi-select>
712
+
713
+ A form-associated multi-select: a trigger showing a compact summary of the
714
+ current selection, opening a multi-selectable listbox popover, with an
715
+ optional removable-chip list of the chosen values. A drop-in generic
716
+ replacement for a native `<select multiple>` — set `name` on the element
717
+ itself and each
718
+ selected value is submitted as a repeated `name=value` entry, matching native
719
+ multiple-select semantics; `new FormData(form).getAll(name)` and
720
+ `form.reset()` work unchanged.
721
+
722
+ The trigger visually follows `form-select`: a `2rem` field showing the
723
+ `placeholder` when empty, the single selected label when one value is chosen,
724
+ and `N selected` when several are. Set `show-chips` to additionally render
725
+ the selected values as compact chips below the trigger, each with an
726
+ accessible `32px` remove control; chips are off by default and the region
727
+ reserves no space while nothing is selected. Like `form-select`, the host is
728
+ `display: block` and fills its container by default; constrain the host
729
+ (`multi-select { display: inline-block; }`) to shrink it to its content
730
+ instead.
731
+
732
+ Set `searchable` to replace the button trigger with an editable combobox that
733
+ infix-filters the predefined options by case-insensitive label. Typed text is
734
+ only a query: `values` changes exclusively when options are toggled, and an
735
+ uncommitted query is discarded when the list closes.
736
+
737
+ The `variant` chooses between two presentations, and `searchable` applies to
738
+ both:
739
+
740
+ - `"dropdown"` (default) is the popover form described above: a compact
741
+ trigger (button, or search combobox when `searchable`) that opens a
742
+ multi-selectable listbox on demand, closes on outside click / Escape, and
743
+ shows a chevron.
744
+ - `"list"` renders the options as a persistently visible, bordered surface
745
+ with no popover, no chevron, and no `open` host state; it never registers
746
+ outside-click listeners. Its scroll viewport is sized to roughly
747
+ `visibleRows` `2rem` rows (see `visible-rows`). When not `searchable` the
748
+ `listbox` itself is focusable and drives Arrow/Home/End/Enter/Space keyboard
749
+ navigation via `aria-activedescendant`; when `searchable` a `2rem` search
750
+ field sits above the list and owns navigation, and Escape only clears the
751
+ query instead of hiding the list.
752
+
753
+ Import: `import "@f-ewald/components/multi-select.js";`
754
+
755
+ Properties: `options` (JS property only) : MultiSelectOption[], default []; `values` (JS property only) : string[], default []; `name` (attribute `name`) : string, default ""; `label` (attribute `label`) : string, default ""; `placeholder` (attribute `placeholder`) : string, default "Select options"; `disabled` (attribute `disabled`) : boolean, default false; `required` (attribute `required`) : boolean, default false; `searchable` (attribute `searchable`) : boolean, default false; `max` (attribute `max`) : number, default 0; `variant` (attribute `variant`) : MultiSelectVariant, default "dropdown"; `visibleRows` (attribute `visible-rows`) : number, default 5; `showChips` (attribute `show-chips`) : boolean, default false
756
+ Events: `change`
757
+ CSS custom properties: `--ui-border`, `--ui-danger`, `--ui-focus-ring`, `--ui-font`, `--ui-font-size-sm`, `--ui-font-weight-medium`, `--ui-font-weight-semibold`, `--ui-line-height-tight`, `--ui-primary`, `--ui-radius`, `--ui-radius-sm`, `--ui-shadow`, `--ui-surface`, `--ui-surface-muted`, `--ui-text`, `--ui-text-muted`
758
+
759
+ Example:
760
+ ```html
761
+ <multi-select name="colors" label="Colors" searchable></multi-select>
762
+ <multi-select id="colors-list" variant="list" visible-rows="4"></multi-select>
763
+ <script type="module">
764
+ const options = [
765
+ { value: "red", label: "Red" },
766
+ { value: "green", label: "Green" },
767
+ { value: "blue", label: "Blue" },
768
+ { value: "amber", label: "Amber" },
769
+ { value: "violet", label: "Violet" },
770
+ ];
771
+ const dropdown = document.querySelector("multi-select[name='colors']");
772
+ dropdown.options = options;
773
+ dropdown.values = ["red", "blue"];
774
+ dropdown.searchable = true;
775
+ dropdown.addEventListener("change", (e) => console.log(e.detail.values));
776
+
777
+ const list = document.getElementById("colors-list");
778
+ list.options = options;
779
+ list.values = ["green"];
780
+ </script>
781
+ ```
782
+
605
783
  ## <percent-bar-chart>
606
784
 
607
785
  Horizontal bar chart for labeled percentage rows, using D3's linear scale.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@f-ewald/components",
3
3
  "private": false,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "A collection of universally usable web components for various tasks.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",