laif-ds 1.1.2 β†’ 1.1.4

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.
package/dist/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable technical changes to the laif-ds core will be documented in this fil
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [laif-ds@1.1.4]
9
+
10
+ ### πŸš€ Added
11
+
12
+ - **AppSelect**: Added `keywords` to `AppSelectOption` so JSX-labelled options can be searched by text the label itself can't provide
13
+ - **AppSelect**: Added `required` and `aria-invalid` support to the label, matching `Input` β€” `required` shows a `*` and sets `aria-required`, `aria-invalid` adds the destructive border/ring and a red `*`, and clicking the label focuses the trigger
14
+
15
+ ### πŸ”§ Changed
16
+
17
+ - **AppSelect**: The multi-select popover now opens over the trigger so the selected badges line up with the trigger chips at every size, with a placeholder row shown when nothing is selected
18
+ - **AppSelect**: Group headings now render on a muted background with rounded corners so they read as section separators
19
+
20
+ ### πŸ› Fixed
21
+
22
+ - **AppSelect**: Fixed the dropdown list being unscrollable by wheel or touch when opened inside a Dialog, Sheet or Drawer
23
+ - **AppSelect**: An empty string value no longer counts as a selection unless an option actually has value `""`, so the placeholder shows correctly and the single clear button only appears for a real selection; the trigger now forwards extra HTML attributes
24
+ - **AppSelect**: Reworked multi-select chips β€” they stay on one line, overflow fades out and becomes inert (click opens the dropdown instead), and a chip is only removed from its own clear button
25
+ - **AppSelect**: The select-all toggle no longer shifts down when pressed
26
+ - **AppSelect**: Options sharing the same label across different groups are now identified by value instead of label, so they highlight and keyboard-navigate independently (#100)
27
+
28
+ ## [laif-ds@1.1.3]
29
+
30
+ ### πŸš€ Added
31
+
32
+ - **AppEditor**: Added link support β€” `[text](url)` in `defaultValue` now renders as a link and round-trips correctly instead of breaking the editor
33
+ - **AppEditor**: Added an opt-in link toolbar with a button that opens a URL + label form, plus an `insertLink` handle
34
+
35
+ ### πŸ› Fixed
36
+
37
+ - **AppEditor**: Fixed underline and strikethrough so they render together when both are applied, instead of one being stripped
38
+ - **AppEditor**: Added an inline code toggle, with inline code now using a readable token in dark mode
39
+ - **AppEditor**: Fixed the font-format toolbar so each format's pressed state follows only its own selection state
40
+
8
41
  ## [laif-ds@1.1.2]
9
42
 
10
43
  ### πŸ› Fixed
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-01T10:45:10.468Z",
3
+ "generatedAt": "2026-10-20T20:53:04.357Z",
4
4
  "package": {
5
5
  "name": "laif-ds",
6
- "version": "1.1.2"
6
+ "version": "1.1.4"
7
7
  },
8
8
  "summary": {
9
- "scannedFiles": 502,
9
+ "scannedFiles": 504,
10
10
  "deprecatedComponentsCount": 5,
11
11
  "deprecatedComponentsUsedCount": 3,
12
12
  "componentsWithoutExamplesCount": 5,
@@ -21,7 +21,7 @@
21
21
  {
22
22
  "component": "DataTable",
23
23
  "replacement": "AppDataGrid",
24
- "totalOccurrences": 24,
24
+ "totalOccurrences": 23,
25
25
  "usages": [
26
26
  {
27
27
  "file": "src/components/ui/app-data-grid/parts/grid-filter-editor.tsx",
@@ -79,10 +79,6 @@
79
79
  "file": "src/components/ui/app-data-grid/parts/grid-selection-bar.tsx",
80
80
  "occurrences": 1
81
81
  },
82
- {
83
- "file": "src/components/ui/app-select.tsx",
84
- "occurrences": 1
85
- },
86
82
  {
87
83
  "file": "src/components/ui/tables/data-table/data-table.utils.ts",
88
84
  "occurrences": 1
@@ -611,6 +607,21 @@
611
607
  "src/components/stories/app-radio-group.stories.tsx"
612
608
  ]
613
609
  },
610
+ {
611
+ "className": "text-d-muted-foreground text-xs",
612
+ "occurrences": 9,
613
+ "files": [
614
+ "src/components/editor/plugins/mention-plugin.tsx",
615
+ "src/components/stories/app-card.stories.tsx",
616
+ "src/components/stories/app-select.stories.tsx",
617
+ "src/components/stories/app-select.test.stories.tsx",
618
+ "src/components/stories/input.stories.tsx",
619
+ "src/components/ui/app-data-grid/parts/grid-filter-editor.tsx",
620
+ "src/components/ui/tables/data-cross-table/cell-editor-dialog.tsx",
621
+ "src/components/ui/tables/data-cross-table/data-cross-table.tsx",
622
+ "src/components/ui/tables/data-cross-table/editable-input.tsx"
623
+ ]
624
+ },
614
625
  {
615
626
  "className": "text-d-muted-foreground max-w-prose text-sm",
616
627
  "occurrences": 9,
@@ -648,32 +659,7 @@
648
659
  ]
649
660
  },
650
661
  {
651
- "className": "!text-d-foreground opacity-50",
652
- "occurrences": 8,
653
- "files": [
654
- "src/components/ui/app-multiple-select-dropdown.tsx",
655
- "src/components/ui/app-select.tsx",
656
- "src/components/ui/app-time-picker.tsx",
657
- "src/components/ui/async-select.tsx",
658
- "src/components/ui/date-picker.tsx",
659
- "src/components/ui/multiple-selector.tsx"
660
- ]
661
- },
662
- {
663
- "className": "text-d-muted-foreground text-xs",
664
- "occurrences": 7,
665
- "files": [
666
- "src/components/editor/plugins/mention-plugin.tsx",
667
- "src/components/stories/app-card.stories.tsx",
668
- "src/components/stories/input.stories.tsx",
669
- "src/components/ui/app-data-grid/parts/grid-filter-editor.tsx",
670
- "src/components/ui/tables/data-cross-table/cell-editor-dialog.tsx",
671
- "src/components/ui/tables/data-cross-table/data-cross-table.tsx",
672
- "src/components/ui/tables/data-cross-table/editable-input.tsx"
673
- ]
674
- },
675
- {
676
- "className": "bg-muted/50 rounded p-4 text-sm",
662
+ "className": "flex w-80 flex-col gap-6",
677
663
  "occurrences": 7,
678
664
  "files": [
679
665
  "src/components/stories/app-select.stories.tsx"
@@ -714,17 +700,6 @@
714
700
  "src/components/editor/plugins/toolbar/block-format/format-quote.tsx"
715
701
  ]
716
702
  },
717
- {
718
- "className": "flex items-center gap-1",
719
- "occurrences": 6,
720
- "files": [
721
- "src/components/editor/plugins/toolbar/history-toolbar-plugin.tsx",
722
- "src/components/stories/app-select.stories.tsx",
723
- "src/components/ui/app-data-grid/parts/grid-group-row.tsx",
724
- "src/components/ui/app-kanban.tsx",
725
- "src/components/ui/calendar.tsx"
726
- ]
727
- },
728
703
  {
729
704
  "className": "text-sm font-semibold",
730
705
  "occurrences": 6,
@@ -735,6 +710,25 @@
735
710
  "src/components/stories/truncated-cell.stories.tsx",
736
711
  "src/components/ui/weekly-calendar/appointment-card.tsx"
737
712
  ]
713
+ },
714
+ {
715
+ "className": "flex flex-wrap gap-2",
716
+ "occurrences": 6,
717
+ "files": [
718
+ "src/components/stories/app-card.stories.tsx",
719
+ "src/components/stories/app-data-grid-filtri-custom.stories.tsx",
720
+ "src/components/stories/file-preview.stories.tsx",
721
+ "src/components/stories/toaster.stories.tsx"
722
+ ]
723
+ },
724
+ {
725
+ "className": "text-lg font-semibold",
726
+ "occurrences": 6,
727
+ "files": [
728
+ "src/components/stories/app-kanban.stories.tsx",
729
+ "src/components/stories/changelog.stories.tsx",
730
+ "src/components/stories/playground.stories.tsx"
731
+ ]
738
732
  }
739
733
  ]
