nuxt-ui-elements-pro 0.1.10 → 0.1.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/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +240 -1
- package/dist/runtime/components/EventCalendar.vue +2 -2
- package/dist/runtime/components/FeedbackWidget.d.vue.ts +118 -0
- package/dist/runtime/components/FeedbackWidget.vue +141 -0
- package/dist/runtime/components/FeedbackWidget.vue.d.ts +118 -0
- package/dist/runtime/components/GanttChart.d.vue.ts +138 -0
- package/dist/runtime/components/GanttChart.vue +206 -0
- package/dist/runtime/components/GanttChart.vue.d.ts +138 -0
- package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +23 -3
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.d.vue.ts +22 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue +27 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue.d.ts +22 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.d.vue.ts +42 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue +288 -0
- package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue.d.ts +42 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.d.vue.ts +16 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue +70 -0
- package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue.d.ts +16 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.d.vue.ts +41 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.vue +56 -0
- package/dist/runtime/components/gantt-chart/GanttChartHeader.vue.d.ts +41 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.d.vue.ts +34 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue +193 -0
- package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue.d.ts +34 -0
- package/dist/runtime/composables/useEventCalendar.d.ts +2 -0
- package/dist/runtime/composables/useEventCalendar.js +8 -6
- package/dist/runtime/composables/useEventCalendarContext.d.ts +2 -7
- package/dist/runtime/composables/useEventCalendarContext.js +4 -11
- package/dist/runtime/composables/useFeedbackWidget.d.ts +46 -0
- package/dist/runtime/composables/useFeedbackWidget.js +137 -0
- package/dist/runtime/composables/useFeedbackWidgetContext.d.ts +3 -0
- package/dist/runtime/composables/useFeedbackWidgetContext.js +4 -0
- package/dist/runtime/composables/useGanttChart.d.ts +52 -0
- package/dist/runtime/composables/useGanttChart.js +224 -0
- package/dist/runtime/composables/useGanttChartContext.d.ts +3 -0
- package/dist/runtime/composables/useGanttChartContext.js +4 -0
- package/dist/runtime/composables/useGanttChartDragDrop.d.ts +28 -0
- package/dist/runtime/composables/useGanttChartDragDrop.js +68 -0
- package/dist/runtime/composables/useGanttChartKeyboard.d.ts +14 -0
- package/dist/runtime/composables/useGanttChartKeyboard.js +92 -0
- package/dist/runtime/composables/useGanttChartResize.d.ts +26 -0
- package/dist/runtime/composables/useGanttChartResize.js +89 -0
- package/dist/runtime/composables/useOrgChartContext.d.ts +2 -7
- package/dist/runtime/composables/useOrgChartContext.js +4 -11
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/server/api/_feedback.post.d.ts +3 -0
- package/dist/runtime/server/api/_feedback.post.js +115 -0
- package/dist/runtime/server/nodemailer.d.ts +29 -0
- package/dist/runtime/server/utils/feedback-captcha.d.ts +1 -0
- package/dist/runtime/server/utils/feedback-captcha.js +27 -0
- package/dist/runtime/server/utils/feedback-rate-limit.d.ts +4 -0
- package/dist/runtime/server/utils/feedback-rate-limit.js +26 -0
- package/dist/runtime/types/event-calendar.d.ts +10 -4
- package/dist/runtime/types/feedback-widget.d.ts +110 -0
- package/dist/runtime/types/feedback-widget.js +0 -0
- package/dist/runtime/types/gantt-chart.d.ts +223 -0
- package/dist/runtime/types/gantt-chart.js +0 -0
- package/dist/runtime/types/index.d.ts +4 -0
- package/dist/runtime/types/index.js +4 -0
- package/dist/runtime/utils/createComponentContext.d.ts +15 -0
- package/dist/runtime/utils/createComponentContext.js +17 -0
- package/dist/runtime/utils/date.d.ts +10 -0
- package/dist/runtime/utils/date.js +9 -0
- package/dist/runtime/utils/event-calendar.d.ts +1 -9
- package/dist/runtime/utils/event-calendar.js +2 -9
- package/dist/runtime/utils/gantt-chart.d.ts +85 -0
- package/dist/runtime/utils/gantt-chart.js +549 -0
- package/dist/runtime/utils/recurrence.js +2 -1
- package/package.json +17 -8
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
3
|
+
import { useFeedbackWidgetContext } from "../../composables/useFeedbackWidgetContext";
|
|
4
|
+
defineProps({
|
|
5
|
+
title: { type: String, required: true }
|
|
6
|
+
});
|
|
7
|
+
const ctx = useFeedbackWidgetContext();
|
|
8
|
+
const captchaContainerRef = ref(null);
|
|
9
|
+
let captchaWidgetId = null;
|
|
10
|
+
const CAPTCHA_SCRIPTS = {
|
|
11
|
+
turnstile: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
|
|
12
|
+
recaptcha: "https://www.google.com/recaptcha/api.js?render=explicit",
|
|
13
|
+
hcaptcha: "https://js.hcaptcha.com/1/api.js?render=explicit"
|
|
14
|
+
};
|
|
15
|
+
function loadCaptchaScript(provider) {
|
|
16
|
+
const existingScript = document.querySelector(`script[data-captcha-provider="${provider}"]`);
|
|
17
|
+
if (existingScript) return Promise.resolve();
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const script = document.createElement("script");
|
|
20
|
+
script.src = CAPTCHA_SCRIPTS[provider];
|
|
21
|
+
script.async = true;
|
|
22
|
+
script.defer = true;
|
|
23
|
+
script.dataset.captchaProvider = provider;
|
|
24
|
+
script.onload = () => resolve();
|
|
25
|
+
script.onerror = () => reject(new Error(`Failed to load ${provider} script`));
|
|
26
|
+
document.head.appendChild(script);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function renderCaptcha() {
|
|
30
|
+
if (!captchaContainerRef.value || !ctx.captchaEnabled.value) return;
|
|
31
|
+
const provider = ctx.captchaProvider.value;
|
|
32
|
+
const siteKey = ctx.captchaSiteKey.value;
|
|
33
|
+
if (!provider || !siteKey) return;
|
|
34
|
+
if (captchaContainerRef.value) {
|
|
35
|
+
captchaContainerRef.value.innerHTML = "";
|
|
36
|
+
}
|
|
37
|
+
const callback = (token) => ctx.setCaptchaToken(token);
|
|
38
|
+
if (provider === "turnstile" && window.turnstile) {
|
|
39
|
+
captchaWidgetId = window.turnstile.render(captchaContainerRef.value, {
|
|
40
|
+
sitekey: siteKey,
|
|
41
|
+
callback
|
|
42
|
+
});
|
|
43
|
+
} else if (provider === "recaptcha" && window.grecaptcha) {
|
|
44
|
+
captchaWidgetId = window.grecaptcha.render(captchaContainerRef.value, {
|
|
45
|
+
sitekey: siteKey,
|
|
46
|
+
callback
|
|
47
|
+
});
|
|
48
|
+
} else if (provider === "hcaptcha" && window.hcaptcha) {
|
|
49
|
+
captchaWidgetId = window.hcaptcha.render(captchaContainerRef.value, {
|
|
50
|
+
sitekey: siteKey,
|
|
51
|
+
callback
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
watch(() => ctx.isOpen.value, async (open) => {
|
|
56
|
+
if (open && ctx.captchaEnabled.value && ctx.captchaProvider.value) {
|
|
57
|
+
try {
|
|
58
|
+
await loadCaptchaScript(ctx.captchaProvider.value);
|
|
59
|
+
setTimeout(renderCaptcha, 100);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
function onKeydown(e) {
|
|
65
|
+
if (e.key === "Escape" && ctx.isOpen.value) {
|
|
66
|
+
ctx.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
onMounted(() => {
|
|
70
|
+
document.addEventListener("keydown", onKeydown);
|
|
71
|
+
});
|
|
72
|
+
onUnmounted(() => {
|
|
73
|
+
document.removeEventListener("keydown", onKeydown);
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<div
|
|
79
|
+
data-slot="panel"
|
|
80
|
+
:class="[
|
|
81
|
+
ctx.ui.value.panel({ class: ctx.propUi.value?.panel }),
|
|
82
|
+
ctx.isOpen.value ? ctx.ui.value.panelVisible({ class: ctx.propUi.value?.panelVisible }) : ctx.ui.value.panelHidden({ class: ctx.propUi.value?.panelHidden })
|
|
83
|
+
]"
|
|
84
|
+
>
|
|
85
|
+
<!-- Success state -->
|
|
86
|
+
<template v-if="ctx.isSubmitted.value">
|
|
87
|
+
<slot name="success" :reset="ctx.reset" :close="ctx.close">
|
|
88
|
+
<div data-slot="successState" :class="ctx.ui.value.successState({ class: ctx.propUi.value?.successState })">
|
|
89
|
+
<UIcon name="i-lucide-check-circle-2" :class="ctx.ui.value.successIcon({ class: ctx.propUi.value?.successIcon })" />
|
|
90
|
+
<p :class="ctx.ui.value.successTitle({ class: ctx.propUi.value?.successTitle })">Thank you!</p>
|
|
91
|
+
<p :class="ctx.ui.value.successMessage({ class: ctx.propUi.value?.successMessage })">
|
|
92
|
+
Your feedback has been submitted successfully.
|
|
93
|
+
</p>
|
|
94
|
+
<UButton size="sm" color="neutral" variant="outline" @click="ctx.reset">
|
|
95
|
+
Send more feedback
|
|
96
|
+
</UButton>
|
|
97
|
+
</div>
|
|
98
|
+
</slot>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<!-- Main form flow -->
|
|
102
|
+
<template v-else>
|
|
103
|
+
<!-- Header -->
|
|
104
|
+
<slot name="header" :title="title" :close="ctx.close">
|
|
105
|
+
<div data-slot="panelHeader" :class="ctx.ui.value.panelHeader({ class: ctx.propUi.value?.panelHeader })">
|
|
106
|
+
<span :class="ctx.ui.value.panelTitle({ class: ctx.propUi.value?.panelTitle })">{{ title }}</span>
|
|
107
|
+
<UButton
|
|
108
|
+
size="xs"
|
|
109
|
+
color="neutral"
|
|
110
|
+
variant="ghost"
|
|
111
|
+
icon="i-lucide-x"
|
|
112
|
+
square
|
|
113
|
+
:class="ctx.ui.value.panelCloseButton({ class: ctx.propUi.value?.panelCloseButton })"
|
|
114
|
+
aria-label="Close feedback panel"
|
|
115
|
+
@click="ctx.close"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
</slot>
|
|
119
|
+
|
|
120
|
+
<!-- Type selector (step 1) -->
|
|
121
|
+
<template v-if="!ctx.selectedType.value">
|
|
122
|
+
<slot
|
|
123
|
+
name="type-selector"
|
|
124
|
+
:types="ctx.feedbackTypes.value"
|
|
125
|
+
:selected="ctx.selectedType.value"
|
|
126
|
+
:select="(id) => ctx.selectedType.value = id"
|
|
127
|
+
>
|
|
128
|
+
<div data-slot="typeSelector" :class="ctx.ui.value.typeSelector({ class: ctx.propUi.value?.typeSelector })">
|
|
129
|
+
<button
|
|
130
|
+
v-for="feedbackType in ctx.feedbackTypes.value"
|
|
131
|
+
:key="feedbackType.id"
|
|
132
|
+
type="button"
|
|
133
|
+
data-slot="typeOption"
|
|
134
|
+
:class="[
|
|
135
|
+
ctx.ui.value.typeOption({ class: ctx.propUi.value?.typeOption }),
|
|
136
|
+
ctx.selectedType.value === feedbackType.id && ctx.ui.value.typeOptionSelected({ class: ctx.propUi.value?.typeOptionSelected })
|
|
137
|
+
]"
|
|
138
|
+
@click="ctx.selectedType.value = feedbackType.id"
|
|
139
|
+
>
|
|
140
|
+
<UIcon
|
|
141
|
+
v-if="feedbackType.icon"
|
|
142
|
+
:name="feedbackType.icon"
|
|
143
|
+
:class="ctx.ui.value.typeOptionIcon({ class: ctx.propUi.value?.typeOptionIcon })"
|
|
144
|
+
/>
|
|
145
|
+
<div>
|
|
146
|
+
<div :class="ctx.ui.value.typeOptionLabel({ class: ctx.propUi.value?.typeOptionLabel })">
|
|
147
|
+
{{ feedbackType.label }}
|
|
148
|
+
</div>
|
|
149
|
+
<div
|
|
150
|
+
v-if="feedbackType.description"
|
|
151
|
+
:class="ctx.ui.value.typeOptionDescription({ class: ctx.propUi.value?.typeOptionDescription })"
|
|
152
|
+
>
|
|
153
|
+
{{ feedbackType.description }}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</slot>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<!-- Form (step 2) -->
|
|
162
|
+
<template v-else>
|
|
163
|
+
<slot
|
|
164
|
+
name="form"
|
|
165
|
+
:email="ctx.email.value"
|
|
166
|
+
:message="ctx.message.value"
|
|
167
|
+
:screenshot="ctx.screenshot.value"
|
|
168
|
+
>
|
|
169
|
+
<div data-slot="form" :class="ctx.ui.value.form({ class: ctx.propUi.value?.form })">
|
|
170
|
+
<!-- Back button -->
|
|
171
|
+
<UButton
|
|
172
|
+
size="xs"
|
|
173
|
+
color="neutral"
|
|
174
|
+
variant="ghost"
|
|
175
|
+
icon="i-lucide-arrow-left"
|
|
176
|
+
label="Back"
|
|
177
|
+
data-slot="backButton"
|
|
178
|
+
:class="ctx.ui.value.backButton({ class: ctx.propUi.value?.backButton })"
|
|
179
|
+
@click="ctx.selectedType.value = null"
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
<!-- Email -->
|
|
183
|
+
<div data-slot="formField" :class="ctx.ui.value.formField({ class: ctx.propUi.value?.formField })">
|
|
184
|
+
<label :class="ctx.ui.value.formLabel({ class: ctx.propUi.value?.formLabel })">Email</label>
|
|
185
|
+
<UInput
|
|
186
|
+
:model-value="ctx.email.value"
|
|
187
|
+
type="email"
|
|
188
|
+
:color="ctx.color.value"
|
|
189
|
+
:placeholder="ctx.emailPlaceholder.value"
|
|
190
|
+
data-slot="formInput"
|
|
191
|
+
@update:model-value="ctx.email.value = $event"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- Message -->
|
|
196
|
+
<div data-slot="formField" :class="ctx.ui.value.formField({ class: ctx.propUi.value?.formField })">
|
|
197
|
+
<label :class="ctx.ui.value.formLabel({ class: ctx.propUi.value?.formLabel })">Message</label>
|
|
198
|
+
<UTextarea
|
|
199
|
+
:model-value="ctx.message.value"
|
|
200
|
+
:color="ctx.color.value"
|
|
201
|
+
:placeholder="ctx.messagePlaceholder.value"
|
|
202
|
+
:rows="3"
|
|
203
|
+
data-slot="formTextarea"
|
|
204
|
+
class="resize-none"
|
|
205
|
+
@update:model-value="ctx.message.value = $event"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Screenshot -->
|
|
210
|
+
<template v-if="ctx.showScreenshot.value">
|
|
211
|
+
<div v-if="ctx.screenshot.value" :class="ctx.ui.value.screenshotPreview({ class: ctx.propUi.value?.screenshotPreview })">
|
|
212
|
+
<img :src="ctx.screenshot.value" alt="Screenshot preview" :class="ctx.ui.value.screenshotImage({ class: ctx.propUi.value?.screenshotImage })">
|
|
213
|
+
<UButton
|
|
214
|
+
size="xs"
|
|
215
|
+
color="neutral"
|
|
216
|
+
variant="ghost"
|
|
217
|
+
icon="i-lucide-x"
|
|
218
|
+
square
|
|
219
|
+
:class="ctx.ui.value.screenshotRemove({ class: ctx.propUi.value?.screenshotRemove })"
|
|
220
|
+
aria-label="Remove screenshot"
|
|
221
|
+
@click="ctx.removeScreenshot"
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
<UButton
|
|
225
|
+
v-else
|
|
226
|
+
size="xs"
|
|
227
|
+
color="neutral"
|
|
228
|
+
variant="ghost"
|
|
229
|
+
icon="i-lucide-camera"
|
|
230
|
+
label="Capture screenshot"
|
|
231
|
+
:class="ctx.ui.value.screenshotButton({ class: ctx.propUi.value?.screenshotButton })"
|
|
232
|
+
@click="ctx.captureScreenshot"
|
|
233
|
+
/>
|
|
234
|
+
</template>
|
|
235
|
+
|
|
236
|
+
<!-- Honeypot (invisible to real users) -->
|
|
237
|
+
<div :class="ctx.ui.value.honeypot({ class: ctx.propUi.value?.honeypot })" aria-hidden="true" tabindex="-1">
|
|
238
|
+
<label for="_feedback_hp">Leave this empty</label>
|
|
239
|
+
<input
|
|
240
|
+
id="_feedback_hp"
|
|
241
|
+
:value="ctx.honeypot.value"
|
|
242
|
+
type="text"
|
|
243
|
+
name="_hp"
|
|
244
|
+
autocomplete="off"
|
|
245
|
+
tabindex="-1"
|
|
246
|
+
@input="ctx.honeypot.value = $event.target.value"
|
|
247
|
+
>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- CAPTCHA -->
|
|
251
|
+
<div
|
|
252
|
+
v-if="ctx.captchaEnabled.value"
|
|
253
|
+
ref="captchaContainerRef"
|
|
254
|
+
data-slot="captchaContainer"
|
|
255
|
+
:class="ctx.ui.value.captchaContainer({ class: ctx.propUi.value?.captchaContainer })"
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
</slot>
|
|
259
|
+
|
|
260
|
+
<!-- Error message -->
|
|
261
|
+
<p v-if="ctx.submitError.value" data-slot="errorMessage" :class="ctx.ui.value.errorMessage({ class: ctx.propUi.value?.errorMessage })">
|
|
262
|
+
{{ ctx.submitError.value }}
|
|
263
|
+
</p>
|
|
264
|
+
|
|
265
|
+
<!-- Submit area -->
|
|
266
|
+
<slot
|
|
267
|
+
name="submit-area"
|
|
268
|
+
:submit="ctx.submit"
|
|
269
|
+
:is-valid="ctx.isValid.value"
|
|
270
|
+
:is-submitting="ctx.isSubmitting.value"
|
|
271
|
+
>
|
|
272
|
+
<div data-slot="submitArea" :class="ctx.ui.value.submitArea({ class: ctx.propUi.value?.submitArea })">
|
|
273
|
+
<span class="text-xs text-muted">
|
|
274
|
+
{{ ctx.feedbackTypes.value.find((t) => t.id === ctx.selectedType.value)?.label }}
|
|
275
|
+
</span>
|
|
276
|
+
<UButton
|
|
277
|
+
size="sm"
|
|
278
|
+
:color="ctx.color.value"
|
|
279
|
+
:disabled="!ctx.isValid.value || ctx.isSubmitting.value"
|
|
280
|
+
:label="ctx.isSubmitting.value ? 'Sending...' : 'Submit'"
|
|
281
|
+
@click="ctx.submit"
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
</slot>
|
|
285
|
+
</template>
|
|
286
|
+
</template>
|
|
287
|
+
</div>
|
|
288
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title: string;
|
|
3
|
+
};
|
|
4
|
+
declare var __VLS_1: {
|
|
5
|
+
reset: () => void;
|
|
6
|
+
close: () => void;
|
|
7
|
+
}, __VLS_16: {
|
|
8
|
+
title: string;
|
|
9
|
+
close: () => void;
|
|
10
|
+
}, __VLS_25: {
|
|
11
|
+
types: import("../../types/feedback-widget.js").FeedbackType[];
|
|
12
|
+
selected: (string & {}) | null;
|
|
13
|
+
select: (id: string) => string;
|
|
14
|
+
}, __VLS_32: {
|
|
15
|
+
email: string;
|
|
16
|
+
message: string;
|
|
17
|
+
screenshot: string | null;
|
|
18
|
+
}, __VLS_69: {
|
|
19
|
+
submit: () => Promise<void>;
|
|
20
|
+
isValid: boolean;
|
|
21
|
+
isSubmitting: boolean;
|
|
22
|
+
};
|
|
23
|
+
type __VLS_Slots = {} & {
|
|
24
|
+
success?: (props: typeof __VLS_1) => any;
|
|
25
|
+
} & {
|
|
26
|
+
header?: (props: typeof __VLS_16) => any;
|
|
27
|
+
} & {
|
|
28
|
+
'type-selector'?: (props: typeof __VLS_25) => any;
|
|
29
|
+
} & {
|
|
30
|
+
form?: (props: typeof __VLS_32) => any;
|
|
31
|
+
} & {
|
|
32
|
+
'submit-area'?: (props: typeof __VLS_69) => any;
|
|
33
|
+
};
|
|
34
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
39
|
+
new (): {
|
|
40
|
+
$slots: S;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GanttDependencyPath } from "../../types/gantt-chart.js";
|
|
2
|
+
declare var __VLS_1: {
|
|
3
|
+
paths: GanttDependencyPath[];
|
|
4
|
+
};
|
|
5
|
+
type __VLS_Slots = {} & {
|
|
6
|
+
default?: (props: typeof __VLS_1) => any;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
9
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
12
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
13
|
+
new (): {
|
|
14
|
+
$slots: S;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useGanttChartContext } from "../../composables/useGanttChartContext";
|
|
4
|
+
import { buildDependencyPath } from "../../utils/gantt-chart";
|
|
5
|
+
const ctx = useGanttChartContext();
|
|
6
|
+
const activePaths = computed(() => {
|
|
7
|
+
const basePaths = ctx.layout.value.dependencyPaths;
|
|
8
|
+
const dragId = ctx.draggedTaskId.value;
|
|
9
|
+
const dragPreview = ctx.dragPreview.value;
|
|
10
|
+
const resizeId = ctx.resizingTaskId.value;
|
|
11
|
+
const resizePreview = ctx.resizePreview.value;
|
|
12
|
+
if ((!dragId || !dragPreview) && (!resizeId || !resizePreview)) {
|
|
13
|
+
return basePaths;
|
|
14
|
+
}
|
|
15
|
+
const activeId = dragId ?? resizeId;
|
|
16
|
+
const preview = dragPreview ?? resizePreview;
|
|
17
|
+
if (!activeId || !preview) return basePaths;
|
|
18
|
+
return basePaths.map((dep) => {
|
|
19
|
+
const isFromActive = dep.from.task.id === activeId;
|
|
20
|
+
const isToActive = dep.to.task.id === activeId;
|
|
21
|
+
if (!isFromActive && !isToActive) return dep;
|
|
22
|
+
const adjustedFrom = isFromActive ? { ...dep.from, leftPx: preview.leftPx, widthPx: preview.widthPx } : dep.from;
|
|
23
|
+
const adjustedTo = isToActive ? { ...dep.to, leftPx: preview.leftPx, widthPx: preview.widthPx } : dep.to;
|
|
24
|
+
const result = buildDependencyPath(adjustedFrom, adjustedTo, ctx.rowHeight.value);
|
|
25
|
+
return { ...dep, path: result.path, fromPoint: result.fromPoint, toPoint: result.toPoint };
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<svg
|
|
32
|
+
v-if="activePaths.length > 0"
|
|
33
|
+
data-slot="dependencyLayer"
|
|
34
|
+
:class="ctx.ui.value.dependencyLayer({ class: ctx.propUi.value?.dependencyLayer })"
|
|
35
|
+
:width="ctx.layout.value.totalWidth"
|
|
36
|
+
:height="ctx.layout.value.totalHeight"
|
|
37
|
+
:viewBox="`0 0 ${ctx.layout.value.totalWidth} ${ctx.layout.value.totalHeight}`"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
>
|
|
40
|
+
<!-- Arrowhead marker definition -->
|
|
41
|
+
<defs>
|
|
42
|
+
<marker
|
|
43
|
+
id="gantt-arrowhead"
|
|
44
|
+
viewBox="0 0 10 10"
|
|
45
|
+
refX="10"
|
|
46
|
+
refY="5"
|
|
47
|
+
markerWidth="6"
|
|
48
|
+
markerHeight="6"
|
|
49
|
+
orient="auto-start-reverse"
|
|
50
|
+
>
|
|
51
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor" class="text-muted" />
|
|
52
|
+
</marker>
|
|
53
|
+
</defs>
|
|
54
|
+
|
|
55
|
+
<slot :paths="activePaths">
|
|
56
|
+
<template v-for="dep in activePaths" :key="`${dep.dependency.fromId}-${dep.dependency.toId}`">
|
|
57
|
+
<path
|
|
58
|
+
data-slot="dependencyArrow"
|
|
59
|
+
:d="dep.path"
|
|
60
|
+
:class="ctx.ui.value.dependencyArrow({ class: ctx.propUi.value?.dependencyArrow })"
|
|
61
|
+
stroke-width="1.5"
|
|
62
|
+
marker-end="url(#gantt-arrowhead)"
|
|
63
|
+
/>
|
|
64
|
+
<!-- Connection dots at both ends -->
|
|
65
|
+
<circle :cx="dep.fromPoint.x" :cy="dep.fromPoint.y" r="3" fill="currentColor" class="text-muted" />
|
|
66
|
+
<circle :cx="dep.toPoint.x" :cy="dep.toPoint.y" r="3" fill="currentColor" class="text-muted" />
|
|
67
|
+
</template>
|
|
68
|
+
</slot>
|
|
69
|
+
</svg>
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GanttDependencyPath } from "../../types/gantt-chart.js";
|
|
2
|
+
declare var __VLS_1: {
|
|
3
|
+
paths: GanttDependencyPath[];
|
|
4
|
+
};
|
|
5
|
+
type __VLS_Slots = {} & {
|
|
6
|
+
default?: (props: typeof __VLS_1) => any;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
9
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
12
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
13
|
+
new (): {
|
|
14
|
+
$slots: S;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GanttZoomLevel } from "../../types/gantt-chart.js";
|
|
2
|
+
import type { Component } from "vue";
|
|
3
|
+
export interface GanttChartHeaderProps {
|
|
4
|
+
/** Rendered element type @defaultValue 'div' */
|
|
5
|
+
as?: string | Component;
|
|
6
|
+
}
|
|
7
|
+
export interface GanttChartHeaderSlots {
|
|
8
|
+
default: (props: {
|
|
9
|
+
title: string;
|
|
10
|
+
prev: () => void;
|
|
11
|
+
next: () => void;
|
|
12
|
+
today: () => void;
|
|
13
|
+
zoomLevel: GanttZoomLevel;
|
|
14
|
+
setZoomLevel: (level: GanttZoomLevel) => void;
|
|
15
|
+
zoomLevels: GanttZoomLevel[];
|
|
16
|
+
}) => any;
|
|
17
|
+
/** Replace just the title element */
|
|
18
|
+
title: (props: {
|
|
19
|
+
title: string;
|
|
20
|
+
}) => any;
|
|
21
|
+
/** Replace just the navigation buttons */
|
|
22
|
+
nav: (props: {
|
|
23
|
+
prev: () => void;
|
|
24
|
+
next: () => void;
|
|
25
|
+
today: () => void;
|
|
26
|
+
}) => any;
|
|
27
|
+
/** Replace just the zoom level switcher */
|
|
28
|
+
"zoom-switcher": (props: {
|
|
29
|
+
zoomLevel: GanttZoomLevel;
|
|
30
|
+
setZoomLevel: (level: GanttZoomLevel) => void;
|
|
31
|
+
zoomLevels: GanttZoomLevel[];
|
|
32
|
+
}) => any;
|
|
33
|
+
}
|
|
34
|
+
declare const _default: typeof __VLS_export;
|
|
35
|
+
export default _default;
|
|
36
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<GanttChartHeaderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<GanttChartHeaderProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, GanttChartHeaderSlots>;
|
|
37
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
38
|
+
new (): {
|
|
39
|
+
$slots: S;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Primitive } from "reka-ui";
|
|
3
|
+
import { useGanttChartContext } from "../../composables/useGanttChartContext";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<script setup>
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
as: { type: null, required: false }
|
|
9
|
+
});
|
|
10
|
+
defineSlots();
|
|
11
|
+
const ctx = useGanttChartContext();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Primitive :as="props.as ?? 'div'" data-slot="header" :class="ctx.ui.value.header({ class: ctx.propUi.value?.header })">
|
|
16
|
+
<slot
|
|
17
|
+
:title="ctx.headerTitle.value"
|
|
18
|
+
:prev="ctx.goToPrev"
|
|
19
|
+
:next="ctx.goToNext"
|
|
20
|
+
:today="ctx.goToToday"
|
|
21
|
+
:zoom-level="ctx.zoomLevel.value"
|
|
22
|
+
:set-zoom-level="ctx.setZoomLevel"
|
|
23
|
+
:zoom-levels="ctx.zoomLevels.value">
|
|
24
|
+
<slot name="title" :title="ctx.headerTitle.value">
|
|
25
|
+
<span data-slot="headerTitle" :class="ctx.ui.value.headerTitle({ class: ctx.propUi.value?.headerTitle })" aria-live="polite" aria-atomic="true">
|
|
26
|
+
{{ ctx.headerTitle.value }}
|
|
27
|
+
</span>
|
|
28
|
+
</slot>
|
|
29
|
+
|
|
30
|
+
<div data-slot="headerActions" :class="ctx.ui.value.headerActions({ class: ctx.propUi.value?.headerActions })">
|
|
31
|
+
<slot name="zoom-switcher" :zoom-level="ctx.zoomLevel.value" :set-zoom-level="ctx.setZoomLevel" :zoom-levels="ctx.zoomLevels.value">
|
|
32
|
+
<div data-slot="zoomSwitcher" role="group" aria-label="Zoom level" :class="ctx.ui.value.zoomSwitcher({ class: ctx.propUi.value?.zoomSwitcher })">
|
|
33
|
+
<UButton
|
|
34
|
+
v-for="level in ctx.zoomLevels.value"
|
|
35
|
+
:key="level"
|
|
36
|
+
:label="level"
|
|
37
|
+
size="xs"
|
|
38
|
+
:color="ctx.zoomLevel.value === level ? ctx.color.value : 'neutral'"
|
|
39
|
+
:variant="ctx.zoomLevel.value === level ? 'subtle' : 'ghost'"
|
|
40
|
+
:aria-pressed="ctx.zoomLevel.value === level"
|
|
41
|
+
class="capitalize"
|
|
42
|
+
@click="ctx.setZoomLevel(level)" />
|
|
43
|
+
</div>
|
|
44
|
+
</slot>
|
|
45
|
+
|
|
46
|
+
<slot name="nav" :prev="ctx.goToPrev" :next="ctx.goToNext" :today="ctx.goToToday">
|
|
47
|
+
<div data-slot="headerNav" :class="ctx.ui.value.headerNav({ class: ctx.propUi.value?.headerNav })">
|
|
48
|
+
<UButton icon="i-lucide-chevron-left" color="neutral" variant="ghost" size="xs" square aria-label="Previous" @click="ctx.goToPrev" />
|
|
49
|
+
<UButton label="Today" color="neutral" variant="ghost" size="xs" aria-label="Go to today" @click="ctx.goToToday" />
|
|
50
|
+
<UButton icon="i-lucide-chevron-right" color="neutral" variant="ghost" size="xs" square aria-label="Next" @click="ctx.goToNext" />
|
|
51
|
+
</div>
|
|
52
|
+
</slot>
|
|
53
|
+
</div>
|
|
54
|
+
</slot>
|
|
55
|
+
</Primitive>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GanttZoomLevel } from "../../types/gantt-chart.js";
|
|
2
|
+
import type { Component } from "vue";
|
|
3
|
+
export interface GanttChartHeaderProps {
|
|
4
|
+
/** Rendered element type @defaultValue 'div' */
|
|
5
|
+
as?: string | Component;
|
|
6
|
+
}
|
|
7
|
+
export interface GanttChartHeaderSlots {
|
|
8
|
+
default: (props: {
|
|
9
|
+
title: string;
|
|
10
|
+
prev: () => void;
|
|
11
|
+
next: () => void;
|
|
12
|
+
today: () => void;
|
|
13
|
+
zoomLevel: GanttZoomLevel;
|
|
14
|
+
setZoomLevel: (level: GanttZoomLevel) => void;
|
|
15
|
+
zoomLevels: GanttZoomLevel[];
|
|
16
|
+
}) => any;
|
|
17
|
+
/** Replace just the title element */
|
|
18
|
+
title: (props: {
|
|
19
|
+
title: string;
|
|
20
|
+
}) => any;
|
|
21
|
+
/** Replace just the navigation buttons */
|
|
22
|
+
nav: (props: {
|
|
23
|
+
prev: () => void;
|
|
24
|
+
next: () => void;
|
|
25
|
+
today: () => void;
|
|
26
|
+
}) => any;
|
|
27
|
+
/** Replace just the zoom level switcher */
|
|
28
|
+
"zoom-switcher": (props: {
|
|
29
|
+
zoomLevel: GanttZoomLevel;
|
|
30
|
+
setZoomLevel: (level: GanttZoomLevel) => void;
|
|
31
|
+
zoomLevels: GanttZoomLevel[];
|
|
32
|
+
}) => any;
|
|
33
|
+
}
|
|
34
|
+
declare const _default: typeof __VLS_export;
|
|
35
|
+
export default _default;
|
|
36
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<GanttChartHeaderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<GanttChartHeaderProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, GanttChartHeaderSlots>;
|
|
37
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
38
|
+
new (): {
|
|
39
|
+
$slots: S;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare var __VLS_1: {}, __VLS_10: {
|
|
2
|
+
paths: import("../../types/gantt-chart.js").GanttDependencyPath[];
|
|
3
|
+
}, __VLS_12: {
|
|
4
|
+
task: import("../../types/gantt-chart.js").GanttTask;
|
|
5
|
+
bar: import("../../types/gantt-chart.js").GanttTaskBar;
|
|
6
|
+
}, __VLS_14: {
|
|
7
|
+
task: import("../../types/gantt-chart.js").GanttTask;
|
|
8
|
+
bar: import("../../types/gantt-chart.js").GanttTaskBar;
|
|
9
|
+
expanded: boolean;
|
|
10
|
+
toggle: () => void;
|
|
11
|
+
}, __VLS_21: {
|
|
12
|
+
task: import("../../types/gantt-chart.js").GanttTask;
|
|
13
|
+
bar: import("../../types/gantt-chart.js").GanttTaskBar;
|
|
14
|
+
};
|
|
15
|
+
type __VLS_Slots = {} & {
|
|
16
|
+
'today-marker'?: (props: typeof __VLS_1) => any;
|
|
17
|
+
} & {
|
|
18
|
+
dependency?: (props: typeof __VLS_10) => any;
|
|
19
|
+
} & {
|
|
20
|
+
milestone?: (props: typeof __VLS_12) => any;
|
|
21
|
+
} & {
|
|
22
|
+
'group-header'?: (props: typeof __VLS_14) => any;
|
|
23
|
+
} & {
|
|
24
|
+
'task-bar'?: (props: typeof __VLS_21) => any;
|
|
25
|
+
};
|
|
26
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
27
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
28
|
+
declare const _default: typeof __VLS_export;
|
|
29
|
+
export default _default;
|
|
30
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
31
|
+
new (): {
|
|
32
|
+
$slots: S;
|
|
33
|
+
};
|
|
34
|
+
};
|