@bagelink/vue 1.12.16 → 1.12.23
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/components/calendar/CalendarTypes.d.ts.map +1 -1
- package/dist/components/calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
- package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/index.cjs +107 -107
- package/dist/index.mjs +12048 -11787
- package/dist/plugins/bagel.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/Pagination.vue +1 -1
- package/src/components/calendar/CalendarTypes.ts +11 -0
- package/src/components/calendar/Index.vue +25 -4
- package/src/components/calendar/views/WeekView.vue +260 -16
- package/src/components/form/inputs/ArrayInput.vue +4 -3
- package/src/components/form/inputs/RichText/utils/formatting.ts +4 -4
- package/src/dialog/DialogForm.vue +7 -9
- package/src/form-flow/FormFlow.vue +14 -1
- package/src/form-flow/MultiStepForm.vue +1 -1
- package/src/form-flow/form-flow.ts +2 -2
- package/src/i18n/locales/en.json +47 -0
- package/src/i18n/locales/es.json +1 -0
- package/src/i18n/locales/fr.json +1 -0
- package/src/i18n/locales/he.json +47 -0
- package/src/i18n/locales/it.json +1 -0
- package/src/i18n/locales/ru.json +1 -0
- package/src/plugins/bagel.ts +2 -0
- package/src/plugins/useModal.ts +2 -2
- package/src/styles/buttons.css +6 -0
- package/src/styles/text.css +1759 -1755
- package/src/types/index.ts +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAEvE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAEvE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAE3I,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;AAEvC,YAAY,EACX,mBAAmB,EACnB,mBAAmB,IAAI,uBAAuB,EAC9C,YAAY,GACZ,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEzC,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,MAAM,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,QAAQ,CAAA;CACf,GAAG,MAAM,CAAA;AAEV,MAAM,MAAM,SAAS,GAChB,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAC9G,SAAS,GAAG,OAAO,GACnB,YAAY,GAAG,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,GAC5F,cAAc,GAAG,iBAAiB,GAAG,YAAY,GAAG,YAAY,GAAG,eAAe,GAClF,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GACtE,OAAO,CAAA;AAGX,MAAM,MAAM,iBAAiB,GAExB,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAC9G,SAAS,GAAG,OAAO,GAEnB,YAAY,GAAG,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,GAC5F,cAAc,GAAG,iBAAiB,GAAG,YAAY,GAAG,YAAY,GAAG,eAAe,GAElF,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,GAEhF,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAE9I,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAEzJ,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAEzJ,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAEzJ,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAEzJ,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE/B,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAED,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAA;AAEnD,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagelink/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.12.
|
|
4
|
+
"version": "1.12.23",
|
|
5
5
|
"description": "Bagel core sdk packages",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bagel Studio",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"signature_pad": "^5.0.9",
|
|
91
91
|
"vue-i18n": "^11.2.8",
|
|
92
92
|
"vue-toastification": "^2.0.0-rc.5",
|
|
93
|
-
"@bagelink/utils": "1.12.
|
|
93
|
+
"@bagelink/utils": "1.12.23"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"dev": "tsx watch src/index.ts",
|
|
@@ -14,11 +14,22 @@ export type CalendarView = 'Week' | 'Month' | 'Day' | 'Agenda'
|
|
|
14
14
|
|
|
15
15
|
export type WeekStart = 'Sunday' | 'Monday'
|
|
16
16
|
|
|
17
|
+
export interface AvailabilitySlot {
|
|
18
|
+
start_time: Date
|
|
19
|
+
end_time: Date
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface CalendarProps {
|
|
18
23
|
events?: CalendarEvent[]
|
|
19
24
|
startDate?: Date
|
|
20
25
|
view?: CalendarView
|
|
21
26
|
weekStart?: WeekStart
|
|
27
|
+
/** When true, renders availability blocks on the week grid */
|
|
28
|
+
availabilityMode?: boolean
|
|
29
|
+
/** When true, allows drag-to-paint editing of availability (requires availabilityMode) */
|
|
30
|
+
availabilityEditable?: boolean
|
|
31
|
+
/** Existing availability slots to display as painted blocks */
|
|
32
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
export interface CalendarViewState {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type { AvailabilitySlot, CalendarEvent, CalendarView, CalendarViewState, WeekStart } from './CalendarTypes'
|
|
4
4
|
import { timeDelta, Btn, ListItem, Dropdown, formatDate } from '@bagelink/vue'
|
|
5
5
|
import { ref, computed, onMounted } from 'vue'
|
|
6
6
|
import CalendarPopover from './CalendarPopover.vue'
|
|
@@ -9,11 +9,22 @@ import DayView from './views/DayView.vue'
|
|
|
9
9
|
import MonthView from './views/MonthView.vue'
|
|
10
10
|
import WeekView from './views/WeekView.vue'
|
|
11
11
|
|
|
12
|
-
const props = withDefaults(defineProps<
|
|
12
|
+
const props = withDefaults(defineProps<{
|
|
13
|
+
events?: CalendarEvent[]
|
|
14
|
+
startDate?: Date
|
|
15
|
+
view?: CalendarView
|
|
16
|
+
weekStart?: WeekStart
|
|
17
|
+
availabilityMode?: boolean
|
|
18
|
+
availabilityEditable?: boolean
|
|
19
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
20
|
+
}>(), {
|
|
13
21
|
startDate: () => new Date(),
|
|
14
22
|
view: 'Week',
|
|
15
23
|
weekStart: 'Sunday',
|
|
16
|
-
events: () => []
|
|
24
|
+
events: () => [],
|
|
25
|
+
availabilityMode: false,
|
|
26
|
+
availabilityEditable: false,
|
|
27
|
+
availabilitySlots: () => []
|
|
17
28
|
})
|
|
18
29
|
|
|
19
30
|
const emit = defineEmits<{
|
|
@@ -24,6 +35,7 @@ const emit = defineEmits<{
|
|
|
24
35
|
(e: 'dateChange', changeEvent: CalendarViewState): void
|
|
25
36
|
(e: 'viewChange', changeEvent: CalendarViewState): void
|
|
26
37
|
(e: 'ready', changeEvent: CalendarViewState): void
|
|
38
|
+
(e: 'availabilityChange', slots: AvailabilitySlot[]): void
|
|
27
39
|
}>()
|
|
28
40
|
|
|
29
41
|
const currentDate = ref(new Date(props.startDate))
|
|
@@ -117,7 +129,14 @@ defineExpose({
|
|
|
117
129
|
closePopover
|
|
118
130
|
})
|
|
119
131
|
|
|
120
|
-
onMounted(() => {
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
console.log('[Calendar] props:', {
|
|
134
|
+
availabilityMode: props.availabilityMode,
|
|
135
|
+
availabilityEditable: props.availabilityEditable,
|
|
136
|
+
availabilitySlots: props.availabilitySlots?.length
|
|
137
|
+
})
|
|
138
|
+
emit('ready', state.value)
|
|
139
|
+
})
|
|
121
140
|
</script>
|
|
122
141
|
|
|
123
142
|
<template>
|
|
@@ -144,8 +163,10 @@ onMounted(() => { emit('ready', state.value) })
|
|
|
144
163
|
</div>
|
|
145
164
|
<component
|
|
146
165
|
:is="views[currentView]" :events="events" :start-date="currentDate" :week-start="weekStart"
|
|
166
|
+
:availability-mode="props.availabilityMode" :availability-editable="props.availabilityEditable" :availability-slots="props.availabilitySlots"
|
|
147
167
|
@event-click="handleEventClick" @event-create="handleEventCreate" @event-update="handleEventUpdate"
|
|
148
168
|
@event-delete="handleEventDelete" @date-change="handleDateChange" @open-popover="openPopover"
|
|
169
|
+
@availability-change="(slots: AvailabilitySlot[]) => emit('availabilityChange', slots)"
|
|
149
170
|
>
|
|
150
171
|
<template #eventContent="{ event, close }">
|
|
151
172
|
<slot name="eventContent" :event="event" :close="close" />
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { SetupContext, ComponentPublicInstance } from 'vue'
|
|
3
|
-
import type { CalendarEvent, WeekStart } from '../CalendarTypes'
|
|
3
|
+
import type { AvailabilitySlot, CalendarEvent, WeekStart } from '../CalendarTypes'
|
|
4
4
|
import { formatDate } from '@bagelink/vue'
|
|
5
|
-
import { ref, computed, onMounted, onUnmounted, useSlots, nextTick } from 'vue'
|
|
5
|
+
import { ref, computed, onMounted, onUnmounted, useSlots, nextTick, watch } from 'vue'
|
|
6
6
|
|
|
7
7
|
interface WeekViewEvent extends CalendarEvent {
|
|
8
8
|
top: number
|
|
@@ -17,16 +17,23 @@ const props = withDefaults(defineProps<{
|
|
|
17
17
|
events?: CalendarEvent[]
|
|
18
18
|
startDate?: Date
|
|
19
19
|
weekStart?: WeekStart
|
|
20
|
+
availabilityMode?: boolean
|
|
21
|
+
availabilityEditable?: boolean
|
|
22
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
20
23
|
}>(), {
|
|
21
24
|
startDate: () => new Date(),
|
|
22
25
|
weekStart: 'Sunday',
|
|
23
|
-
events: () => []
|
|
26
|
+
events: () => [],
|
|
27
|
+
availabilityMode: false,
|
|
28
|
+
availabilityEditable: false,
|
|
29
|
+
availabilitySlots: () => []
|
|
24
30
|
})
|
|
25
31
|
|
|
26
32
|
const emit = defineEmits<{
|
|
27
33
|
(e: 'eventClick', event: CalendarEvent): void
|
|
28
34
|
(e: 'eventCreate', event: { start_time: Date, end_time: Date }): void
|
|
29
35
|
(e: 'openPopover', event: CalendarEvent, position?: { top: number, left: number }): void
|
|
36
|
+
(e: 'availabilityChange', slots: AvailabilitySlot[]): void
|
|
30
37
|
}>()
|
|
31
38
|
|
|
32
39
|
const slots: SetupContext['slots'] = useSlots()
|
|
@@ -52,16 +59,24 @@ const dragState = ref({
|
|
|
52
59
|
endTime: undefined as Date | undefined
|
|
53
60
|
})
|
|
54
61
|
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
62
|
+
// ===== AVAILABILITY MODE =====
|
|
63
|
+
const AVAILABILITY_SNAP_MINUTES = 30
|
|
64
|
+
const localAvailability = ref<AvailabilitySlot[]>([])
|
|
65
|
+
const availDragState = ref({
|
|
66
|
+
isDragging: false,
|
|
67
|
+
mode: 'add' as 'add' | 'remove',
|
|
68
|
+
dayIndex: -1,
|
|
69
|
+
startMinute: 0,
|
|
70
|
+
currentMinute: 0
|
|
71
|
+
})
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
// Sync external availability slots to local state
|
|
74
|
+
watch(() => props.availabilitySlots, (slots) => {
|
|
75
|
+
localAvailability.value = slots.map(s => ({
|
|
76
|
+
start_time: new Date(s.start_time),
|
|
77
|
+
end_time: new Date(s.end_time)
|
|
78
|
+
}))
|
|
79
|
+
}, { immediate: true })
|
|
65
80
|
|
|
66
81
|
// Calculate week days based on start date and week start preference
|
|
67
82
|
const weekDays = computed(() => {
|
|
@@ -83,19 +98,197 @@ const weekDays = computed(() => {
|
|
|
83
98
|
return days
|
|
84
99
|
})
|
|
85
100
|
|
|
101
|
+
function snapMinutes(minutes: number): number {
|
|
102
|
+
return Math.round(minutes / AVAILABILITY_SNAP_MINUTES) * AVAILABILITY_SNAP_MINUTES
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getAvailabilitySlotsForDay(day: Date) {
|
|
106
|
+
return localAvailability.value.filter((slot) => {
|
|
107
|
+
const slotDate = new Date(slot.start_time)
|
|
108
|
+
return slotDate.getFullYear() === day.getFullYear()
|
|
109
|
+
&& slotDate.getMonth() === day.getMonth()
|
|
110
|
+
&& slotDate.getDate() === day.getDate()
|
|
111
|
+
}).map((slot) => {
|
|
112
|
+
const startMinutes = new Date(slot.start_time).getHours() * 60 + new Date(slot.start_time).getMinutes()
|
|
113
|
+
const endMinutes = new Date(slot.end_time).getHours() * 60 + new Date(slot.end_time).getMinutes()
|
|
114
|
+
return {
|
|
115
|
+
top: (startMinutes / slotDuration) * slotHeight,
|
|
116
|
+
height: ((endMinutes - startMinutes) / slotDuration) * slotHeight,
|
|
117
|
+
slot
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function availabilityPreviewForDay(dayIndex: number) {
|
|
123
|
+
if (!availDragState.value.isDragging || availDragState.value.dayIndex !== dayIndex) return null
|
|
124
|
+
const minM = Math.min(availDragState.value.startMinute, availDragState.value.currentMinute)
|
|
125
|
+
const maxM = Math.max(availDragState.value.startMinute, availDragState.value.currentMinute)
|
|
126
|
+
return {
|
|
127
|
+
top: (minM / slotDuration) * slotHeight,
|
|
128
|
+
height: ((maxM - minM) / slotDuration) * slotHeight,
|
|
129
|
+
mode: availDragState.value.mode
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isMinuteInAvailability(day: Date, minute: number): boolean {
|
|
134
|
+
return localAvailability.value.some((slot) => {
|
|
135
|
+
const slotDate = new Date(slot.start_time)
|
|
136
|
+
if (slotDate.getFullYear() !== day.getFullYear()
|
|
137
|
+
|| slotDate.getMonth() !== day.getMonth()
|
|
138
|
+
|| slotDate.getDate() !== day.getDate()) { return false
|
|
139
|
+
}
|
|
140
|
+
const startM = slotDate.getHours() * 60 + slotDate.getMinutes()
|
|
141
|
+
const endM = new Date(slot.end_time).getHours() * 60 + new Date(slot.end_time).getMinutes()
|
|
142
|
+
return minute >= startM && minute < endM
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function handleAvailMouseDown(e: MouseEvent, dayIndex: number) {
|
|
147
|
+
if (!props.availabilityEditable) return
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
e.stopPropagation()
|
|
150
|
+
const day = weekDays.value[dayIndex]
|
|
151
|
+
const minutes = snapMinutes(getMinutesFromY(e.clientY))
|
|
152
|
+
const isExisting = isMinuteInAvailability(day, minutes)
|
|
153
|
+
|
|
154
|
+
availDragState.value = {
|
|
155
|
+
isDragging: true,
|
|
156
|
+
mode: isExisting ? 'remove' : 'add',
|
|
157
|
+
dayIndex,
|
|
158
|
+
startMinute: minutes,
|
|
159
|
+
currentMinute: minutes + AVAILABILITY_SNAP_MINUTES
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
document.addEventListener('mousemove', handleAvailMouseMove)
|
|
163
|
+
document.addEventListener('mouseup', handleAvailMouseUp)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handleAvailMouseMove(e: MouseEvent) {
|
|
167
|
+
if (!availDragState.value.isDragging) return
|
|
168
|
+
const minutes = snapMinutes(getMinutesFromY(e.clientY))
|
|
169
|
+
availDragState.value.currentMinute = minutes
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleAvailMouseUp() {
|
|
173
|
+
if (!availDragState.value.isDragging) return
|
|
174
|
+
const { dayIndex, startMinute, currentMinute, mode } = availDragState.value
|
|
175
|
+
const day = weekDays.value[dayIndex]
|
|
176
|
+
const minM = Math.min(startMinute, currentMinute)
|
|
177
|
+
const maxM = Math.max(startMinute, currentMinute)
|
|
178
|
+
|
|
179
|
+
if (maxM - minM < AVAILABILITY_SNAP_MINUTES) {
|
|
180
|
+
availDragState.value.isDragging = false
|
|
181
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
182
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const newStart = new Date(day)
|
|
187
|
+
newStart.setHours(Math.floor(minM / 60), minM % 60, 0, 0)
|
|
188
|
+
const newEnd = new Date(day)
|
|
189
|
+
newEnd.setHours(Math.floor(maxM / 60), maxM % 60, 0, 0)
|
|
190
|
+
|
|
191
|
+
if (mode === 'add') {
|
|
192
|
+
// Add and merge overlapping
|
|
193
|
+
const otherDays = localAvailability.value.filter((s) => {
|
|
194
|
+
const d = new Date(s.start_time)
|
|
195
|
+
return d.getFullYear() !== day.getFullYear()
|
|
196
|
+
|| d.getMonth() !== day.getMonth()
|
|
197
|
+
|| d.getDate() !== day.getDate()
|
|
198
|
+
})
|
|
199
|
+
const sameDaySlots = localAvailability.value.filter((s) => {
|
|
200
|
+
const d = new Date(s.start_time)
|
|
201
|
+
return d.getFullYear() === day.getFullYear()
|
|
202
|
+
&& d.getMonth() === day.getMonth()
|
|
203
|
+
&& d.getDate() === day.getDate()
|
|
204
|
+
})
|
|
205
|
+
sameDaySlots.push({ start_time: newStart, end_time: newEnd })
|
|
206
|
+
// Sort and merge
|
|
207
|
+
sameDaySlots.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime())
|
|
208
|
+
const merged: AvailabilitySlot[] = []
|
|
209
|
+
for (const s of sameDaySlots) {
|
|
210
|
+
if (merged.length === 0) {
|
|
211
|
+
merged.push({ start_time: new Date(s.start_time), end_time: new Date(s.end_time) })
|
|
212
|
+
} else {
|
|
213
|
+
const last = merged[merged.length - 1]
|
|
214
|
+
if (new Date(s.start_time).getTime() <= new Date(last.end_time).getTime()) {
|
|
215
|
+
last.end_time = new Date(Math.max(new Date(last.end_time).getTime(), new Date(s.end_time).getTime()))
|
|
216
|
+
} else {
|
|
217
|
+
merged.push({ start_time: new Date(s.start_time), end_time: new Date(s.end_time) })
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
localAvailability.value = [...otherDays, ...merged]
|
|
222
|
+
} else {
|
|
223
|
+
// Remove: subtract the range from existing slots
|
|
224
|
+
const result: AvailabilitySlot[] = []
|
|
225
|
+
for (const s of localAvailability.value) {
|
|
226
|
+
const d = new Date(s.start_time)
|
|
227
|
+
if (d.getFullYear() !== day.getFullYear()
|
|
228
|
+
|| d.getMonth() !== day.getMonth()
|
|
229
|
+
|| d.getDate() !== day.getDate()) {
|
|
230
|
+
result.push(s)
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
const sStart = new Date(s.start_time).getTime()
|
|
234
|
+
const sEnd = new Date(s.end_time).getTime()
|
|
235
|
+
const rStart = newStart.getTime()
|
|
236
|
+
const rEnd = newEnd.getTime()
|
|
237
|
+
|
|
238
|
+
// No overlap
|
|
239
|
+
if (sEnd <= rStart || sStart >= rEnd) {
|
|
240
|
+
result.push(s)
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
// Left remainder
|
|
244
|
+
if (sStart < rStart) {
|
|
245
|
+
result.push({ start_time: new Date(sStart), end_time: new Date(rStart) })
|
|
246
|
+
}
|
|
247
|
+
// Right remainder
|
|
248
|
+
if (sEnd > rEnd) {
|
|
249
|
+
result.push({ start_time: new Date(rEnd), end_time: new Date(sEnd) })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
localAvailability.value = result
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
emit('availabilityChange', localAvailability.value)
|
|
256
|
+
availDragState.value.isDragging = false
|
|
257
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
258
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Time indicators
|
|
262
|
+
const currentTimeTop = ref(0)
|
|
263
|
+
const isToday = ref(false)
|
|
264
|
+
const currentTimeInterval = ref<any>()
|
|
265
|
+
|
|
266
|
+
const timeSlotsContainer = ref<HTMLElement>()
|
|
267
|
+
const calendarGrid = ref<HTMLElement>()
|
|
268
|
+
const dayColumns = ref<HTMLElement[]>([])
|
|
269
|
+
const dayColumnsContainer = ref<HTMLElement>()
|
|
270
|
+
const scrollableContainer = ref<HTMLElement>()
|
|
271
|
+
|
|
272
|
+
function getMinutesFromY(clientY: number): number {
|
|
273
|
+
if (!dayColumnsContainer.value) return 0
|
|
274
|
+
const rect = dayColumnsContainer.value.getBoundingClientRect()
|
|
275
|
+
const relativeY = clientY - rect.top
|
|
276
|
+
return Math.max(0, Math.min((relativeY / slotHeight) * slotDuration, 24 * 60))
|
|
277
|
+
}
|
|
278
|
+
|
|
86
279
|
// Generate hourly time slots
|
|
87
280
|
const timeSlots = computed(() => {
|
|
88
|
-
const
|
|
281
|
+
const items = []
|
|
89
282
|
for (let hour = timeRange.start; hour < timeRange.end; hour++) {
|
|
90
283
|
for (let minute = 0; minute < 60; minute += slotDuration) {
|
|
91
|
-
|
|
284
|
+
items.push({
|
|
92
285
|
hour,
|
|
93
286
|
minute,
|
|
94
287
|
time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
|
|
95
288
|
})
|
|
96
289
|
}
|
|
97
290
|
}
|
|
98
|
-
return
|
|
291
|
+
return items
|
|
99
292
|
})
|
|
100
293
|
|
|
101
294
|
// Calculate drag preview positioning using the same formula as events
|
|
@@ -410,6 +603,11 @@ function scrollToTime(time: number) {
|
|
|
410
603
|
|
|
411
604
|
// Lifecycle hooks
|
|
412
605
|
onMounted(() => {
|
|
606
|
+
console.log('[WeekView] mounted with props:', {
|
|
607
|
+
availabilityMode: props.availabilityMode,
|
|
608
|
+
availabilityEditable: props.availabilityEditable,
|
|
609
|
+
availabilitySlotsCount: props.availabilitySlots?.length
|
|
610
|
+
})
|
|
413
611
|
updateCurrentTimeIndicator()
|
|
414
612
|
currentTimeInterval.value = setInterval(updateCurrentTimeIndicator, 60000)
|
|
415
613
|
|
|
@@ -420,6 +618,8 @@ onMounted(() => {
|
|
|
420
618
|
onUnmounted(() => {
|
|
421
619
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
422
620
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
621
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
622
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
423
623
|
|
|
424
624
|
if (currentTimeInterval.value) {
|
|
425
625
|
clearInterval(currentTimeInterval.value)
|
|
@@ -466,8 +666,29 @@ onUnmounted(() => {
|
|
|
466
666
|
|
|
467
667
|
<div
|
|
468
668
|
v-for="(day, index) in weekDays" :key="day.toISOString()" :ref="el => setDayColumnRef(el, index)"
|
|
469
|
-
class="day-column top bottom border-start relative"
|
|
669
|
+
class="day-column top bottom border-start relative"
|
|
670
|
+
@mousedown="props.availabilityEditable ? handleAvailMouseDown($event, index) : handleMouseDown($event, day)"
|
|
470
671
|
>
|
|
672
|
+
<!-- Availability painted blocks (behind events) -->
|
|
673
|
+
<template v-if="props.availabilityMode">
|
|
674
|
+
<div
|
|
675
|
+
v-for="(block, bi) in getAvailabilitySlotsForDay(day)" :key="`avail-${bi}`"
|
|
676
|
+
class="availability-block absolute start end"
|
|
677
|
+
:class="{ 'availability-editable': props.availabilityEditable }"
|
|
678
|
+
:style="{ top: `${block.top}px`, height: `${block.height}px` }"
|
|
679
|
+
/>
|
|
680
|
+
<!-- Availability drag preview -->
|
|
681
|
+
<div
|
|
682
|
+
v-if="availabilityPreviewForDay(index)"
|
|
683
|
+
class="availability-preview absolute start end pointer-events-none"
|
|
684
|
+
:class="{ 'avail-remove': availabilityPreviewForDay(index)!.mode === 'remove' }"
|
|
685
|
+
:style="{
|
|
686
|
+
top: `${availabilityPreviewForDay(index)!.top}px`,
|
|
687
|
+
height: `${availabilityPreviewForDay(index)!.height}px`,
|
|
688
|
+
}"
|
|
689
|
+
/>
|
|
690
|
+
</template>
|
|
691
|
+
|
|
471
692
|
<template
|
|
472
693
|
v-for="event in processedEvents.filter(e =>
|
|
473
694
|
new Date(e.start_time).toDateString() === day.toDateString())" :key="event.id"
|
|
@@ -587,6 +808,29 @@ onUnmounted(() => {
|
|
|
587
808
|
justify-content: center; */
|
|
588
809
|
}
|
|
589
810
|
|
|
811
|
+
.availability-block {
|
|
812
|
+
background-color: rgba(76, 175, 80, 0.18);
|
|
813
|
+
border-inline-start: 3px solid rgba(76, 175, 80, 0.5);
|
|
814
|
+
z-index: 0;
|
|
815
|
+
pointer-events: none;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.availability-block.availability-editable {
|
|
819
|
+
background-color: rgba(33, 150, 243, 0.18);
|
|
820
|
+
border-inline-start: 3px solid rgba(33, 150, 243, 0.5);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.availability-preview {
|
|
824
|
+
background-color: rgba(76, 175, 80, 0.25);
|
|
825
|
+
border: 1px dashed rgba(76, 175, 80, 0.6);
|
|
826
|
+
z-index: 0;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.availability-preview.avail-remove {
|
|
830
|
+
background-color: rgba(244, 67, 54, 0.15);
|
|
831
|
+
border-color: rgba(244, 67, 54, 0.5);
|
|
832
|
+
}
|
|
833
|
+
|
|
590
834
|
@media (max-width: 910px) {
|
|
591
835
|
.weekGrid {
|
|
592
836
|
display: grid;
|
|
@@ -136,7 +136,7 @@ function getItemRef(i: number) {
|
|
|
136
136
|
{{ helpText }}
|
|
137
137
|
</p>
|
|
138
138
|
<template v-if="!collapsible">
|
|
139
|
-
<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center">
|
|
139
|
+
<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center pb-1">
|
|
140
140
|
<slot :item="getItemRef(i)" :index="i" />
|
|
141
141
|
<div v-if="allowDelete" class="ms-075 my-1 border-start h-100p ps-025">
|
|
142
142
|
<Btn
|
|
@@ -149,13 +149,14 @@ function getItemRef(i: number) {
|
|
|
149
149
|
<Draggable v-if="collapsible" :model-value="items" handle=".grab" :disabled="!allowReorder" class="grid gap-05 mb-05" @end="onDraggableEnd">
|
|
150
150
|
<template #default="{ index: i }">
|
|
151
151
|
<div class="border radius-1 overflow-hidden">
|
|
152
|
-
<div class="grid gap-
|
|
152
|
+
<div class="grid gap-0 grid-array-line align-items-center pe-025 hover">
|
|
153
153
|
<Btn
|
|
154
154
|
v-if="allowReorder" v-tooltip="resolveI18n('Drag to reorder')" flat thin icon="drag_indicator"
|
|
155
155
|
class="grab"
|
|
156
156
|
/>
|
|
157
|
+
<div v-else />
|
|
157
158
|
<Btn
|
|
158
|
-
full-width align-txt="start" class="px-
|
|
159
|
+
full-width align-txt="start" class="px-025 bg-transparent color-inherit" :ripple="false"
|
|
159
160
|
:icon="minimizedItems[i] ? 'expand_more' : 'expand_less'" @click="toggleItem(i)"
|
|
160
161
|
>
|
|
161
162
|
<p class="ellipsis-1">
|
|
@@ -178,10 +178,10 @@ export function formatting(state: EditorState) {
|
|
|
178
178
|
|
|
179
179
|
const clear = () => {
|
|
180
180
|
console.log('[Clear Format] Starting clear format process', state)
|
|
181
|
-
console.assert(state, '[Clear Format] State must exist')
|
|
182
|
-
console.assert(state.doc, '[Clear Format] Document must exist')
|
|
183
|
-
console.assert(state.range, '[Clear Format] Range must exist')
|
|
184
|
-
console.assert(state.selection, '[Clear Format] Selection must exist')
|
|
181
|
+
console.assert(!!state, '[Clear Format] State must exist')
|
|
182
|
+
console.assert(!!state.doc, '[Clear Format] Document must exist')
|
|
183
|
+
console.assert(!!state.range, '[Clear Format] Range must exist')
|
|
184
|
+
console.assert(!!state.selection, '[Clear Format] Selection must exist')
|
|
185
185
|
|
|
186
186
|
if (!state.doc || !state.range || !state.selection) {
|
|
187
187
|
console.log('[Clear Format] No document or selection')
|
|
@@ -108,15 +108,13 @@ function handleClose() {
|
|
|
108
108
|
:open="open" :title="title" :width="width" :position="position" :dismissable="dismissable"
|
|
109
109
|
@update:open="$emit('update:open', $event)" @close="handleClose"
|
|
110
110
|
>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<FormFlow ref="formFlowRef" v-model="formData" :schema="schema" />
|
|
119
|
-
</form>
|
|
111
|
+
<!-- Error message -->
|
|
112
|
+
<div v-if="error" class="dialog-form-error bg-red-30 color-red radius-1 mb-1 txt14 px-1 py-075">
|
|
113
|
+
{{ error }}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Form fields -->
|
|
117
|
+
<FormFlow ref="formFlowRef" v-model="formData" :schema="schema" @submit="handleSubmit" />
|
|
120
118
|
|
|
121
119
|
<template #footer>
|
|
122
120
|
<!-- Delete button (left side) -->
|
|
@@ -348,7 +348,7 @@ defineExpose({
|
|
|
348
348
|
</script>
|
|
349
349
|
|
|
350
350
|
<template>
|
|
351
|
-
<form ref="formElement" :class="schema._class" @submit="handleSubmit">
|
|
351
|
+
<form ref="formElement" class="grid-form" :class="schema._class" @submit="handleSubmit">
|
|
352
352
|
<template v-if="formState === 'success'">
|
|
353
353
|
<slot name="success" :form-data="formData" />
|
|
354
354
|
</template>
|
|
@@ -376,6 +376,19 @@ defineExpose({
|
|
|
376
376
|
</ArrayInput>
|
|
377
377
|
</template>
|
|
378
378
|
|
|
379
|
+
<!-- Array field with primitive string item type e.g. $.array('Label', 'text') -->
|
|
380
|
+
<template v-else-if="field._type === 'array' && typeof field._config.schema === 'string'">
|
|
381
|
+
<ArrayInput v-bind="getFieldProps(field, key)">
|
|
382
|
+
<template #default="{ item }">
|
|
383
|
+
<component
|
|
384
|
+
:is="getFieldComponent(field._config.schema as string)"
|
|
385
|
+
:model-value="(item.value as string | number | undefined)"
|
|
386
|
+
@update:model-value="(val: any) => { item.value = val }"
|
|
387
|
+
/>
|
|
388
|
+
</template>
|
|
389
|
+
</ArrayInput>
|
|
390
|
+
</template>
|
|
391
|
+
|
|
379
392
|
<!-- Array field with custom component -->
|
|
380
393
|
<template v-else-if="field._type === 'array' && field._config.schema">
|
|
381
394
|
<ArrayInput v-bind="getFieldProps(field, key)">
|
|
@@ -360,7 +360,7 @@ defineExpose({
|
|
|
360
360
|
<!-- Default: Use FormFlow -->
|
|
361
361
|
<FormFlow v-model="formData" :schema="currentStepSchema" :components="components" :errors="stepErrors">
|
|
362
362
|
<!-- Pass through field slots -->
|
|
363
|
-
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
363
|
+
<template v-for="(_, name) in ($slots as Record<string, unknown>)" #[name]="slotProps">
|
|
364
364
|
<slot :name="name" v-bind="slotProps" />
|
|
365
365
|
</template>
|
|
366
366
|
</FormFlow>
|
|
@@ -285,8 +285,8 @@ export const $ = {
|
|
|
285
285
|
},
|
|
286
286
|
|
|
287
287
|
richtext(
|
|
288
|
-
labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean }),
|
|
289
|
-
config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean }
|
|
288
|
+
labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean }),
|
|
289
|
+
config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean }
|
|
290
290
|
): FieldBuilder<string> {
|
|
291
291
|
return new Field('richtext', parseArgs(labelOrConfig, config))
|
|
292
292
|
},
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -182,6 +182,7 @@
|
|
|
182
182
|
"closeEditor": "Close Editor (ESC)",
|
|
183
183
|
"blockNotEditable": "This block's content comes from a data source",
|
|
184
184
|
"blockNotEditableDesc": "and cannot be edited here.",
|
|
185
|
+
"dataSourceHint": "Some content in this block may come from a data source. To change it, edit the data directly.",
|
|
185
186
|
"screenTooSmall": "The screen is too small for this editor. Try reducing zoom or using a larger screen.",
|
|
186
187
|
"cancel": "Cancel",
|
|
187
188
|
"publish": "Publish",
|
|
@@ -210,6 +211,52 @@
|
|
|
210
211
|
"production": "Production",
|
|
211
212
|
"publishFailed": "Publish failed"
|
|
212
213
|
},
|
|
214
|
+
"collection": {
|
|
215
|
+
"loadError": "Loading failed",
|
|
216
|
+
"newItemTitle": "New item — {label}",
|
|
217
|
+
"createSubmit": "Create",
|
|
218
|
+
"editTitle": "Edit",
|
|
219
|
+
"saveSubmit": "Save",
|
|
220
|
+
"importTitle": "Import JSON — {label}",
|
|
221
|
+
"importSubmit": "Import",
|
|
222
|
+
"importJsonLabel": "JSON content",
|
|
223
|
+
"importJsonHint": "Array of objects [ {...}, {...} ] or a single object",
|
|
224
|
+
"importSummary": "{imported} imported · {failed} failed",
|
|
225
|
+
"deleteItemTitle": "Delete item",
|
|
226
|
+
"deleteItemMessage": "Delete this item?",
|
|
227
|
+
"deleteConfirm": "Delete",
|
|
228
|
+
"batchDeleteTitle": "Batch delete",
|
|
229
|
+
"batchDeleteMessage": "Delete {count} items?",
|
|
230
|
+
"batchDeleteSummary": "{deleted} deleted · {failed} failed",
|
|
231
|
+
"batchUpdateTitle": "Batch update — {count} items",
|
|
232
|
+
"updateAllSubmit": "Update all",
|
|
233
|
+
"batchUpdateSummary": "Updated · {failed} failed",
|
|
234
|
+
"selectedCount": "{count} selected",
|
|
235
|
+
"updateBtn": "Update",
|
|
236
|
+
"deleteBtn": "Delete",
|
|
237
|
+
"searchPlaceholder": "Search...",
|
|
238
|
+
"refreshTooltip": "Refresh",
|
|
239
|
+
"importTooltip": "Import JSON",
|
|
240
|
+
"newItemBtn": "New item",
|
|
241
|
+
"deleteTooltip": "Delete",
|
|
242
|
+
"reorderTooltip": "Reorder",
|
|
243
|
+
"reorderSaving": "Saving order..."
|
|
244
|
+
},
|
|
245
|
+
"datastores": {
|
|
246
|
+
"fieldName": "Name",
|
|
247
|
+
"fieldStore": "Store",
|
|
248
|
+
"fieldStoreHint": "e.g.: default",
|
|
249
|
+
"fieldCollection": "Collection",
|
|
250
|
+
"fieldCollectionHint": "e.g.: my_collection",
|
|
251
|
+
"fieldDescription": "Description",
|
|
252
|
+
"createTitle": "Create new Datastore",
|
|
253
|
+
"createSubmit": "Create",
|
|
254
|
+
"editTitle": "Edit {name}",
|
|
255
|
+
"saveSubmit": "Save",
|
|
256
|
+
"addBtn": "Add Datastore",
|
|
257
|
+
"empty": "No Datastores yet",
|
|
258
|
+
"deleteConfirm": "Delete \"{name}\"?"
|
|
259
|
+
},
|
|
213
260
|
"admin": {
|
|
214
261
|
"content": "Content",
|
|
215
262
|
"blox": "Blox",
|