740
734
  }
@@ -8,18 +8,29 @@ Rich text editor powered by Lexical with optional Markdown sync. Supports config
8
8
 
9
9
  ## Props
10
10
 
11
- | Prop | Type | Default | Description |
12
- | ---------------- | ------------------------------------------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | `defaultValue` | `string` | `undefined` | Initial content. Interpreted as Markdown when `onlyMarkdown=true`, plain text otherwise. |
14
- | `onValueChange` | `(markdown: string, meta: { mentions: MentionItem[] }) => void` | `undefined` | Fires on every edit with the current Markdown and a metadata object. `meta.mentions` is the deduped (by `id`) list of mentions in the text (empty when mentions are disabled). Takes precedence over `onMarkdownEdit`. |
15
- | `onMarkdownEdit` | `(markdown: string) => void` | `undefined` | **Deprecated** β€” use `onValueChange`. Still honored as a fallback when `onValueChange` is not provided, but never receives mention metadata. |
16
- | `plugins` | `("clear" \| "counter")[]` | `[]` | Enable optional plugins: clear buttons, character counter. |
17
- | `toolbars` | `("block-format" \| "font-format" \| "history")[]` | `[]` | Enable toolbar groups. |
18
- | `placeholder` | `string` | `"Start typing..."` | Placeholder for empty editor. |
19
- | `onlyMarkdown` | `boolean` | `true` | Treat `defaultValue` as Markdown and sync editor to Markdown. If `false`, `defaultValue` is plain text. |
20
- | `className` | `string` | `"overflow-hidden rounded-lg border border-d-border"` | Extra classes for the content editable. |
21
- | `mentions` | `MentionItem[] \| (() => MentionItem[] \| Promise<MentionItem[]>)` | `undefined` | Enables @mention support when provided. Either a ready array (filtered client-side) or a **loader** called once on first menu open (no preload on mount). When omitted, no mention node/plugin/transformer is registered and output is identical to a mention-less editor. |
22
- | `mentionTrigger` | `string` | `"@"` | Single character that opens the mention typeahead menu. |
11
+ | Prop | Type | Default | Description |
12
+ | ---------------- | ---------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------- |
13
+ | `defaultValue` | `string` | `undefined` | Initial content. Interpreted as Markdown when `onlyMarkdown=true`, plain text otherwise. |
14
+ | `onValueChange` | `(markdown: string, meta: { mentions: MentionItem[] }) => void` | `undefined` | Fires on every edit with the current Markdown and a metadata object. `meta.mentions` is the deduped (by `id`) list of mentions in the text (empty when mentions are disabled). Takes precedence over `onMarkdownEdit`. |
15
+ | `onMarkdownEdit` | `(markdown: string) => void` | `undefined` | **Deprecated** β€” use `onValueChange`. Still honored as a fallback when `onValueChange` is not provided, but never receives mention metadata. |
16
+ | `plugins` | `("clear" \| "counter")[]` | `[]` | Enable optional plugins: clear buttons, character counter. |
17
+ | `toolbars` | `("block-format" \| "font-format" \| "history" \| "link")[]` | `[]` | Enable toolbar groups. |
18
+ | `placeholder` | `string` | `"Start typing..."` | Placeholder for empty editor. |
19
+ | `onlyMarkdown` | `boolean` | `true` | Treat `defaultValue` as Markdown and sync editor to Markdown. If `false`, `defaultValue` is plain text. |
20
+ | `className` | `string` | `"overflow-hidden rounded-lg border border-d-border"` | Extra classes for the content editable. |
21
+ | `mentions` | `MentionItem[] \| (() => MentionItem[] \| Promise<MentionItem[]>)` | `undefined` | Enables @mention support when provided. Either a ready array (filtered client-side) or a **loader** called once on first menu open (no preload on mount). When omitted, no mention node/plugin/transformer is registered and output is identical to a mention-less editor. |
22
+ | `mentionTrigger` | `string` | `"@"` | Single character that opens the mention typeahead menu. |
23
+ | `ref` | `Ref<AppEditorHandle>` | `undefined` | Imperative handle β€” see `AppEditorHandle` below. |
24
+
25
+ ### AppEditorHandle
26
+
27
+ ```ts
28
+ interface AppEditorHandle {
29
+ // Inserts a hyperlink at the caret (end of the text when there is no
30
+ // selection) and leaves the caret after it. `text` defaults to `url`.
31
+ insertLink: (url: string, text?: string) => void;
32
+ }
33
+ ```
23
34
 
