@lapis-notes/lapis-plugin-docs 0.0.0-stage → 0.1.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 (196) hide show
  1. package/README.md +188 -2
  2. package/dist/docs/DocsBlockMarkdownEditor.svelte +37 -0
  3. package/dist/docs/DocsBlockMarkdownEditor.svelte.d.ts +13 -0
  4. package/dist/docs/DocsBlockRefPicker.svelte +75 -0
  5. package/dist/docs/DocsBlockRefPicker.svelte.d.ts +11 -0
  6. package/dist/docs/DocsBookEditor.svelte +141 -0
  7. package/dist/docs/DocsBookEditor.svelte.d.ts +10 -0
  8. package/dist/docs/DocsBookNavigation.svelte +838 -0
  9. package/dist/docs/DocsBookNavigation.svelte.d.ts +11 -0
  10. package/dist/docs/DocsBooksColumn.svelte +82 -0
  11. package/dist/docs/DocsBooksColumn.svelte.d.ts +10 -0
  12. package/dist/docs/DocsCardBackMatter.svelte +32 -0
  13. package/dist/docs/DocsCardBackMatter.svelte.d.ts +10 -0
  14. package/dist/docs/DocsCardBlockExtra.svelte +43 -0
  15. package/dist/docs/DocsCardBlockExtra.svelte.d.ts +9 -0
  16. package/dist/docs/DocsCardPreviewFlip.svelte +143 -0
  17. package/dist/docs/DocsCardPreviewFlip.svelte.d.ts +17 -0
  18. package/dist/docs/DocsCardPreviewStack.svelte +135 -0
  19. package/dist/docs/DocsCardPreviewStack.svelte.d.ts +13 -0
  20. package/dist/docs/DocsCardPrompt.svelte +33 -0
  21. package/dist/docs/DocsCardPrompt.svelte.d.ts +8 -0
  22. package/dist/docs/DocsCardReviewAnswer.svelte +45 -0
  23. package/dist/docs/DocsCardReviewAnswer.svelte.d.ts +8 -0
  24. package/dist/docs/DocsCardTagChips.svelte +19 -0
  25. package/dist/docs/DocsCardTagChips.svelte.d.ts +7 -0
  26. package/dist/docs/DocsCardsPanel.svelte +232 -0
  27. package/dist/docs/DocsCardsPanel.svelte.d.ts +17 -0
  28. package/dist/docs/DocsCardsReview.css +1050 -0
  29. package/dist/docs/DocsChapterFooter.svelte +248 -0
  30. package/dist/docs/DocsChapterFooter.svelte.d.ts +11 -0
  31. package/dist/docs/DocsChapterScrollRing.svelte +101 -0
  32. package/dist/docs/DocsChapterScrollRing.svelte.d.ts +6 -0
  33. package/dist/docs/DocsDisplayPanel.css +627 -0
  34. package/dist/docs/DocsDisplayPanel.svelte +78 -0
  35. package/dist/docs/DocsDisplayPanel.svelte.d.ts +16 -0
  36. package/dist/docs/DocsEditorPanel.css +325 -0
  37. package/dist/docs/DocsEditorPanel.svelte +570 -0
  38. package/dist/docs/DocsEditorPanel.svelte.d.ts +22 -0
  39. package/dist/docs/DocsFlipCard.svelte +68 -0
  40. package/dist/docs/DocsFlipCard.svelte.d.ts +8 -0
  41. package/dist/docs/DocsFolderDirectoryList.svelte +206 -0
  42. package/dist/docs/DocsFolderDirectoryList.svelte.d.ts +14 -0
  43. package/dist/docs/DocsFolderPanel.css +357 -0
  44. package/dist/docs/DocsFolderPanel.svelte +102 -0
  45. package/dist/docs/DocsFolderPanel.svelte.d.ts +17 -0
  46. package/dist/docs/DocsLinksPanel.css +61 -0
  47. package/dist/docs/DocsLinksPanel.svelte +55 -0
  48. package/dist/docs/DocsLinksPanel.svelte.d.ts +11 -0
  49. package/dist/docs/DocsMarkdownField.svelte +93 -0
  50. package/dist/docs/DocsMarkdownField.svelte.d.ts +11 -0
  51. package/dist/docs/DocsMarkdownPreview.svelte +278 -0
  52. package/dist/docs/DocsMarkdownPreview.svelte.d.ts +17 -0
  53. package/dist/docs/DocsReviewPanel.svelte +63 -0
  54. package/dist/docs/DocsReviewPanel.svelte.d.ts +11 -0
  55. package/dist/docs/DocsRichBlock.svelte +291 -0
  56. package/dist/docs/DocsRichBlock.svelte.d.ts +13 -0
  57. package/dist/docs/DocsRichDisplay.svelte +316 -0
  58. package/dist/docs/DocsRichDisplay.svelte.d.ts +14 -0
  59. package/dist/docs/DocsSlidesPreview.css +243 -0
  60. package/dist/docs/DocsSlidesPreview.svelte +206 -0
  61. package/dist/docs/DocsSlidesPreview.svelte.d.ts +10 -0
  62. package/dist/docs/DocsWorkspace.css +570 -0
  63. package/dist/docs/DocsWorkspace.svelte +831 -0
  64. package/dist/docs/DocsWorkspace.svelte.d.ts +48 -0
  65. package/dist/docs/DocsWorkspaceToolbar.svelte +211 -0
  66. package/dist/docs/DocsWorkspaceToolbar.svelte.d.ts +27 -0
  67. package/dist/docs/candidates/ChoiceMenu.svelte +114 -0
  68. package/dist/docs/candidates/ChoiceMenu.svelte.d.ts +10 -0
  69. package/dist/docs/candidates/FlipCardDeck.svelte +800 -0
  70. package/dist/docs/candidates/FlipCardDeck.svelte.d.ts +36 -0
  71. package/dist/docs/candidates/MarkdownFloatingToc.svelte +313 -0
  72. package/dist/docs/candidates/MarkdownFloatingToc.svelte.d.ts +14 -0
  73. package/dist/docs/candidates/SearchableChoicePicker.css +161 -0
  74. package/dist/docs/candidates/SearchableChoicePicker.svelte +208 -0
  75. package/dist/docs/candidates/SearchableChoicePicker.svelte.d.ts +19 -0
  76. package/dist/docs/candidates/StructuredTable.svelte +98 -0
  77. package/dist/docs/candidates/StructuredTable.svelte.d.ts +8 -0
  78. package/dist/docs/candidates/StructuredTableEditor.svelte +374 -0
  79. package/dist/docs/candidates/StructuredTableEditor.svelte.d.ts +12 -0
  80. package/dist/docs/candidates/TagEditor.svelte +31 -0
  81. package/dist/docs/candidates/TagEditor.svelte.d.ts +14 -0
  82. package/dist/docs/candidates/WorkspaceHistoryControls.svelte +112 -0
  83. package/dist/docs/candidates/WorkspaceHistoryControls.svelte.d.ts +8 -0
  84. package/dist/docs/candidates/markdown-floating-toc.css +151 -0
  85. package/dist/docs/candidates/markdown-floating-toc.d.ts +1 -0
  86. package/dist/docs/candidates/markdown-floating-toc.js +1 -0
  87. package/dist/docs/docs-anki-import.d.ts +34 -0
  88. package/dist/docs/docs-anki-import.js +516 -0
  89. package/dist/docs/docs-book-view-model.d.ts +9 -0
  90. package/dist/docs/docs-book-view-model.js +71 -0
  91. package/dist/docs/docs-card-answer-markdown.d.ts +1 -0
  92. package/dist/docs/docs-card-answer-markdown.js +63 -0
  93. package/dist/docs/docs-card-display.d.ts +10 -0
  94. package/dist/docs/docs-card-display.js +18 -0
  95. package/dist/docs/docs-display-focus.d.ts +7 -0
  96. package/dist/docs/docs-display-focus.js +42 -0
  97. package/dist/docs/docs-document-surface.d.ts +7 -0
  98. package/dist/docs/docs-document-surface.js +25 -0
  99. package/dist/docs/docs-folder-directory.d.ts +21 -0
  100. package/dist/docs/docs-folder-directory.js +56 -0
  101. package/dist/docs/docs-link-mode.d.ts +16 -0
  102. package/dist/docs/docs-link-mode.js +94 -0
  103. package/dist/docs/docs-workspace-helpers.d.ts +25 -0
  104. package/dist/docs/docs-workspace-helpers.js +195 -0
  105. package/dist/docs/docs.tokens.css +13 -0
  106. package/dist/docs/import-anki-package.d.ts +10 -0
  107. package/dist/docs/import-anki-package.js +30 -0
  108. package/dist/docs/lapis-markdown-extensions.d.ts +9 -0
  109. package/dist/docs/lapis-markdown-extensions.js +128 -0
  110. package/dist/docs/pin-reading-chrome.d.ts +4 -0
  111. package/dist/docs/pin-reading-chrome.js +32 -0
  112. package/dist/docs/sidebar/DocsFoldersSidebar.svelte +134 -0
  113. package/dist/docs/sidebar/DocsFoldersSidebar.svelte.d.ts +15 -0
  114. package/dist/docs/sidebar/DocsLinksSidebar.svelte +105 -0
  115. package/dist/docs/sidebar/DocsLinksSidebar.svelte.d.ts +11 -0
  116. package/dist/docs/sidebar/DocsSidebar.css +346 -0
  117. package/dist/docs/sidebar/DocsSidebar.svelte +430 -0
  118. package/dist/docs/sidebar/DocsSidebar.svelte.d.ts +18 -0
  119. package/dist/docs/sidebar/DocsSidebarHost.svelte +139 -0
  120. package/dist/docs/sidebar/DocsSidebarHost.svelte.d.ts +12 -0
  121. package/dist/docs/sidebar/DocsTagsSidebar.svelte +176 -0
  122. package/dist/docs/sidebar/DocsTagsSidebar.svelte.d.ts +12 -0
  123. package/dist/docs/sidebar/docs-book-note-tree.d.ts +23 -0
  124. package/dist/docs/sidebar/docs-book-note-tree.js +88 -0
  125. package/dist/docs/sidebar/docs-note-sort.d.ts +26 -0
  126. package/dist/docs/sidebar/docs-note-sort.js +54 -0
  127. package/dist/docs/sidebar/docs-sidebar-filters.d.ts +17 -0
  128. package/dist/docs/sidebar/docs-sidebar-filters.js +74 -0
  129. package/dist/docs/sidebar/docs-sidebar-ui-storage.d.ts +24 -0
  130. package/dist/docs/sidebar/docs-sidebar-ui-storage.js +87 -0
  131. package/dist/docs/sidebar/docs-source-folder-tree.d.ts +16 -0
  132. package/dist/docs/sidebar/docs-source-folder-tree.js +143 -0
  133. package/dist/docs/sidebar/docs-tag-tree.d.ts +17 -0
  134. package/dist/docs/sidebar/docs-tag-tree.js +84 -0
  135. package/dist/docs-cards-view.d.ts +18 -0
  136. package/dist/docs-cards-view.js +52 -0
  137. package/dist/docs-document-view.d.ts +24 -0
  138. package/dist/docs-document-view.js +127 -0
  139. package/dist/docs-document-workspace.svelte +215 -0
  140. package/dist/docs-document-workspace.svelte.d.ts +16 -0
  141. package/dist/docs-index.d.ts +5 -0
  142. package/dist/docs-index.js +49 -0
  143. package/dist/docs-path.d.ts +6 -0
  144. package/dist/docs-path.js +10 -0
  145. package/dist/docs-plugin.d.ts +21 -0
  146. package/dist/docs-plugin.js +140 -0
  147. package/dist/docs-repository.d.ts +15 -0
  148. package/dist/docs-repository.js +75 -0
  149. package/dist/docs-review-view.d.ts +18 -0
  150. package/dist/docs-review-view.js +52 -0
  151. package/dist/docs-settings-tab.d.ts +8 -0
  152. package/dist/docs-settings-tab.js +25 -0
  153. package/dist/docs-settings.d.ts +17 -0
  154. package/dist/docs-settings.js +38 -0
  155. package/dist/docs-view.d.ts +22 -0
  156. package/dist/docs-view.js +51 -0
  157. package/dist/docs-workspace.svelte +394 -0
  158. package/dist/docs-workspace.svelte.d.ts +15 -0
  159. package/dist/index.d.ts +12 -0
  160. package/dist/index.js +12 -0
  161. package/dist/model/book.d.ts +6 -0
  162. package/dist/model/book.js +101 -0
  163. package/dist/model/coding-runtime.d.ts +4 -0
  164. package/dist/model/coding-runtime.js +9 -0
  165. package/dist/model/docs-card-context.d.ts +32 -0
  166. package/dist/model/docs-card-context.js +89 -0
  167. package/dist/model/docs-card-preview-stack-motion.d.ts +51 -0
  168. package/dist/model/docs-card-preview-stack-motion.js +94 -0
  169. package/dist/model/docs-media-context.d.ts +9 -0
  170. package/dist/model/docs-media-context.js +18 -0
  171. package/dist/model/docs-note-list.d.ts +8 -0
  172. package/dist/model/docs-note-list.js +25 -0
  173. package/dist/model/docs-relative-media.d.ts +30 -0
  174. package/dist/model/docs-relative-media.js +116 -0
  175. package/dist/model/docs-review-constants.d.ts +4 -0
  176. package/dist/model/docs-review-constants.js +13 -0
  177. package/dist/model/fsrs.d.ts +14 -0
  178. package/dist/model/fsrs.js +153 -0
  179. package/dist/model/graph.d.ts +12 -0
  180. package/dist/model/graph.js +169 -0
  181. package/dist/model/index.d.ts +14 -0
  182. package/dist/model/index.js +14 -0
  183. package/dist/model/markdown-normalize.d.ts +11 -0
  184. package/dist/model/markdown-normalize.js +755 -0
  185. package/dist/model/markdown.d.ts +167 -0
  186. package/dist/model/markdown.js +365 -0
  187. package/dist/model/sample.d.ts +7 -0
  188. package/dist/model/sample.js +524 -0
  189. package/dist/model/types.d.ts +328 -0
  190. package/dist/model/types.js +1 -0
  191. package/dist/model/workspace.d.ts +6 -0
  192. package/dist/model/workspace.js +81 -0
  193. package/dist/storybook.css +52 -0
  194. package/dist/styles.css +1 -0
  195. package/manifest.json +11 -0
  196. package/package.json +147 -4
