intelliwaketssveltekitv25 1.0.82 → 1.0.83

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.
@@ -0,0 +1,249 @@
1
+ # DisplayHTML Component
2
+
3
+ **Purpose:** Safely render text, HTML, or Svelte snippets with automatic URL link conversion
4
+
5
+ **When to Use:**
6
+ - Display user-generated content that may contain HTML
7
+ - Automatically convert URLs to clickable links
8
+ - Render dynamic content from database fields
9
+ - Display formatted text with HTML tags
10
+ - Conditionally render Svelte snippets
11
+
12
+ ## Key Props
13
+
14
+ - `value: string | null | undefined | Snippet` (required) - Content to display
15
+ - `anchorClasses?: string` (default: '') - CSS classes for auto-generated anchor links
16
+ - `noLinkReplace?: boolean` (default: false) - Disable automatic URL-to-link conversion
17
+ - `hidden?: boolean` - Hide the component
18
+
19
+ ## Features
20
+
21
+ ### Automatic Link Conversion
22
+ By default, the component automatically converts plain URLs in text to clickable anchor links:
23
+ ```
24
+ "Visit https://example.com" → "Visit <a href="https://example.com">https://example.com</a>"
25
+ ```
26
+
27
+ ### HTML Detection
28
+ The component intelligently detects HTML content and renders it using `{@html}` when needed, while rendering plain text normally.
29
+
30
+ ### Snippet Support
31
+ Pass Svelte snippets as the `value` for dynamic component rendering.
32
+
33
+ ### Text-to-HTML Conversion
34
+ Converts newlines to `<br>` tags for proper text formatting.
35
+
36
+ ## Usage Examples
37
+
38
+ ```svelte
39
+ <script>
40
+ import { DisplayHTML } from 'intelliwaketssveltekitv25';
41
+
42
+ let plainText = 'Hello, world!';
43
+ let htmlContent = '<strong>Bold text</strong> and <em>italic text</em>';
44
+ let textWithUrl = 'Visit our site at https://example.com for more info';
45
+ let multilineText = 'Line 1\nLine 2\nLine 3';
46
+ </script>
47
+
48
+ <!-- Plain text -->
49
+ <DisplayHTML value={plainText} />
50
+ <!-- Output: Hello, world! -->
51
+
52
+ <!-- HTML content -->
53
+ <DisplayHTML value={htmlContent} />
54
+ <!-- Output: Bold text and italic text (rendered) -->
55
+
56
+ <!-- Text with URL (auto-converted to link) -->
57
+ <DisplayHTML value={textWithUrl} />
58
+ <!-- Output: Visit our site at <a href="https://example.com">https://example.com</a> for more info -->
59
+
60
+ <!-- Disable link conversion -->
61
+ <DisplayHTML value={textWithUrl} noLinkReplace />
62
+ <!-- Output: Visit our site at https://example.com for more info (plain text) -->
63
+
64
+ <!-- Styled links -->
65
+ <DisplayHTML
66
+ value={textWithUrl}
67
+ anchorClasses="text-blue-600 hover:underline"
68
+ />
69
+
70
+ <!-- Conditional display -->
71
+ <DisplayHTML value={description} hidden={!showDescription} />
72
+
73
+ <!-- With Svelte snippet -->
74
+ <DisplayHTML value={mySnippet} />
75
+
76
+ <!-- Null-safe (won't render if null/undefined) -->
77
+ <DisplayHTML value={maybeNull} />
78
+
79
+ <!-- Multiline text (newlines converted to <br>) -->
80
+ <DisplayHTML value={multilineText} />
81
+ <!-- Output:
82
+ Line 1<br>
83
+ Line 2<br>
84
+ Line 3
85
+ -->
86
+ ```
87
+
88
+ ## Real-World Examples
89
+
90
+ ### User Comments
91
+ ```svelte
92
+ <script>
93
+ let comment = $state('Check out https://example.com - it's great!');
94
+ </script>
95
+
96
+ <DisplayHTML
97
+ value={comment}
98
+ anchorClasses="text-blue-500 hover:underline"
99
+ />
100
+ ```
101
+
102
+ ### Product Description
103
+ ```svelte
104
+ <script>
105
+ export let data;
106
+ let product = data.product;
107
+ </script>
108
+
109
+ <h2>{product.name}</h2>
110
+ <DisplayHTML value={product.descriptionHTML} />
111
+ ```
112
+
113
+ ### Dynamic Content from Database
114
+ ```svelte
115
+ <script>
116
+ // Database field may contain HTML or plain text
117
+ let content = $state(data.content); // Could be "<p>HTML</p>" or "Plain text"
118
+ </script>
119
+
120
+ <DisplayHTML value={content} />
121
+ <!-- Automatically handles both cases -->
122
+ ```
123
+
124
+ ### Email Body Display
125
+ ```svelte
126
+ <script>
127
+ let emailBody = $state(email.body);
128
+ </script>
129
+
130
+ <div class="email-content">
131
+ <DisplayHTML
132
+ value={emailBody}
133
+ anchorClasses="text-blue-600 underline"
134
+ />
135
+ </div>
136
+ ```
137
+
138
+ ### Conditional Rendering with Snippets
139
+ ```svelte
140
+ <script>
141
+ let useCustomRender = $state(true);
142
+ </script>
143
+
144
+ {#snippet customContent()}
145
+ <div class="custom">
146
+ Custom rendered content
147
+ </div>
148
+ {/snippet}
149
+
150
+ <DisplayHTML value={useCustomRender ? customContent : 'Plain text'} />
151
+ ```
152
+
153
+ ## Security Considerations
154
+
155
+ **Important:** This component uses `{@html}` to render HTML content. Only use it with **trusted content**. Never pass unsanitized user input directly without server-side sanitization.
156
+
157
+ ### Safe Usage
158
+ ```svelte
159
+ <!-- ✅ SAFE: Content from your database (sanitized on server) -->
160
+ <DisplayHTML value={product.description} />
161
+
162
+ <!-- ✅ SAFE: Static content you control -->
163
+ <DisplayHTML value="<strong>Our Policy</strong>" />
164
+
165
+ <!-- ❌ UNSAFE: Raw user input without sanitization -->
166
+ <DisplayHTML value={userInput} />
167
+ <!-- Should be: -->
168
+ <DisplayHTML value={sanitizedUserInput} />
169
+ ```
170
+
171
+ ### Sanitization Example (Server-side)
172
+ ```typescript
173
+ // +page.server.ts
174
+ import DOMPurify from 'isomorphic-dompurify';
175
+
176
+ export const load = async () => {
177
+ const rawContent = await db.content.get();
178
+ const sanitizedContent = DOMPurify.sanitize(rawContent);
179
+
180
+ return {
181
+ content: sanitizedContent
182
+ };
183
+ };
184
+ ```
185
+
186
+ ## Common Patterns
187
+
188
+ ### Table Cell with Links
189
+ ```svelte
190
+ <ArrayTable data={items} columns={[
191
+ {
192
+ title: 'Description',
193
+ cell: (item) => ({ snippet: () => (
194
+ <DisplayHTML
195
+ value={item.description}
196
+ anchorClasses="text-primary-main"
197
+ />
198
+ )})
199
+ }
200
+ ]} />
201
+ ```
202
+
203
+ ### Fallback Content
204
+ ```svelte
205
+ <DisplayHTML value={content || 'No description available'} />
206
+ ```
207
+
208
+ ### Optional Display
209
+ ```svelte
210
+ {#if showDetails}
211
+ <DisplayHTML value={details} />
212
+ {/if}
213
+ ```
214
+
215
+ ## Common Mistakes
216
+
217
+ - ❌ Passing raw user input without sanitization (XSS risk)
218
+ ✅ Correct: Sanitize HTML content on the server before rendering
219
+
220
+ - ❌ Not using `noLinkReplace` when you don't want URL conversion
221
+ ✅ Correct: Set `noLinkReplace={true}` for technical content with URLs
222
+
223
+ - ❌ Using this component when simple text rendering would suffice
224
+ ✅ Correct: Use plain `{value}` for simple text, DisplayHTML only when needed
225
+
226
+ - ❌ Forgetting that `null` or `undefined` values won't render
227
+ ✅ Correct: Provide fallback: `value={content || 'Default text'}`
228
+
229
+ ## Related Functions
230
+
231
+ The component uses these utility functions from `@solidbasisventures/intelliwaketsfoundation`:
232
+ - **ReplaceLinks(html, classes)** - Converts URLs to anchor tags
233
+ - **TextToHTML(text)** - Converts plain text to HTML (newlines to `<br>`)
234
+ - **IncludesHTML(text)** - Detects if string contains HTML tags
235
+
236
+ ## Props Reference
237
+
238
+ | Prop | Type | Default | Description |
239
+ |------|------|---------|-------------|
240
+ | `value` | `string \| null \| undefined \| Snippet` | (required) | Content to display |
241
+ | `anchorClasses` | `string` | `''` | CSS classes for auto-generated links |
242
+ | `noLinkReplace` | `boolean` | `false` | Skip URL-to-link conversion |
243
+ | `hidden` | `boolean` | `false` | Hide component |
244
+
245
+ ## Performance Notes
246
+
247
+ - The component efficiently detects HTML vs plain text to minimize unnecessary `{@html}` usage
248
+ - Link replacement only occurs when `noLinkReplace` is false
249
+ - Snippet detection happens once via `$derived`
@@ -0,0 +1,269 @@
1
+ # DropDown Component
2
+
3
+ **Replaces:** `<select>` elements and custom dropdown menus
4
+
5
+ **Purpose:** Rich dropdown menu with keyboard navigation, icons, search, and action handling
6
+
7
+ **When to Use:**
8
+ - Action menus (context menus, toolbar dropdowns)
9
+ - Navigation dropdowns with links
10
+ - Replacing `<select>` when you need icons, dividers, or complex items
11
+ - Any menu that needs better UX than native `<select>`
12
+
13
+ ## Key Props
14
+
15
+ - `show?: boolean` ($bindable) - Control dropdown open/closed state
16
+ - `position?: TDropDownControlPosition` (default: null) - Menu position relative to button
17
+ - `ddActions?: IDDAction[]` (default: []) - Array of menu items/actions (see IDDAction interface below)
18
+ - `noCaret?: boolean` - Hide the dropdown arrow icon
19
+ - `buttonTitle?: string | null` - Button text (if not using `button` snippet)
20
+ - `buttonClass?: string` - CSS classes for the button
21
+ - `controlClass?: string` - CSS classes for the dropdown wrapper
22
+ - `toggleClass?: string` - CSS classes for the toggle container
23
+ - `inputControl?: boolean` - Style button as input control
24
+ - `fullBlock?: boolean` - Make button full width
25
+ - `bodyClass?: string` - CSS classes for dropdown body/menu
26
+ - `sameSize?: boolean` - Make dropdown menu same width as button
27
+ - `zIndex?: number` (default: 40) - Z-index for dropdown positioning
28
+ - `disabled?: boolean` - Disable the dropdown
29
+ - `hidden?: boolean` - Hide the dropdown
30
+ - `hideEmptyDDActions?: boolean` - Auto-hide if no actions provided
31
+ - `verbose?: boolean` - Enable console logging for debugging
32
+ - `dataColor?: TDefaultColorPalate` - Color theme for button
33
+ - `clientWidth?: number` ($bindable) - Width of the button element
34
+
35
+ ## Snippets
36
+
37
+ - `button?: Snippet` - Custom button content (overrides `buttonTitle`)
38
+ - `actions?: Snippet` - Custom menu content (in addition to or instead of `ddActions`)
39
+
40
+ ## IDDAction Interface
41
+
42
+ Menu items configuration:
43
+
44
+ ```typescript
45
+ interface IDDAction {
46
+ title?: string // Display text
47
+ key?: string | number // Unique key for item
48
+ divider?: boolean // Render as divider line
49
+ header?: boolean // Render as header text (bold, no click)
50
+ dividerGroup?: string // Auto-add divider when group changes
51
+ headerGroup?: string // Auto-add header when group changes
52
+ active?: boolean // Highlight as active/selected
53
+ disabled?: boolean // Disable interaction
54
+ hidden?: boolean // Hide from menu
55
+ indented?: boolean // Indent item (for hierarchy)
56
+
57
+ // Actions
58
+ action?: () => void // Click handler
59
+ href?: string // Navigation URL
60
+ hrefReplace?: boolean // Use replaceState navigation
61
+ hrefDownload?: string // Download URL
62
+ hrefDownloadFilename?: string // Filename for download
63
+
64
+ // Visual
65
+ faProps?: IFAProps // FontAwesome icon
66
+ imageHref?: string // Image URL for icon
67
+
68
+ // Alternate action (right side button)
69
+ alternateAction?: () => void // Right-side button action
70
+ alternateTitle?: string // Right-side button text
71
+ alternateFAProps?: IFAProps // Right-side button icon
72
+ alternateNoClose?: boolean // Keep menu open after alternate action
73
+
74
+ // Behavior
75
+ noCloseMenu?: boolean // Keep menu open after action
76
+ }
77
+ ```
78
+
79
+ ## Keyboard Navigation
80
+
81
+ - **Arrow Down:** Open menu / Move to next item
82
+ - **Arrow Up:** Move to previous item
83
+ - **Enter:** Execute selected item action
84
+ - **Tab:** Close menu
85
+ - **Type letter:** Jump to first item starting with that letter
86
+
87
+ ## Usage Examples
88
+
89
+ ```svelte
90
+ <script>
91
+ import { DropDown } from 'intelliwaketssveltekitv25';
92
+ import { faEdit, faTrash, faCopy } from '@fortawesome/free-solid-svg-icons';
93
+
94
+ let showMenu = $state(false);
95
+ let selectedOption = $state('option1');
96
+
97
+ const actions = [
98
+ {
99
+ title: 'Edit',
100
+ faProps: { icon: faEdit },
101
+ action: () => editItem()
102
+ },
103
+ {
104
+ title: 'Duplicate',
105
+ faProps: { icon: faCopy },
106
+ action: () => duplicateItem()
107
+ },
108
+ { divider: true },
109
+ {
110
+ title: 'Delete',
111
+ faProps: { icon: faTrash },
112
+ action: () => deleteItem()
113
+ }
114
+ ];
115
+ </script>
116
+
117
+ <!-- Simple action menu -->
118
+ <DropDown
119
+ bind:show={showMenu}
120
+ buttonTitle="Actions"
121
+ ddActions={actions}
122
+ />
123
+
124
+ <!-- As select replacement with active indicator -->
125
+ <DropDown
126
+ bind:show={showOptions}
127
+ buttonTitle={selectedOption}
128
+ ddActions={[
129
+ {
130
+ title: 'Option 1',
131
+ active: selectedOption === 'option1',
132
+ action: () => selectedOption = 'option1'
133
+ },
134
+ {
135
+ title: 'Option 2',
136
+ active: selectedOption === 'option2',
137
+ action: () => selectedOption = 'option2'
138
+ },
139
+ {
140
+ title: 'Option 3',
141
+ active: selectedOption === 'option3',
142
+ action: () => selectedOption = 'option3'
143
+ }
144
+ ]}
145
+ inputControl
146
+ />
147
+
148
+ <!-- With navigation links -->
149
+ <DropDown
150
+ buttonTitle="Navigate"
151
+ ddActions={[
152
+ { title: 'Dashboard', href: '/dashboard' },
153
+ { title: 'Settings', href: '/settings' },
154
+ { divider: true },
155
+ { title: 'Logout', href: '/logout', hrefReplace: true }
156
+ ]}
157
+ />
158
+
159
+ <!-- With grouped items -->
160
+ <DropDown
161
+ buttonTitle="Options"
162
+ ddActions={[
163
+ { title: 'File Operations', header: true },
164
+ { title: 'Open', action: () => open() },
165
+ { title: 'Save', action: () => save() },
166
+ { divider: true },
167
+ { title: 'Edit Operations', header: true },
168
+ { title: 'Cut', action: () => cut() },
169
+ { title: 'Copy', action: () => copy() },
170
+ { title: 'Paste', action: () => paste() }
171
+ ]}
172
+ />
173
+
174
+ <!-- With auto-grouping by headerGroup -->
175
+ <DropDown
176
+ buttonTitle="Grouped"
177
+ ddActions={[
178
+ { title: 'Item 1', headerGroup: 'Group A', action: () => {} },
179
+ { title: 'Item 2', headerGroup: 'Group A', action: () => {} },
180
+ { title: 'Item 3', headerGroup: 'Group B', action: () => {} },
181
+ { title: 'Item 4', headerGroup: 'Group B', action: () => {} }
182
+ ]}
183
+ />
184
+ <!-- Automatically adds headers "Group A" and "Group B" -->
185
+
186
+ <!-- Custom button content -->
187
+ <DropDown bind:show={showCustom} ddActions={actions}>
188
+ {#snippet button()}
189
+ <Icon icon={faEllipsisV} />
190
+ More Options
191
+ {/snippet}
192
+ </DropDown>
193
+
194
+ <!-- Full width dropdown -->
195
+ <DropDown
196
+ buttonTitle="Select Option"
197
+ ddActions={options}
198
+ fullBlock
199
+ sameSize
200
+ inputControl
201
+ />
202
+
203
+ <!-- With disabled items -->
204
+ <DropDown
205
+ buttonTitle="Actions"
206
+ ddActions={[
207
+ { title: 'Available Action', action: () => {} },
208
+ { title: 'Coming Soon', disabled: true },
209
+ { title: 'Premium Only', disabled: true }
210
+ ]}
211
+ />
212
+
213
+ <!-- With alternate actions (two buttons per row) -->
214
+ <DropDown
215
+ buttonTitle="Files"
216
+ ddActions={[
217
+ {
218
+ title: 'document.pdf',
219
+ action: () => openFile('document.pdf'),
220
+ alternateAction: () => downloadFile('document.pdf'),
221
+ alternateFAProps: { icon: faDownload }
222
+ }
223
+ ]}
224
+ />
225
+
226
+ <!-- Download action -->
227
+ <DropDown
228
+ buttonTitle="Export"
229
+ ddActions={[
230
+ {
231
+ title: 'Export as CSV',
232
+ hrefDownload: '/api/export?format=csv',
233
+ hrefDownloadFilename: 'data.csv'
234
+ },
235
+ {
236
+ title: 'Export as PDF',
237
+ hrefDownload: '/api/export?format=pdf',
238
+ hrefDownloadFilename: 'data.pdf'
239
+ }
240
+ ]}
241
+ />
242
+ ```
243
+
244
+ ## Common Mistakes
245
+
246
+ - ❌ Not providing `action`, `href`, or `hrefDownload` for menu items
247
+ ✅ Correct: Every menu item (except dividers/headers) needs an action
248
+
249
+ - ❌ Using DropDown when MultiSelect is more appropriate
250
+ ✅ Correct: Use `MultiSelect` for selecting multiple options, `DropDown` for single actions/selections
251
+
252
+ - ❌ Forgetting to handle menu close in custom `noCloseMenu` actions
253
+ ✅ Correct: Manually set `show = false` when using `noCloseMenu: true`
254
+
255
+ - ❌ Not using `bind:show` for controlled dropdowns
256
+ ✅ Correct: `<DropDown bind:show={showMenu}>` to control open/close state
257
+
258
+ - ❌ Using complex objects as `key` without providing unique string/number keys
259
+ ✅ Correct: Always provide `key` prop for array items
260
+
261
+ ## Related Components
262
+
263
+ - `DropDownControl` - Lower-level dropdown control (used internally by DropDown)
264
+ - `MultiSelect` - For selecting multiple options from a list
265
+ - `Importer` - Specialized dropdown for file imports
266
+
267
+ ## Storybook
268
+
269
+ See `Components/DropDown` stories