24
35
  ### MentionItem
25
36
 
@@ -41,12 +52,14 @@ type MentionItem = {
41
52
  - **Toolbars**:
42
53
  - `history`: undo/redo toolbar.
43
54
  - `block-format`: heading levels (h1-h3), numbered/bulleted/check lists, blockquote.
44
- - `font-format`: bold, italic, underline, strikethrough, inline code.
55
+ - `font-format`: bold, italic, underline, strikethrough, inline code. Each button is an independent toggle whose pressed state follows the selection; formats combine freely (underline + strikethrough together render both lines and export as `~~<u>text</u>~~`).
56
+ - `link`: a link button that opens a form with **URL** and **Etichetta** (label; form copy is Italian like the block-format menu). The label is prefilled with the selected text; with the caret inside a link the button shows as pressed and the form edits that link. A URL without a scheme gets `https://` (`example.com` β†’ `https://example.com`); an empty label falls back to the URL.
45
57
  - **Plugins**:
46
58
  - `clear`: clear actions in the footer.
47
59
  - `counter`: UTF-16 character counter in the footer.
48
60
  - **Editing**: Autosave via `onValueChange`. Keyboard navigation and selection per Lexical defaults.
49
61
  - **Markdown Features**: Full support for headings, lists, quotes, links, inline code, bold, italic, strikethrough, and custom underline syntax.
62
+ - **Links**: `[text](url)` in `defaultValue` renders as a link and is emitted back unchanged; links pasted as HTML are kept as links. Enable the `link` toolbar for a user-facing button; to insert a link programmatically (e.g. a "quick insert" action outside the editor) call `ref.current.insertLink(url, text)`. Give the triggering button `onMouseDown={(e) => e.preventDefault()}` so the editor keeps its caret.
50
63
  - **Mentions** (opt-in via `mentions`):
51
64
  - `mentions` is either a ready `MentionItem[]` or a **loader function** `() => MentionItem[] | Promise<MentionItem[]>`. The loader is called **once, the first time the menu opens**, so a large list is never preloaded on mount; while it resolves the menu shows a `Loading…` row, and on failure a `Couldn't load mentions` row (re-opening the menu retries). The resolved list is then filtered client-side like the array form.
52
65
  - With the **loader form**, `meta.mentions` cannot reconcile against a synchronous source list, so its emitted items carry `id`/`label`/`value` from the chip but **not `sublabel`** (which is menu-only metadata). The array form preserves `sublabel`.
@@ -153,6 +166,31 @@ export function UnderlineExample() {
153
166
  }
154
167
  ```
155
168
 
169
+ ### Inserting a link from outside the editor
170
+
171
+ ```tsx
172
+ import { AppEditor, type AppEditorHandle } from "laif-ds";
173
+ import { useRef } from "react";
174
+
175
+ export function ReplyEditor() {
176
+ const editorRef = useRef<AppEditorHandle>(null);
177
+ return (
178
+ <>
179
+ <AppEditor ref={editorRef} onValueChange={console.log} />
180
+ <button
181
+ type="button"
182
+ onMouseDown={(e) => e.preventDefault()}
183
+ onClick={() =>
184
+ editorRef.current?.insertLink("https://example.com", "Example")
185
+ }
186
+ >
187
+ Insert link
188
+ </button>
189
+ </>
190
+ );
191
+ }
192
+ ```
193
+
156
194
  ### Mentions
157
195
 
158
196
  ```tsx