@cocoar/vue-ui 3.1.0 → 3.2.0-beta.11
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/bin/cli.mjs +53 -0
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
- package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
- package/dist/components/data-list/data-list-builder.d.ts +288 -0
- package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
- package/dist/components/data-list/index.d.ts +12 -0
- package/dist/components/data-list/index.d.ts.map +1 -0
- package/dist/components/data-list/internal/compare.d.ts +14 -0
- package/dist/components/data-list/internal/compare.d.ts.map +1 -0
- package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
- package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
- package/dist/components/data-list/internal/search.d.ts +16 -0
- package/dist/components/data-list/internal/search.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
- package/dist/components/data-list/types.d.ts +185 -0
- package/dist/components/data-list/types.d.ts.map +1 -0
- package/dist/components/data-list/useDataListModel.d.ts +79 -0
- package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
- package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
- package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
- package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
- package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
- package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
- package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
- package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
- package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
- package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
- package/dist/composables/dragRegistry.d.ts +12 -0
- package/dist/composables/dragRegistry.d.ts.map +1 -1
- package/dist/composables/index.d.ts +2 -2
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDragDrop.d.ts +62 -6
- package/dist/composables/useDragDrop.d.ts.map +1 -1
- package/dist/composables/useVirtualList.d.ts +21 -1
- package/dist/composables/useVirtualList.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4508 -2039
- package/package.json +9 -3
- package/skills/cocoar-vue-ui/SKILL.md +244 -0
- package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
- package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
- package/skills/cocoar-vue-ui/references/components/button.md +252 -0
- package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +372 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1647 -0
- package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
- package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
- package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +486 -0
- package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
- package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
- package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
- package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
- package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
- package/skills/cocoar-vue-ui/references/components/card.md +151 -0
- package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
- package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
- package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
- package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
- package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
- package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
- package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
- package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
- package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
- package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
- package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
- package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
- package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
- package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
- package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
- package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
- package/skills/cocoar-vue-ui/references/components/link.md +235 -0
- package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
- package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
- package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
- package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
- package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
- package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
- package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
- package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
- package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
- package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
- package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
- package/skills/cocoar-vue-ui/references/components/note.md +96 -0
- package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
- package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
- package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
- package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
- package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
- package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
- package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
- package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
- package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
- package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
- package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
- package/skills/cocoar-vue-ui/references/components/select.md +463 -0
- package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
- package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
- package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
- package/skills/cocoar-vue-ui/references/components/table.md +170 -0
- package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
- package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
- package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
- package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
- package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
- package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
- package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
- package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
- package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
- package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
- package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
- package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
- package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
- package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
- package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
- package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
- package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
- package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
- package/skills/cocoar-vue-ui/references/guide/getting-started.md +116 -0
- package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
- package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
- package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/fragment-parser.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# Fragment Parser & Modal Routing
|
|
4
|
+
|
|
5
|
+
Parse URL fragments (hash portion) into structured routes with parameters. Combined with composables, this enables **deep-linkable modals** — open modals via URL, share links, and use browser back to close them.
|
|
6
|
+
|
|
7
|
+
> **Info: Separate Package**
|
|
8
|
+
>
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @cocoar/vue-fragment-parser
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
parseFragment, // Core parser
|
|
16
|
+
useFragmentNavigation, // navigateToModal(), closeModal()
|
|
17
|
+
useRoutedFragments, // Reactive fragment parsing
|
|
18
|
+
useRoutedModals, // Auto dialog/modal from URL
|
|
19
|
+
type DialogFragment, // type: 'dialog' route config
|
|
20
|
+
type ModalFragment, // type: 'modal' route config
|
|
21
|
+
} from '@cocoar/vue-fragment-parser';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Basic Usage
|
|
25
|
+
|
|
26
|
+
Define routes with path patterns and parse the current URL fragment:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { parseFragment, type RoutedFragmentBase } from '@cocoar/vue-fragment-parser';
|
|
30
|
+
|
|
31
|
+
interface AppRoute extends RoutedFragmentBase {
|
|
32
|
+
type: string;
|
|
33
|
+
path: string | string[];
|
|
34
|
+
options?: { requiresAuth?: boolean };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const routes: AppRoute[] = [
|
|
38
|
+
{ type: 'overview', path: 'overview' },
|
|
39
|
+
{ type: 'details', path: 'details/:id' },
|
|
40
|
+
{ type: 'edit', path: 'details/:id/edit' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// URL: https://app.com/#details/42?tab=comments
|
|
44
|
+
const result = parseFragment('#details/42?tab=comments', routes);
|
|
45
|
+
// → [{ route: { type: 'details', ... }, params: { id: '42', tab: 'comments' }, fragment: '...' }]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Path Parameters
|
|
49
|
+
|
|
50
|
+
Use `:param` syntax for dynamic segments (powered by `path-to-regexp`):
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const routes = [
|
|
54
|
+
{ type: 'user', path: 'user/:userId' },
|
|
55
|
+
{ type: 'project', path: 'project/:projectId/task/:taskId' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// #user/abc → params: { userId: 'abc' }
|
|
59
|
+
// #project/1/task/42 → params: { projectId: '1', taskId: '42' }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Query Parameters
|
|
63
|
+
|
|
64
|
+
Query parameters are parsed automatically with JSON type coercion:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// #details/5?edit=true&count=3
|
|
68
|
+
// → params: { id: '5', edit: true, count: 3 }
|
|
69
|
+
|
|
70
|
+
// #overview?tags=["a","b"]
|
|
71
|
+
// → params: { tags: ['a', 'b'] }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Multiple Fragments
|
|
75
|
+
|
|
76
|
+
Chain multiple fragments with `#` for composable routing:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// #details/5#confirm?force=true
|
|
80
|
+
const results = parseFragment('#details/5#confirm?force=true', routes);
|
|
81
|
+
// → Two parsed routes: details + confirm
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Array Paths
|
|
85
|
+
|
|
86
|
+
A single route can match multiple paths:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const routes = [
|
|
90
|
+
{ type: 'docs', path: ['overview', 'usage', 'examples'] },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
// #overview → matches docs route
|
|
94
|
+
// #usage → matches docs route
|
|
95
|
+
// #examples → matches docs route
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Modal Routing
|
|
99
|
+
|
|
100
|
+
The fragment parser's main use case: **deep-linkable modals**. When a user double-clicks a grid row, the modal opens AND the URL updates. Copy-pasting that URL opens the same modal. Browser back closes it.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/todos → just the list
|
|
104
|
+
/todos#todo-42 → list + detail modal for todo-42
|
|
105
|
+
/todos#todo-42?tab=2 → list + detail modal, comments tab selected
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### How It Works
|
|
109
|
+
|
|
110
|
+
Three composables work together:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
User clicks row
|
|
114
|
+
↓
|
|
115
|
+
navigateToModal('todo-42') ← useFragmentNavigation
|
|
116
|
+
↓
|
|
117
|
+
URL changes to /todos#todo-42
|
|
118
|
+
↓
|
|
119
|
+
useRoutedFragments detects change ← useRoutedFragments
|
|
120
|
+
↓
|
|
121
|
+
Parses fragment against routes
|
|
122
|
+
↓
|
|
123
|
+
useRoutedModals opens dialog ← useRoutedModals
|
|
124
|
+
↓
|
|
125
|
+
User closes dialog (or browser back)
|
|
126
|
+
↓
|
|
127
|
+
Fragment removed from URL
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Step 1: Define Fragment Routes
|
|
131
|
+
|
|
132
|
+
Register which fragments should open components in your Vue Router config. Two types are supported:
|
|
133
|
+
|
|
134
|
+
- **`type: 'dialog'`** — Opens inside a `CoarDialog` shell (header, title, close button)
|
|
135
|
+
- **`type: 'modal'`** — Opens as a raw overlay (no shell, your component IS the entire modal)
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// routes.ts
|
|
139
|
+
import type { RoutedOverlayFragment } from '@cocoar/vue-fragment-parser';
|
|
140
|
+
|
|
141
|
+
const routes = [
|
|
142
|
+
{
|
|
143
|
+
path: '/todos',
|
|
144
|
+
component: () => import('./TodoList.vue'),
|
|
145
|
+
meta: {
|
|
146
|
+
routedFragments: [
|
|
147
|
+
{
|
|
148
|
+
type: 'dialog',
|
|
149
|
+
path: ':todoId',
|
|
150
|
+
component: () => import('./TodoDetail.vue'),
|
|
151
|
+
dialogOptions: { title: 'Todo Details', size: 'l' },
|
|
152
|
+
},
|
|
153
|
+
] satisfies RoutedOverlayFragment[],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Step 2: Wire Up the List View
|
|
160
|
+
|
|
161
|
+
```vue
|
|
162
|
+
<!-- TodoList.vue -->
|
|
163
|
+
<script setup lang="ts">
|
|
164
|
+
import { useFragmentNavigation, useRoutedModals } from '@cocoar/vue-fragment-parser';
|
|
165
|
+
|
|
166
|
+
// Auto-open/close modals based on URL fragments
|
|
167
|
+
useRoutedModals();
|
|
168
|
+
|
|
169
|
+
// Navigate to modal on interaction
|
|
170
|
+
const { navigateToModal } = useFragmentNavigation();
|
|
171
|
+
|
|
172
|
+
builder.onRowDoubleClicked((event) => {
|
|
173
|
+
navigateToModal(event.data.id);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// With query params:
|
|
177
|
+
// navigateToModal(event.data.id, { tab: 0 })
|
|
178
|
+
// → URL: #todo-42?tab=0
|
|
179
|
+
</script>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Step 3: Build the Modal Content
|
|
183
|
+
|
|
184
|
+
The modal component receives fragment params as props, plus a `close` function:
|
|
185
|
+
|
|
186
|
+
```vue
|
|
187
|
+
<!-- TodoDetail.vue -->
|
|
188
|
+
<script setup lang="ts">
|
|
189
|
+
const props = defineProps<{
|
|
190
|
+
todoId: string; // from fragment path ':todoId'
|
|
191
|
+
tab?: number; // from query params '?tab=2'
|
|
192
|
+
close: (result?: unknown) => void;
|
|
193
|
+
}>();
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<template>
|
|
197
|
+
<div>
|
|
198
|
+
<h3>Todo: {{ todoId }}</h3>
|
|
199
|
+
<button @click="close()">Done</button>
|
|
200
|
+
</div>
|
|
201
|
+
</template>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Deep-Linking
|
|
205
|
+
|
|
206
|
+
Copy the URL `https://app.com/todos#todo-42?tab=2` and paste it in a new browser tab. The page loads, the fragment is parsed, and the modal opens automatically with `todoId: 'todo-42'` and `tab: 2`.
|
|
207
|
+
|
|
208
|
+
### Browser Back & History
|
|
209
|
+
|
|
210
|
+
The modal system integrates with browser history:
|
|
211
|
+
|
|
212
|
+
- **Modal open** → creates a history entry (`/todos` → `/todos#todo-42`)
|
|
213
|
+
- **Modal close** (X button) → creates another entry (`/todos#todo-42` → `/todos`)
|
|
214
|
+
- **Browser Back** after close → goes back to `/todos#todo-42` → modal reopens
|
|
215
|
+
- **Browser Back** while modal is open → goes back to `/todos` → modal closes
|
|
216
|
+
|
|
217
|
+
This means users can navigate modal state with the browser's back/forward buttons, just like regular pages.
|
|
218
|
+
|
|
219
|
+
### Multiple Modals
|
|
220
|
+
|
|
221
|
+
By default, `navigateToModal` **replaces** the current fragment. Use `append: true` to open multiple modals at once:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// Opens modal, replaces any existing fragment
|
|
225
|
+
navigateToModal('todo-42');
|
|
226
|
+
// URL: /todos#todo-42
|
|
227
|
+
|
|
228
|
+
// Opens second modal alongside the first
|
|
229
|
+
navigateToModal('confirm', undefined, { append: true });
|
|
230
|
+
// URL: /todos#todo-42#confirm
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Each fragment is matched independently. Closing one modal removes only its fragment.
|
|
234
|
+
|
|
235
|
+
## Composable API
|
|
236
|
+
|
|
237
|
+
### `useFragmentNavigation()`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const { navigateToModal, closeModal } = useFragmentNavigation();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Method | Parameters | Description |
|
|
244
|
+
|--------|-----------|-------------|
|
|
245
|
+
| `navigateToModal(path, params?, options?)` | `string, Record<string, ...>?, { append?: boolean }?` | Set fragment in URL, opens modal. `append: true` for multi-modal. |
|
|
246
|
+
| `closeModal(path)` | `string` | Remove fragment from URL, closes modal |
|
|
247
|
+
|
|
248
|
+
### `useRoutedFragments(routes?)`
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const { fragments } = useRoutedFragments();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Reactively parses `route.hash` against routes from `route.meta.routedFragments`. Returns `computed<ParsedRoute[]>`.
|
|
255
|
+
|
|
256
|
+
### `useRoutedModals()`
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
useRoutedModals();
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Fire-and-forget composable. Watches fragments and manages the overlay lifecycle:
|
|
263
|
+
|
|
264
|
+
- **`type: 'dialog'`** → opens via `useDialog().open()` with `DialogConfig` (shell with header/title)
|
|
265
|
+
- **`type: 'modal'`** → opens via `getOverlayService().open()` with `OverlaySpec` (raw overlay, no shell)
|
|
266
|
+
|
|
267
|
+
Handles:
|
|
268
|
+
- Fragment appears → lazy-load component → open dialog/modal
|
|
269
|
+
- Fragment removed (close button, browser back) → close
|
|
270
|
+
- Closed by user → remove fragment from URL
|
|
271
|
+
- Page load with fragment → deep-link: opens immediately
|
|
272
|
+
|
|
273
|
+
## API
|
|
274
|
+
|
|
275
|
+
### `parseFragment<T>(fragment, routes)`
|
|
276
|
+
|
|
277
|
+
| Parameter | Type | Description |
|
|
278
|
+
|-----------|------|-------------|
|
|
279
|
+
| `fragment` | `string` | URL fragment string (with or without leading `#`) |
|
|
280
|
+
| `routes` | `T[]` | Array of route definitions |
|
|
281
|
+
|
|
282
|
+
**Returns:** `ParsedRoute<T>[]` — array of matched routes
|
|
283
|
+
|
|
284
|
+
### Types
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
interface RoutedFragmentBase<TOptions = unknown> {
|
|
288
|
+
type: string;
|
|
289
|
+
path: string | string[];
|
|
290
|
+
options?: TOptions;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
interface ParsedRoute<T extends RoutedFragmentBase> {
|
|
294
|
+
params: Record<string, unknown>; // Path + query parameters
|
|
295
|
+
route: T; // Matched route config
|
|
296
|
+
fragment: string; // Original fragment string
|
|
297
|
+
}
|
|
298
|
+
```
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/link.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# Link
|
|
4
|
+
|
|
5
|
+
Styled anchor elements for in-page navigation, external references, and any clickable text that isn't a button action. Two equivalent APIs:
|
|
6
|
+
|
|
7
|
+
- **`<CoarLink>` SFC** (recommended) — `to` for router navigation, `href` for external links, automatic `rel="noopener"` for new-tab safety, disabled handling.
|
|
8
|
+
- **CSS classes** — `<a class="coar-link">` with hand-written `href` / `<RouterLink>` for consumers who want zero abstraction.
|
|
9
|
+
|
|
10
|
+
Both layers share the same CSS in `@cocoar/vue-ui/styles`. Mix them freely.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Basic usage
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<!-- Router navigation (when vue-router is installed) -->
|
|
20
|
+
<CoarLink to="/docs">Documentation</CoarLink>
|
|
21
|
+
|
|
22
|
+
<!-- External link with safe new-tab default -->
|
|
23
|
+
<CoarLink href="https://docs.cocoar.dev" target="_blank">
|
|
24
|
+
Cocoar Docs
|
|
25
|
+
</CoarLink>
|
|
26
|
+
|
|
27
|
+
<!-- mailto / tel still works via href -->
|
|
28
|
+
<CoarLink href="mailto:hi@example.com">hi@example.com</CoarLink>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The default style applies an accent color with an underline on hover, making links instantly recognizable in any context.
|
|
32
|
+
|
|
33
|
+
**Demo — `link/demos/LinkBasic.vue`**
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<template>
|
|
37
|
+
<CoarLink to="/getting-started">Default link</CoarLink>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Variants
|
|
46
|
+
|
|
47
|
+
Use the `accent` variant (default) for primary navigation and calls to action. Switch to `subtle` when the link should blend into surrounding body text.
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<CoarLink to="/docs" variant="subtle">Read the docs</CoarLink>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Demo — `link/demos/LinkVariants.vue`**
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<template>
|
|
57
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
58
|
+
<div><CoarLink to="/getting-started">Accent link (default)</CoarLink> — Primary interactive text</div>
|
|
59
|
+
<div><CoarLink to="/changelog" variant="subtle">Subtle link</CoarLink> — Secondary actions, less emphasis</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
65
|
+
</script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Sizes
|
|
69
|
+
|
|
70
|
+
Three size modifiers align with the typography scale, so links stay proportional whether they appear in footnotes or headings.
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<CoarLink href="#" size="s">Small</CoarLink>
|
|
74
|
+
<CoarLink href="#" size="m">Medium (default)</CoarLink>
|
|
75
|
+
<CoarLink href="#" size="l">Large</CoarLink>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Demo — `link/demos/LinkSizes.vue`**
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<template>
|
|
82
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
83
|
+
<div><CoarLink to="/changelog" size="s">Small link</CoarLink> — Compact contexts</div>
|
|
84
|
+
<div><CoarLink to="/getting-started" size="m">Default link</CoarLink> — Body text size</div>
|
|
85
|
+
<div><CoarLink to="/components/button" size="l">Large link</CoarLink> — Prominent CTAs</div>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Disabled state
|
|
95
|
+
|
|
96
|
+
Pass `disabled` to deactivate the link visually and semantically. The component sets `aria-disabled="true"`, `tabindex="-1"`, and intercepts clicks — navigation and `@click` emit are both suppressed.
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<CoarLink to="/admin" disabled>Admin (insufficient permissions)</CoarLink>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Demo — `link/demos/LinkDisabled.vue`**
|
|
103
|
+
|
|
104
|
+
```vue
|
|
105
|
+
<template>
|
|
106
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
107
|
+
<div><CoarLink to="/getting-started">Active link</CoarLink></div>
|
|
108
|
+
<div><CoarLink to="/components/button" disabled>Disabled link</CoarLink></div>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<script setup lang="ts">
|
|
113
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
114
|
+
</script>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Inline usage
|
|
118
|
+
|
|
119
|
+
Links are designed to sit naturally inside running prose without disrupting line height or text flow.
|
|
120
|
+
|
|
121
|
+
**Demo — `link/demos/LinkInline.vue`**
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<template>
|
|
125
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6;">
|
|
126
|
+
The COAR Design System is built on top of
|
|
127
|
+
<CoarLink href="https://vuejs.org" target="_blank">Vue 3</CoarLink> and uses the
|
|
128
|
+
<CoarLink href="https://tc39.es/proposal-temporal/docs/" target="_blank">Temporal API</CoarLink> for date/time handling.
|
|
129
|
+
See the <CoarLink to="/getting-started">Getting Started guide</CoarLink> or
|
|
130
|
+
browse the <CoarLink to="/components/button" variant="subtle">full component reference</CoarLink>
|
|
131
|
+
for more details.
|
|
132
|
+
</p>
|
|
133
|
+
</template>
|
|
134
|
+
|
|
135
|
+
<script setup lang="ts">
|
|
136
|
+
import { CoarLink } from '@cocoar/vue-ui';
|
|
137
|
+
</script>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Router Integration
|
|
141
|
+
|
|
142
|
+
When `to` is set, the link renders via `<RouterLink>` (if `vue-router` is installed and `app.use(router)` has registered the plugin) for SPA navigation. Without a router, it falls back to a plain `<a href={String(to)}>` that works for absolute URLs. The router detection uses `resolveDynamicComponent('RouterLink')` — no hard dependency, no peerDependency requirement.
|
|
143
|
+
|
|
144
|
+
```vue
|
|
145
|
+
<!-- SPA navigation when router available -->
|
|
146
|
+
<CoarLink to="/docs">Documentation</CoarLink>
|
|
147
|
+
|
|
148
|
+
<!-- Object-shaped routes work too (with router) -->
|
|
149
|
+
<CoarLink :to="{ name: 'docs', params: { section: 'intro' } }">
|
|
150
|
+
Intro
|
|
151
|
+
</CoarLink>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`aria-current="page"` is applied automatically when the current route matches `to` (via `RouterLink.isActive`).
|
|
155
|
+
|
|
156
|
+
> **Warning: Object `to` without router**
|
|
157
|
+
>
|
|
158
|
+
> Passing an object literal (`{ name: 'docs' }`) when no router is installed falls back to `String(to)`, producing `href="[object Object]"` — a broken link. The component logs a DEV-only `console.warn` once per call site to make this loud. Pass a string path for the no-router case.
|
|
159
|
+
|
|
160
|
+
## External Links
|
|
161
|
+
|
|
162
|
+
For absolute URLs and `mailto:` / `tel:` schemes, use `href` instead of `to`. The component skips the router entirely and renders a plain `<a>`. When `target="_blank"` is set, `rel="noopener"` is added automatically as a tab-nabbing defence (browsers do this by default since 2021, but the explicit attribute documents intent and works in older browsers).
|
|
163
|
+
|
|
164
|
+
```vue
|
|
165
|
+
<CoarLink href="https://github.com/cocoar-dev" target="_blank">
|
|
166
|
+
GitHub
|
|
167
|
+
</CoarLink>
|
|
168
|
+
<!-- Renders: <a href="..." target="_blank" rel="noopener" class="coar-link"> -->
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
You can override the auto-rel by passing your own:
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<CoarLink
|
|
175
|
+
href="https://untrusted.example.com"
|
|
176
|
+
target="_blank"
|
|
177
|
+
rel="noopener noreferrer external"
|
|
178
|
+
>
|
|
179
|
+
External (extra-cautious)
|
|
180
|
+
</CoarLink>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## CSS-only usage (no SFC)
|
|
184
|
+
|
|
185
|
+
If you only need the styling and want to wire the link element yourself (typically inside a `<RouterLink>` slot, breadcrumb, or templated context), apply the CSS classes directly to a native `<a>`:
|
|
186
|
+
|
|
187
|
+
```vue
|
|
188
|
+
<RouterLink to="/docs" custom v-slot="{ href, navigate, isActive }">
|
|
189
|
+
<a
|
|
190
|
+
:href="href"
|
|
191
|
+
class="coar-link"
|
|
192
|
+
:class="{ 'coar-link--disabled': !canVisit }"
|
|
193
|
+
@click="navigate"
|
|
194
|
+
>
|
|
195
|
+
Documentation
|
|
196
|
+
</a>
|
|
197
|
+
</RouterLink>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This is the original API and stays supported indefinitely — the SFC is purely additive.
|
|
201
|
+
|
|
202
|
+
## API
|
|
203
|
+
|
|
204
|
+
### Props
|
|
205
|
+
|
|
206
|
+
| Prop | Type | Default | Description |
|
|
207
|
+
|------|------|---------|-------------|
|
|
208
|
+
| `to` | `RouteLocationRaw \| string` | `undefined` | Vue Router target. Takes precedence over `href`. Renders via `<RouterLink>` if installed, else falls back to plain `<a href={String(to)}>`. |
|
|
209
|
+
| `href` | `string` | `undefined` | External URL. Used when `to` is not set. Works for `https:`, `mailto:`, `tel:`, etc. |
|
|
210
|
+
| `variant` | `'accent' \| 'subtle'` | `'accent'` | Visual variant. `accent` for primary links, `subtle` for blending into body text. |
|
|
211
|
+
| `size` | `'s' \| 'm' \| 'l'` | `'m'` | Typography size. |
|
|
212
|
+
| `disabled` | `boolean` | `false` | Disabled state (aria-disabled, tabindex=-1, click suppressed). |
|
|
213
|
+
| `target` | `string` | `undefined` | Anchor target attribute. Only applied to the plain `<a>` branches (not via `<RouterLink>`). |
|
|
214
|
+
| `rel` | `string` | *(see below)* | Anchor rel attribute. Auto-fills to `noopener` when `target="_blank"` is set and no explicit `rel` is provided. |
|
|
215
|
+
|
|
216
|
+
### Events
|
|
217
|
+
|
|
218
|
+
| Event | Payload | Description |
|
|
219
|
+
|-------|---------|-------------|
|
|
220
|
+
| `click` | `MouseEvent` | Emitted on every plain click (not when disabled). Modifier-clicks (Ctrl/Cmd/Middle) pass through to the browser. |
|
|
221
|
+
|
|
222
|
+
### Slots
|
|
223
|
+
|
|
224
|
+
| Slot | Description |
|
|
225
|
+
|------|-------------|
|
|
226
|
+
| `default` | Link content (text, icons, anything inline). |
|
|
227
|
+
|
|
228
|
+
### CSS Classes (legacy / advanced)
|
|
229
|
+
|
|
230
|
+
| Class | Description |
|
|
231
|
+
|-------|-------------|
|
|
232
|
+
| `.coar-link` | Base link style — accent color, underline on hover |
|
|
233
|
+
| `.coar-link--subtle` | Subtle variant with less color emphasis |
|
|
234
|
+
| `.coar-link--s` / `--m` / `--l` | Typography size |
|
|
235
|
+
| `.coar-link--disabled` | Disabled appearance (combine with `aria-disabled="true"`) |
|