@@ -0,0 +1,208 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DESIGN_CORE_CANDIDATE: SearchableChoicePicker — ported for Docs; promote to design-core/forms later.
4
+ */
5
+ import "./SearchableChoicePicker.css";
6
+ import { Command as CommandPrimitive } from "bits-ui";
7
+ import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
8
+ import SearchIcon from "@lucide/svelte/icons/search";
9
+ import type { SearchableChoiceOption } from "@lapismd/design-core/forms";
10
+
11
+ let {
12
+ value = "",
13
+ options,
14
+ placeholder = "Select option",
15
+ searchPlaceholder = "Search...",
16
+ emptyLabel = "No options found.",
17
+ ariaLabel = "Select option",
18
+ allowCustom = true,
19
+ normalize = (next: string) => next.trim().toLowerCase(),
20
+ onChange = () => {},
21
+ }: {
22
+ value?: string;
23
+ options: SearchableChoiceOption[];
24
+ placeholder?: string;
25
+ searchPlaceholder?: string;
26
+ emptyLabel?: string;
27
+ ariaLabel?: string;
28
+ allowCustom?: boolean;
29
+ normalize?: (value: string) => string;
30
+ onChange?: (value: string) => void;
31
+ } = $props();
32
+
33
+ let open = $state(false);
34
+ let query = $state("");
35
+ let root = $state<HTMLElement | null>(null);
36
+
37
+ const currentOption = $derived(
38
+ options.find((option) => option.value === value),
39
+ );
40
+ const triggerLabel = $derived(
41
+ currentOption?.label ?? (value.trim() || placeholder),
42
+ );
43
+ const showPlaceholder = $derived(!value.trim());
44
+
45
+ const filteredOptions = $derived.by(() => {
46
+ const needle = query.trim().toLowerCase();
47
+ return options.filter((option) => {
48
+ if (!needle) return true;
49
+ const haystack = [
50
+ option.value,
51
+ option.label,
52
+ ...(option.keywords ?? []),
53
+ ]
54
+ .join(" ")
55
+ .toLowerCase();
56
+ return haystack.includes(needle);
57
+ });
58
+ });
59
+
60
+ const trimmedQuery = $derived(query.trim());
61
+ const exactMatch = $derived(
62
+ trimmedQuery.length > 0 &&
63
+ options.some(
64
+ (option) =>
65
+ option.value.toLowerCase() === trimmedQuery.toLowerCase() ||
66
+ option.label.toLowerCase() === trimmedQuery.toLowerCase(),
67
+ ),
68
+ );
69
+ const canCreateCustom = $derived(
70
+ allowCustom && trimmedQuery.length > 0 && !exactMatch,
71
+ );
72
+
73
+ $effect(() => {
74
+ if (!open) return;
75
+
76
+ let armed = false;
77
+ const armTimer = setTimeout(() => {
78
+ armed = true;
79
+ }, 0);
80
+
81
+ function handlePointerDown(event: PointerEvent) {
82
+ if (!armed) return;
83
+ if (!(event.target instanceof Node)) return;
84
+ if (root?.contains(event.target)) return;
85
+ open = false;
86
+ }
87
+
88
+ document.addEventListener("pointerdown", handlePointerDown, true);
89
+ return () => {
90
+ clearTimeout(armTimer);
91
+ document.removeEventListener("pointerdown", handlePointerDown, true);
92
+ };
93
+ });
94
+
95
+ $effect(() => {
96
+ if (!open) query = "";
97
+ });
98
+
99
+ function select(nextValue: string) {
100
+ onChange(normalize(nextValue));
101
+ open = false;
102
+ }
103
+
104
+ function toggleOpen() {
105
+ open = !open;
106
+ }
107
+
108
+ function handleCustomSelect() {
109
+ if (!trimmedQuery) return;
110
+ select(trimmedQuery);
111
+ }
112
+ </script>
113
+
114
+ <div bind:this={root} class="cv-searchable-choice-picker" data-open={open}>
115
+ <button
116
+ type="button"
117
+ class="cv-searchable-choice-trigger"
118
+ class:is-placeholder={showPlaceholder}
119
+ aria-label={ariaLabel}
120
+ aria-haspopup="listbox"
121
+ aria-expanded={open}
122
+ onclick={toggleOpen}
123
+ >
124
+ <span class="cv-searchable-choice-trigger-label">{triggerLabel}</span>
125
+ <ChevronDownIcon aria-hidden="true" />
126
+ </button>
127
+
128
+ {#if open}
129
+ <div class="cv-searchable-choice-popover">
130
+ <CommandPrimitive.Root
131
+ class="cv-searchable-choice-command"
132
+ label={ariaLabel}
133
+ shouldFilter={false}
134
+ loop
135
+ >
136
+ <div class="cv-searchable-choice-command-input-row">
137
+ <SearchIcon aria-hidden="true" />
138
+ <CommandPrimitive.Input
139
+ bind:value={query}
140
+ class="cv-searchable-choice-command-input"
141
+ placeholder={searchPlaceholder}
142
+ />
143
+ </div>
144
+ <CommandPrimitive.List class="cv-searchable-choice-command-list">
145
+ {#if filteredOptions.length > 0}
146
+ <CommandPrimitive.Group
147
+ class="cv-searchable-choice-command-group"
148
+ value="options"
149
+ forceMount
150
+ >
151
+ <CommandPrimitive.GroupItems>
152
+ {#each filteredOptions as option (option.value)}
153
+ <CommandPrimitive.Item
154
+ class={[
155
+ "cv-searchable-choice-command-item",
156
+ option.value === value && "is-active",
157
+ ]
158
+ .filter(Boolean)
159
+ .join(" ")}
160
+ value={option.value}
161
+ keywords={[option.label, ...(option.keywords ?? [])]}
162
+ onSelect={() => select(option.value)}
163
+ >
164
+ <span class="cv-searchable-choice-option-label">
165
+ {option.label}
166
+ </span>
167
+ {#if option.label.toLowerCase() !== option.value.toLowerCase()}
168
+ <span class="cv-searchable-choice-option-value">
169
+ {option.value}
170
+ </span>
171
+ {/if}
172
+ </CommandPrimitive.Item>
173
+ {/each}
174
+ </CommandPrimitive.GroupItems>
175
+ </CommandPrimitive.Group>
176
+ {/if}
177
+
178
+ {#if canCreateCustom}
179
+ <CommandPrimitive.Group
180
+ class="cv-searchable-choice-command-group"
181
+ value="create"
182
+ forceMount
183
+ >
184
+ <CommandPrimitive.GroupItems>
185
+ <CommandPrimitive.Item
186
+ class="cv-searchable-choice-command-item"
187
+ value={`__create__:${trimmedQuery}`}
188
+ onSelect={handleCustomSelect}
189
+ >
190
+ <span class="cv-searchable-choice-create-label">
191
+ Use "{trimmedQuery}"
192
+ </span>
193
+ </CommandPrimitive.Item>
194
+ </CommandPrimitive.GroupItems>
195
+ </CommandPrimitive.Group>
196
+ {:else if filteredOptions.length === 0}
197
+ <CommandPrimitive.Empty
198
+ class="cv-searchable-choice-empty"
199
+ forceMount
200
+ >
201
+ {emptyLabel}
202
+ </CommandPrimitive.Empty>
203
+ {/if}
204
+ </CommandPrimitive.List>
205
+ </CommandPrimitive.Root>
206
+ </div>
207
+ {/if}
208
+ </div>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * DESIGN_CORE_CANDIDATE: SearchableChoicePicker — ported for Docs; promote to design-core/forms later.
3
+ */
4
+ import "./SearchableChoicePicker.css";
5
+ import type { SearchableChoiceOption } from "@lapismd/design-core/forms";
6
+ type $$ComponentProps = {
7
+ value?: string;
8
+ options: SearchableChoiceOption[];
9
+ placeholder?: string;
10
+ searchPlaceholder?: string;
11
+ emptyLabel?: string;
12
+ ariaLabel?: string;
13
+ allowCustom?: boolean;
14
+ normalize?: (value: string) => string;
15
+ onChange?: (value: string) => void;
16
+ };
17
+ declare const SearchableChoicePicker: import("svelte").Component<$$ComponentProps, {}, "">;
18
+ type SearchableChoicePicker = ReturnType<typeof SearchableChoicePicker>;
19
+ export default SearchableChoicePicker;
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DESIGN_CORE_CANDIDATE: StructuredTable — ported for Docs; promote to design-core/forms later.
4
+ */
5
+ let {
6
+ columns = [],
7
+ rows = [],
8
+ empty = ''
9
+ }: {
10
+ columns?: string[];
11
+ rows?: string[][];
12
+ empty?: string;
13
+ } = $props();
14
+
15
+ const visibleColumns = $derived(columns.filter((column) => column.trim()));
16
+ </script>
17
+
18
+ {#if visibleColumns.length}
19
+ <div class="cv-structured-table-wrap">
20
+ <table class="cv-structured-table">
21
+ <thead>
22
+ <tr>
23
+ {#each visibleColumns as column, columnIndex (`${column}-${columnIndex}`)}
24
+ <th>{column}</th>
25
+ {/each}
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ {#each rows as row, rowIndex (rowIndex)}
30
+ <tr>
31
+ {#each visibleColumns as _, cellIndex (cellIndex)}
32
+ <td>{row[cellIndex] ?? ''}</td>
33
+ {/each}
34
+ </tr>
35
+ {/each}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ {:else if empty}
40
+ <p class="cv-structured-table-empty">{empty}</p>
41
+ {/if}
42
+
43
+ <style>
44
+ .cv-structured-table-wrap {
45
+ overflow-x: auto;
46
+ width: 100%;
47
+ }
48
+
49
+ .cv-structured-table {
50
+ width: 100%;
51
+ border: 1px solid var(--kanban-border, rgb(226 232 240));
52
+ border-collapse: separate;
53
+ border-radius: 0.6rem;
54
+ border-spacing: 0;
55
+ background: var(--kanban-card, rgb(255 255 255));
56
+ font-size: 0.92rem;
57
+ overflow: hidden;
58
+ }
59
+
60
+ .cv-structured-table th,
61
+ .cv-structured-table td {
62
+ border-bottom: 1px solid color-mix(in srgb, var(--kanban-border, rgb(226 232 240)) 84%, transparent);
63
+ padding: 0.55rem 0.7rem;
64
+ text-align: left;
65
+ vertical-align: top;
66
+ }
67
+
68
+ .cv-structured-table th {
69
+ background: color-mix(in srgb, var(--card-color, var(--column-color, var(--kanban-accent, rgb(14 165 233)))) 10%, var(--kanban-card, rgb(255 255 255)));
70
+ font-size: 0.78rem;
71
+ font-weight: 900;
72
+ white-space: nowrap;
73
+ }
74
+
75
+ .cv-structured-table th + th,
76
+ .cv-structured-table td + td {
77
+ border-left: 1px solid color-mix(in srgb, var(--kanban-border, rgb(226 232 240)) 74%, transparent);
78
+ }
79
+
80
+ .cv-structured-table tbody tr:nth-child(even) td {
81
+ background: color-mix(in srgb, var(--kanban-card-foreground, rgb(15 23 42)) 3%, transparent);
82
+ }
83
+
84
+ .cv-structured-table tbody tr:last-child td {
85
+ border-bottom: 0;
86
+ }
87
+
88
+ .cv-structured-table td {
89
+ overflow-wrap: anywhere;
90
+ }
91
+
92
+ .cv-structured-table-empty {
93
+ margin: 0;
94
+ color: var(--kanban-muted);
95
+ font-size: 0.85rem;
96
+ font-weight: 700;
97
+ }
98
+ </style>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ columns?: string[];
3
+ rows?: string[][];
4
+ empty?: string;
5
+ };
6
+ declare const StructuredTable: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type StructuredTable = ReturnType<typeof StructuredTable>;
8
+ export default StructuredTable;
@@ -0,0 +1,374 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DESIGN_CORE_CANDIDATE: StructuredTableEditor — ported for Docs; promote to design-core/forms later.
4
+ */
5
+ import ArrowDownIcon from '@lucide/svelte/icons/arrow-down';
6
+ import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
7
+ import ArrowRightIcon from '@lucide/svelte/icons/arrow-right';
8
+ import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
9
+ import PlusIcon from '@lucide/svelte/icons/plus';
10
+ import Trash2Icon from '@lucide/svelte/icons/trash-2';
11
+ import { autosizeTextarea } from '@lapismd/design-core/forms';
12
+
13
+ type TablePatch = {
14
+ columns: string[];
15
+ rows: string[][];
16
+ };
17
+
18
+ let {
19
+ columns = [],
20
+ rows = [],
21
+ onChange = () => {}
22
+ }: {
23
+ columns?: string[];
24
+ rows?: string[][];
25
+ onChange?: (table: TablePatch) => void;
26
+ } = $props();
27
+
28
+ const normalizedColumns = $derived(columns.length ? columns : ['Column']);
29
+ const normalizedRows = $derived(rows.length ? normalizeRows(rows, normalizedColumns.length) : [[]]);
30
+
31
+ function emit(nextColumns: string[], nextRows: string[][]) {
32
+ onChange({
33
+ columns: nextColumns,
34
+ rows: normalizeRows(nextRows, nextColumns.length)
35
+ });
36
+ }
37
+
38
+ function updateColumn(index: number, value: string) {
39
+ const nextColumns = [...normalizedColumns];
40
+ nextColumns[index] = value;
41
+ emit(nextColumns, normalizedRows);
42
+ }
43
+
44
+ function addColumn() {
45
+ const nextColumns = [...normalizedColumns, `Column ${normalizedColumns.length + 1}`];
46
+ emit(
47
+ nextColumns,
48
+ normalizedRows.map((row) => [...row, ''])
49
+ );
50
+ }
51
+
52
+ function removeColumn(index: number) {
53
+ const nextColumns = normalizedColumns.filter((_, columnIndex) => columnIndex !== index);
54
+ const safeColumns = nextColumns.length ? nextColumns : ['Column'];
55
+ emit(
56
+ safeColumns,
57
+ normalizedRows.map((row) => row.filter((_, cellIndex) => cellIndex !== index))
58
+ );
59
+ }
60
+
61
+ function moveColumn(index: number, direction: -1 | 1) {
62
+ const targetIndex = index + direction;
63
+ if (targetIndex < 0 || targetIndex >= normalizedColumns.length) return;
64
+
65
+ const nextColumns = [...normalizedColumns];
66
+ [nextColumns[index], nextColumns[targetIndex]] = [nextColumns[targetIndex], nextColumns[index]];
67
+
68
+ const nextRows = normalizedRows.map((row) => {
69
+ const nextRow = [...row];
70
+ [nextRow[index], nextRow[targetIndex]] = [nextRow[targetIndex], nextRow[index]];
71
+ return nextRow;
72
+ });
73
+
74
+ emit(nextColumns, nextRows);
75
+ }
76
+
77
+ function updateCell(rowIndex: number, columnIndex: number, value: string) {
78
+ const nextRows = normalizedRows.map((row, index) => (index === rowIndex ? [...row] : row));
79
+ nextRows[rowIndex][columnIndex] = value;
80
+ emit(normalizedColumns, nextRows);
81
+ }
82
+
83
+ function addRow() {
84
+ emit(normalizedColumns, [...normalizedRows, Array.from({ length: normalizedColumns.length }, () => '')]);
85
+ }
86
+
87
+ function removeRow(index: number) {
88
+ const nextRows = normalizedRows.filter((_, rowIndex) => rowIndex !== index);
89
+ emit(normalizedColumns, nextRows.length ? nextRows : [[]]);
90
+ }
91
+
92
+ function moveRow(index: number, direction: -1 | 1) {
93
+ const targetIndex = index + direction;
94
+ if (targetIndex < 0 || targetIndex >= normalizedRows.length) return;
95
+
96
+ const nextRows = normalizedRows.map((row) => [...row]);
97
+ [nextRows[index], nextRows[targetIndex]] = [nextRows[targetIndex], nextRows[index]];
98
+ emit(normalizedColumns, nextRows);
99
+ }
100
+
101
+ function normalizeRows(value: string[][], width: number) {
102
+ return value.map((row) => Array.from({ length: width }, (_, index) => row[index] ?? ''));
103
+ }
104
+ </script>
105
+
106
+ <div class="cv-structured-table-editor">
107
+ <div class="cv-structured-table-editor__scroll">
108
+ <table>
109
+ <thead>
110
+ <tr>
111
+ {#each normalizedColumns as column, columnIndex (columnIndex)}
112
+ <th>
113
+ <div class="cv-structured-table-editor__header-cell">
114
+ <input
115
+ aria-label={`Column ${columnIndex + 1}`}
116
+ value={column}
117
+ placeholder={`Column ${columnIndex + 1}`}
118
+ oninput={(event) => updateColumn(columnIndex, event.currentTarget.value)}
119
+ />
120
+ <div class="cv-structured-table-editor__cell-actions">
121
+ <button
122
+ type="button"
123
+ aria-label={`Move column ${columnIndex + 1} left`}
124
+ disabled={columnIndex === 0}
125
+ onclick={() => moveColumn(columnIndex, -1)}
126
+ >
127
+ <ArrowLeftIcon aria-hidden="true" />
128
+ </button>
129
+ <button
130
+ type="button"
131
+ aria-label={`Move column ${columnIndex + 1} right`}
132
+ disabled={columnIndex === normalizedColumns.length - 1}
133
+ onclick={() => moveColumn(columnIndex, 1)}
134
+ >
135
+ <ArrowRightIcon aria-hidden="true" />
136
+ </button>
137
+ <button
138
+ type="button"
139
+ aria-label={`Remove column ${columnIndex + 1}`}
140
+ onclick={() => removeColumn(columnIndex)}
141
+ >
142
+ <Trash2Icon aria-hidden="true" />
143
+ </button>
144
+ </div>
145
+ </div>
146
+ </th>
147
+ {/each}
148
+ <th class="cv-structured-table-editor__action-column">
149
+ <button type="button" aria-label="Add column" onclick={addColumn}>
150
+ <PlusIcon aria-hidden="true" />
151
+ </button>
152
+ </th>
153
+ </tr>
154
+ </thead>
155
+ <tbody>
156
+ {#each normalizedRows as row, rowIndex (rowIndex)}
157
+ <tr>
158
+ {#each normalizedColumns as _, columnIndex (columnIndex)}
159
+ <td>
160
+ <textarea
161
+ rows="1"
162
+ aria-label={`Row ${rowIndex + 1} column ${columnIndex + 1}`}
163
+ value={row[columnIndex] ?? ''}
164
+ placeholder="Add value"
165
+ use:autosizeTextarea={row[columnIndex] ?? ''}
166
+ oninput={(event) =>
167
+ updateCell(rowIndex, columnIndex, event.currentTarget.value)}
168
+ ></textarea>
169
+ </td>
170
+ {/each}
171
+ <td class="cv-structured-table-editor__action-column">
172
+ <div class="cv-structured-table-editor__row-actions">
173
+ <div class="cv-structured-table-editor__row-move">
174
+ <button
175
+ type="button"
176
+ aria-label={`Move row ${rowIndex + 1} up`}
177
+ disabled={rowIndex === 0}
178
+ onclick={() => moveRow(rowIndex, -1)}
179
+ >
180
+ <ArrowUpIcon aria-hidden="true" />
181
+ </button>
182
+ <button
183
+ type="button"
184
+ aria-label={`Move row ${rowIndex + 1} down`}
185
+ disabled={rowIndex === normalizedRows.length - 1}
186
+ onclick={() => moveRow(rowIndex, 1)}
187
+ >
188
+ <ArrowDownIcon aria-hidden="true" />
189
+ </button>
190
+ </div>
191
+ <button type="button" aria-label={`Remove row ${rowIndex + 1}`} onclick={() => removeRow(rowIndex)}>
192
+ <Trash2Icon aria-hidden="true" />
193
+ </button>
194
+ </div>
195
+ </td>
196
+ </tr>
197
+ {/each}
198
+ </tbody>
199
+ </table>
200
+ </div>
201
+ <button type="button" class="cv-structured-table-editor__add-row" onclick={addRow}>
202
+ <PlusIcon aria-hidden="true" />
203
+ Add row
204
+ </button>
205
+ </div>
206
+
207
+ <style>
208
+ .cv-structured-table-editor {
209
+ display: grid;
210
+ gap: 0.55rem;
211
+ width: 100%;
212
+ padding-top: 0.35rem;
213
+ }
214
+
215
+ .cv-structured-table-editor__scroll {
216
+ overflow-x: auto;
217
+ border: 1px solid var(--kanban-border, rgb(226 232 240));
218
+ border-radius: 0.6rem;
219
+ background: var(--kanban-card, rgb(255 255 255));
220
+ }
221
+
222
+ .cv-structured-table-editor table {
223
+ width: 100%;
224
+ min-width: 28rem;
225
+ border-collapse: separate;
226
+ border-spacing: 0;
227
+ }
228
+
229
+ .cv-structured-table-editor th,
230
+ .cv-structured-table-editor td {
231
+ border-bottom: 1px solid color-mix(in srgb, var(--kanban-border, rgb(226 232 240)) 82%, transparent);
232
+ padding: 0;
233
+ vertical-align: top;
234
+ }
235
+
236
+ .cv-structured-table-editor thead th {
237
+ background: color-mix(in srgb, var(--card-color, var(--column-color, var(--kanban-accent, rgb(14 165 233)))) 9%, transparent);
238
+ }
239
+
240
+ .cv-structured-table-editor th + th,
241
+ .cv-structured-table-editor td + td {
242
+ border-left: 1px solid color-mix(in srgb, var(--kanban-border, rgb(226 232 240)) 72%, transparent);
243
+ }
244
+
245
+ .cv-structured-table-editor tbody tr:last-child td {
246
+ border-bottom: 0;
247
+ }
248
+
249
+ .cv-structured-table-editor__header-cell {
250
+ display: grid;
251
+ grid-template-columns: minmax(8rem, 1fr) auto;
252
+ align-items: center;
253
+ min-height: 2.35rem;
254
+ }
255
+
256
+ .cv-structured-table-editor input,
257
+ .cv-structured-table-editor textarea {
258
+ width: 100%;
259
+ border: 0;
260
+ background: transparent;
261
+ color: inherit;
262
+ font: inherit;
263
+ outline: 0;
264
+ padding: 0.48rem 0.58rem;
265
+ }
266
+
267
+ .cv-structured-table-editor input {
268
+ font-size: 0.8rem;
269
+ font-weight: 900;
270
+ }
271
+
272
+ .cv-structured-table-editor textarea {
273
+ min-height: 2.25rem;
274
+ resize: none;
275
+ }
276
+
277
+ .cv-structured-table-editor button {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ border: 0;
282
+ background: transparent;
283
+ color: var(--kanban-muted);
284
+ cursor: pointer;
285
+ font: inherit;
286
+ font-size: 0.82rem;
287
+ font-weight: 800;
288
+ }
289
+
290
+ .cv-structured-table-editor button:hover,
291
+ .cv-structured-table-editor button:focus-visible {
292
+ background: color-mix(in srgb, var(--kanban-card-foreground, rgb(15 23 42)) 6%, transparent);
293
+ color: var(--kanban-card-foreground, rgb(15 23 42));
294
+ outline: 0;
295
+ }
296
+
297
+ .cv-structured-table-editor button:disabled {
298
+ cursor: default;
299
+ opacity: 0.35;
300
+ }
301
+
302
+ .cv-structured-table-editor button:disabled:hover,
303
+ .cv-structured-table-editor button:disabled:focus-visible {
304
+ background: transparent;
305
+ color: var(--kanban-muted);
306
+ }
307
+
308
+ .cv-structured-table-editor button :global(svg) {
309
+ width: 0.95rem;
310
+ height: 0.95rem;
311
+ }
312
+
313
+ .cv-structured-table-editor__action-column {
314
+ width: 5.75rem;
315
+ min-width: 5.75rem;
316
+ text-align: center;
317
+ }
318
+
319
+ .cv-structured-table-editor tbody .cv-structured-table-editor__action-column {
320
+ vertical-align: middle;
321
+ }
322
+
323
+ .cv-structured-table-editor__action-column button {
324
+ width: 1.62rem;
325
+ min-height: 1.62rem;
326
+ border-radius: 0.28rem;
327
+ padding: 0;
328
+ }
329
+
330
+ .cv-structured-table-editor thead .cv-structured-table-editor__action-column button {
331
+ width: 100%;
332
+ min-height: 2.35rem;
333
+ border-radius: 0;
334
+ }
335
+
336
+ .cv-structured-table-editor__cell-actions,
337
+ .cv-structured-table-editor__row-actions {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ gap: 0.12rem;
342
+ padding-inline: 0 0.25rem;
343
+ }
344
+
345
+ .cv-structured-table-editor__row-actions {
346
+ min-height: 2.25rem;
347
+ }
348
+
349
+ .cv-structured-table-editor__row-move {
350
+ display: flex;
351
+ flex-direction: column;
352
+ align-items: center;
353
+ gap: 0;
354
+ }
355
+
356
+ .cv-structured-table-editor__row-actions button {
357
+ width: 1rem;
358
+ min-height: 1rem;
359
+ border-radius: 0.25rem;
360
+ }
361
+
362
+ .cv-structured-table-editor__row-actions button :global(svg) {
363
+ width: 0.75rem;
364
+ height: 0.75rem;
365
+ }
366
+
367
+ .cv-structured-table-editor__add-row {
368
+ justify-self: start;
369
+ gap: 0.4rem;
370
+ border: 1px dashed var(--kanban-border, rgb(226 232 240)) !important;
371
+ border-radius: 999px;
372
+ padding: 0.38rem 0.75rem;
373
+ }
374
+ </style>