@bagelink/vue 1.4.139 → 1.4.145
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/Btn.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +1 -1
- package/dist/components/Modal.vue.d.ts +3 -0
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts +1 -1
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +11 -3
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/LineChart.vue.d.ts +9 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts +30 -2
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +8 -0
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts +9 -0
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +0 -14
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts +15 -15
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -3
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/media-clean.d.ts +2 -0
- package/dist/components/form/inputs/RichText/utils/media-clean.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -4
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/index.cjs +123 -22
- package/dist/index.mjs +123 -22
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Btn.vue +50 -42
- package/src/components/Modal.vue +49 -50
- package/src/components/analytics/BarChart.vue +118 -7
- package/src/components/analytics/KpiCard.vue +2 -2
- package/src/components/analytics/LineChart.vue +189 -105
- package/src/components/analytics/PieChart.vue +392 -49
- package/src/components/dataTable/DataTable.vue +1 -1
- package/src/components/form/inputs/RichText/CheckList.md +23 -0
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +243 -27
- package/src/components/form/inputs/RichText/components/TableGridSelector.vue +94 -0
- package/src/components/form/inputs/RichText/composables/useCommands.ts +45 -0
- package/src/components/form/inputs/RichText/composables/useEditor.ts +13 -10
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +3 -128
- package/src/components/form/inputs/RichText/config.ts +33 -10
- package/src/components/form/inputs/RichText/editor.css +300 -33
- package/src/components/form/inputs/RichText/index.vue +3271 -130
- package/src/components/form/inputs/RichText/richTextTypes.ts +7 -3
- package/src/components/form/inputs/RichText/utils/commands.ts +851 -90
- package/src/components/form/inputs/RichText/utils/formatting.ts +17 -15
- package/src/components/form/inputs/RichText/utils/media-clean.ts +0 -0
- package/src/components/form/inputs/RichText/utils/media.ts +133 -67
- package/src/components/form/inputs/RichText/utils/selection.ts +40 -11
- package/src/components/form/inputs/RichText/utils/table.ts +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/AppContent.vue +26 -26
- package/src/components/layout/AppLayout.vue +21 -3
- package/src/components/layout/AppSidebar.vue +5 -2
- package/src/styles/layout.css +267 -0
- package/src/styles/mobilLayout.css +266 -0
- package/src/styles/modal.css +3 -17
|
@@ -19,30 +19,32 @@ export function formatting(state: EditorState) {
|
|
|
19
19
|
// Don't apply inline styles directly to block elements
|
|
20
20
|
if (parentBlock?.tagName.match(/^H[1-6]|P|BLOCKQUOTE|LI$/)) {
|
|
21
21
|
if (!range.collapsed && range.toString().trim()) {
|
|
22
|
-
|
|
23
|
-
if (command === 'underline')
|
|
24
|
-
else if (command === 'bold')
|
|
25
|
-
else if (command === 'italic')
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
let element: HTMLElement
|
|
23
|
+
if (command === 'underline') element = doc.createElement('u')
|
|
24
|
+
else if (command === 'bold') element = doc.createElement('b')
|
|
25
|
+
else if (command === 'italic') element = doc.createElement('i')
|
|
26
|
+
else return
|
|
27
|
+
|
|
28
|
+
if (isRTL) element.dir = 'rtl'
|
|
29
|
+
range.surroundContents(element)
|
|
29
30
|
}
|
|
30
31
|
} else {
|
|
31
32
|
if (range.collapsed) return // No selection, nothing to format
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
if (command === 'bold')
|
|
35
|
-
else if (command === 'italic')
|
|
36
|
-
else if (command === 'underline')
|
|
34
|
+
let element: HTMLElement
|
|
35
|
+
if (command === 'bold') element = doc.createElement('b')
|
|
36
|
+
else if (command === 'italic') element = doc.createElement('i')
|
|
37
|
+
else if (command === 'underline') element = doc.createElement('u')
|
|
38
|
+
else return
|
|
37
39
|
|
|
38
40
|
try {
|
|
39
|
-
range.surroundContents(
|
|
40
|
-
} catch
|
|
41
|
+
range.surroundContents(element)
|
|
42
|
+
} catch {
|
|
41
43
|
// If surroundContents fails (e.g., for selections across multiple nodes)
|
|
42
44
|
// Extract the fragment, wrap it, and insert it back
|
|
43
45
|
const fragment = range.extractContents()
|
|
44
|
-
|
|
45
|
-
range.insertNode(
|
|
46
|
+
element.appendChild(fragment)
|
|
47
|
+
range.insertNode(element)
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
}
|
|
File without changes
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ModalApi } from '../../../../../plugins/useModal'
|
|
2
|
-
|
|
3
2
|
import type { EditorState } from '../richTextTypes'
|
|
4
3
|
import { bagelFormUtils as frm } from '../../../../../utils'
|
|
5
4
|
|
|
@@ -53,87 +52,154 @@ export function insertImage(modal: ModalApi, state: EditorState) {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
export function insertLink(modal: ModalApi, state: EditorState) {
|
|
56
|
-
const {
|
|
57
|
-
if (!
|
|
55
|
+
const { doc } = state
|
|
56
|
+
if (!doc) {
|
|
57
|
+
console.log('No doc found')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
// Get current selection
|
|
62
|
+
const selection = doc.getSelection()
|
|
63
|
+
if (!selection || !selection.rangeCount) {
|
|
64
|
+
console.log('No selection found')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let range = selection.getRangeAt(0)
|
|
69
|
+
|
|
70
|
+
// If no text is selected, try to select word at cursor
|
|
71
|
+
if (range.collapsed) {
|
|
72
|
+
const success = selectWordAtCursor(doc, range)
|
|
73
|
+
if (!success) {
|
|
74
|
+
console.log('Failed to select word at cursor')
|
|
75
|
+
// Show a helpful tooltip to the user
|
|
76
|
+
const showTooltipMessage = (state as any).showTooltipMessage
|
|
77
|
+
if (showTooltipMessage) {
|
|
78
|
+
showTooltipMessage('To add a link, please select text first or place cursor within a word')
|
|
79
|
+
} else {
|
|
80
|
+
// Fallback to alert if tooltip system is not available
|
|
81
|
+
alert('To add a link, please select text first or place the cursor within a word you want to turn into a link.')
|
|
72
82
|
}
|
|
83
|
+
return
|
|
73
84
|
}
|
|
74
|
-
|
|
85
|
+
// Get the updated range after word selection
|
|
86
|
+
range = selection.getRangeAt(0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if we're editing an existing link
|
|
90
|
+
const commonAncestor = range.commonAncestorContainer
|
|
91
|
+
const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
|
|
92
|
+
? commonAncestor.parentElement
|
|
93
|
+
: commonAncestor as Element
|
|
94
|
+
|
|
95
|
+
const existingLink = parentElement?.closest('a')
|
|
96
|
+
|
|
97
|
+
// Use the new modal function if available
|
|
98
|
+
if ((state as any).openLinkModal) {
|
|
99
|
+
(state as any).openLinkModal(selection, range, existingLink)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fallback - just show an alert if modal is not available
|
|
104
|
+
console.warn('Link modal not available - openLinkModal function not found')
|
|
105
|
+
alert('Link functionality requires proper modal setup')
|
|
75
106
|
}
|
|
76
107
|
|
|
77
|
-
|
|
108
|
+
// Helper function to select word at cursor
|
|
109
|
+
function selectWordAtCursor(doc: Document, range: Range): boolean {
|
|
110
|
+
if (!range.collapsed) return true // Already has selection
|
|
111
|
+
|
|
112
|
+
let textNode = range.startContainer
|
|
113
|
+
let offset = range.startOffset
|
|
114
|
+
|
|
115
|
+
// If we're in an element node, try to find a text node
|
|
116
|
+
if (textNode.nodeType !== Node.TEXT_NODE) {
|
|
117
|
+
const element = textNode as Element
|
|
118
|
+
if (element.childNodes.length > 0 && offset < element.childNodes.length) {
|
|
119
|
+
const childNode = element.childNodes[offset]
|
|
120
|
+
if (childNode.nodeType === Node.TEXT_NODE) {
|
|
121
|
+
textNode = childNode
|
|
122
|
+
offset = 0
|
|
123
|
+
} else {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const text = textNode.textContent || ''
|
|
132
|
+
if (!text) return false
|
|
133
|
+
|
|
134
|
+
// Find word boundaries - support Hebrew, English, and numbers
|
|
135
|
+
let start = offset
|
|
136
|
+
let end = offset
|
|
137
|
+
|
|
138
|
+
// Define word characters (Hebrew, English, numbers)
|
|
139
|
+
const wordRegex = /[\u0590-\u05FF\u0600-\u06FF\w]/
|
|
140
|
+
|
|
141
|
+
// Move start backwards to find beginning of word
|
|
142
|
+
while (start > 0 && wordRegex.test(text[start - 1])) {
|
|
143
|
+
start--
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Move end forwards to find end of word
|
|
147
|
+
while (end < text.length && wordRegex.test(text[end])) {
|
|
148
|
+
end++
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If we found a word
|
|
152
|
+
if (start < end) {
|
|
153
|
+
range.setStart(textNode, start)
|
|
154
|
+
range.setEnd(textNode, end)
|
|
155
|
+
|
|
156
|
+
const selection = doc.getSelection()
|
|
157
|
+
if (selection) {
|
|
158
|
+
selection.removeAllRanges()
|
|
159
|
+
selection.addRange(range)
|
|
160
|
+
}
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface InsertImbedModalData {
|
|
168
|
+
src: string
|
|
169
|
+
width: number
|
|
170
|
+
height: number
|
|
171
|
+
allowfullscreen: boolean
|
|
172
|
+
}
|
|
78
173
|
|
|
79
174
|
export function insertEmbed(modal: ModalApi, state: EditorState) {
|
|
80
175
|
const { range, doc } = state
|
|
81
176
|
if (!range || !doc) return
|
|
82
177
|
|
|
83
|
-
modal.showModalForm<InsertImbedModalData
|
|
84
|
-
|
|
85
|
-
title: 'Insert Embed',
|
|
178
|
+
modal.showModalForm<InsertImbedModalData>({
|
|
179
|
+
title: 'Embed iframe',
|
|
86
180
|
schema: [
|
|
87
|
-
{ id: '
|
|
181
|
+
{ id: 'src', $el: 'text', label: 'Source URL' },
|
|
88
182
|
frm.frmRow(
|
|
89
|
-
frm.numField('width', 'Width', { min:
|
|
90
|
-
frm.numField('height', 'Height', { min:
|
|
183
|
+
frm.numField('width', 'Width', { min: 1 }),
|
|
184
|
+
frm.numField('height', 'Height', { min: 1 }),
|
|
91
185
|
),
|
|
92
|
-
{ id: '
|
|
186
|
+
{ id: 'allowfullscreen', $el: 'check', label: 'Allow Fullscreen' },
|
|
93
187
|
],
|
|
94
|
-
onSubmit: (data:
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
? url.pathname.slice(1)
|
|
105
|
-
: url.searchParams.get('v')
|
|
106
|
-
if (videoId) {
|
|
107
|
-
embedUrl = `https://www.youtube.com/embed/${videoId}`
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Vimeo
|
|
111
|
-
else if (url.hostname.includes('vimeo.com')) {
|
|
112
|
-
const videoId = url.pathname.split('/').pop()
|
|
113
|
-
if (videoId) {
|
|
114
|
-
embedUrl = `https://player.vimeo.com/video/${videoId}`
|
|
115
|
-
}
|
|
116
|
-
}
|
|
188
|
+
onSubmit: (data: InsertImbedModalData) => {
|
|
189
|
+
if (data.src) {
|
|
190
|
+
const iframe = doc.createElement('iframe')
|
|
191
|
+
Object.assign(iframe, {
|
|
192
|
+
src: data.src,
|
|
193
|
+
width: data.width || 560,
|
|
194
|
+
height: data.height || 315,
|
|
195
|
+
allowfullscreen: data.allowfullscreen || false,
|
|
196
|
+
frameborder: '0'
|
|
197
|
+
})
|
|
117
198
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
height: data.height || 315,
|
|
123
|
-
frameBorder: '0',
|
|
124
|
-
allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
|
|
125
|
-
allowFullscreen: data.allowFullscreen
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// Create a wrapper div for proper alignment and spacing
|
|
129
|
-
const wrapper = doc.createElement('div')
|
|
130
|
-
wrapper.style.textAlign = 'center'
|
|
131
|
-
wrapper.style.margin = '1em 0'
|
|
132
|
-
wrapper.appendChild(iframe)
|
|
133
|
-
|
|
134
|
-
range.deleteContents()
|
|
135
|
-
range.insertNode(wrapper)
|
|
136
|
-
state.content = doc.body.innerHTML
|
|
199
|
+
range.collapse(false)
|
|
200
|
+
range.insertNode(iframe)
|
|
201
|
+
state.content = doc.body.innerHTML
|
|
202
|
+
}
|
|
137
203
|
}
|
|
138
204
|
})
|
|
139
205
|
}
|
|
@@ -25,26 +25,23 @@ export function isStyleActive(style: string, doc: Document) {
|
|
|
25
25
|
|
|
26
26
|
// Define style checkers for different formatting types
|
|
27
27
|
const styleCheckers: { [key: string]: (el: Element) => boolean } = {
|
|
28
|
-
// Text formatting - check for elements
|
|
28
|
+
// Text formatting - check for elements only, not CSS styles
|
|
29
29
|
bold: (el) => {
|
|
30
30
|
const tagName = el.tagName?.toLowerCase()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return styles.fontWeight === 'bold' || styles.fontWeight === '700'
|
|
31
|
+
// Only consider <b> and <strong> tags, not CSS bold styling
|
|
32
|
+
return tagName === 'strong' || tagName === 'b'
|
|
34
33
|
},
|
|
35
34
|
|
|
36
35
|
italic: (el) => {
|
|
37
36
|
const tagName = el.tagName?.toLowerCase()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return styles.fontStyle === 'italic'
|
|
37
|
+
// Only consider <i> and <em> tags, not CSS italic styling
|
|
38
|
+
return tagName === 'em' || tagName === 'i'
|
|
41
39
|
},
|
|
42
40
|
|
|
43
41
|
underline: (el) => {
|
|
44
42
|
const tagName = el.tagName?.toLowerCase()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return styles.textDecoration.includes('underline')
|
|
43
|
+
// Only consider <u> tag, not CSS underline styling
|
|
44
|
+
return tagName === 'u'
|
|
48
45
|
},
|
|
49
46
|
|
|
50
47
|
// Block elements
|
|
@@ -66,6 +63,38 @@ export function isStyleActive(style: string, doc: Document) {
|
|
|
66
63
|
unorderedList: (el) => {
|
|
67
64
|
// Check if we're inside an unordered list
|
|
68
65
|
return !!el.closest('ul')
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Text alignment
|
|
69
|
+
alignLeft: (el) => {
|
|
70
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
71
|
+
return (paragraph as HTMLElement)?.style.textAlign === 'start'
|
|
72
|
+
},
|
|
73
|
+
alignCenter: (el) => {
|
|
74
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
75
|
+
return (paragraph as HTMLElement)?.style.textAlign === 'center'
|
|
76
|
+
},
|
|
77
|
+
alignRight: (el) => {
|
|
78
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
79
|
+
return (paragraph as HTMLElement)?.style.textAlign === 'end'
|
|
80
|
+
},
|
|
81
|
+
alignJustify: (el) => {
|
|
82
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
83
|
+
return (paragraph as HTMLElement)?.style.textAlign === 'justify'
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Text direction
|
|
87
|
+
textDirection: (el) => {
|
|
88
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
89
|
+
return (paragraph as HTMLElement)?.dir === 'rtl'
|
|
90
|
+
},
|
|
91
|
+
ltrDirection: (el) => {
|
|
92
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
93
|
+
return (paragraph as HTMLElement)?.dir === 'ltr'
|
|
94
|
+
},
|
|
95
|
+
rtlDirection: (el) => {
|
|
96
|
+
const paragraph = el.closest('p, h1, h2, h3, h4, h5, h6')
|
|
97
|
+
return (paragraph as HTMLElement)?.dir === 'rtl'
|
|
69
98
|
}
|
|
70
99
|
}
|
|
71
100
|
|
|
@@ -141,7 +170,7 @@ export function restoreSelection(
|
|
|
141
170
|
try {
|
|
142
171
|
range.setStart(info.originalStart, info.originalStartOffset)
|
|
143
172
|
range.setEnd(info.originalEnd, info.originalEndOffset)
|
|
144
|
-
} catch
|
|
173
|
+
} catch {
|
|
145
174
|
if (fallbackNode) {
|
|
146
175
|
range.selectNodeContents(fallbackNode)
|
|
147
176
|
}
|
|
@@ -209,7 +209,7 @@ export function deleteColumn(range: Range) {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
export function alignColumn(range: Range, alignment: '
|
|
212
|
+
export function alignColumn(range: Range, alignment: 'start' | 'center' | 'end' | 'justify') {
|
|
213
213
|
const cell = range.startContainer.parentElement?.closest('td')
|
|
214
214
|
if (!cell) return
|
|
215
215
|
|
package/src/components/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { default as Accordion } from './Accordion.vue'
|
|
|
2
2
|
export { default as AccordionItem } from './AccordionItem.vue'
|
|
3
3
|
export { default as AddressSearch } from './AddressSearch.vue'
|
|
4
4
|
export { default as Alert } from './Alert.vue'
|
|
5
|
+
export * from './analytics'
|
|
5
6
|
export { default as Avatar } from './Avatar.vue'
|
|
6
7
|
export { default as Badge } from './Badge.vue'
|
|
7
8
|
export { default as BglVideo } from './BglVideo.vue'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { inject } from 'vue'
|
|
2
|
+
import { inject, computed, onMounted, ref } from 'vue'
|
|
3
3
|
import { Btn, PageTitle } from '@bagelink/vue'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -25,22 +25,30 @@ const menuState = inject('menuState') as {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Inject sidebar card style state
|
|
28
|
-
const sidebarCardStyle = inject('sidebarCardStyle', { value:
|
|
28
|
+
const sidebarCardStyle = inject('sidebarCardStyle', { value: false })
|
|
29
|
+
|
|
30
|
+
// Computed property to check if sidebar has card style
|
|
31
|
+
const hasSidebarCard = computed(() => {
|
|
32
|
+
// Check if there's an AppSidebar with card class in the DOM
|
|
33
|
+
const sidebar = document.querySelector('.app-sidebar .card')
|
|
34
|
+
return sidebar !== null || sidebarCardStyle?.value
|
|
35
|
+
})
|
|
29
36
|
</script>
|
|
30
37
|
|
|
31
38
|
<template>
|
|
32
39
|
<div class="app-content h-100p flex column" :class="{
|
|
33
|
-
'paddingAppContent':
|
|
40
|
+
'paddingAppContent': hasSidebarCard,
|
|
34
41
|
}">
|
|
35
42
|
<!-- Header -->
|
|
36
|
-
<header class="app-header flex align-items-center space-between py-1 min-h60px w-100p m_flex-wrap" :class="{
|
|
43
|
+
<header class="app-header flex align-items-center space-between py-1 m_pt-025 m_pb-05 min-h60px w-100p m_flex-wrap" :class="{
|
|
37
44
|
'border-bottom': border,
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
'px-1': !hasSidebarCard,
|
|
46
|
+
'm_px-1': hasSidebarCard
|
|
47
|
+
}">
|
|
40
48
|
<!-- Left Side -->
|
|
41
|
-
<div class="flex align-items-center gap-col-075 m_flex-wrap">
|
|
49
|
+
<div class="flex align-items-center gap-col-075 m_flex-wrap m_pe-075">
|
|
42
50
|
<!-- Menu Toggle Button -->
|
|
43
|
-
<Btn v-if="showMenuButton" flat icon="dock_to_right" @click="menuState.toggleMenu" />
|
|
51
|
+
<Btn v-if="showMenuButton" flat icon="dock_to_right" class="menuToggleButton" @click="menuState.toggleMenu" />
|
|
44
52
|
|
|
45
53
|
<!-- Back Button -->
|
|
46
54
|
<Btn v-if="showBackButton" flat icon="arrow_back" :to="backTo" class="back-btn" />
|
|
@@ -60,21 +68,21 @@ const sidebarCardStyle = inject('sidebarCardStyle', { value: true })
|
|
|
60
68
|
</div>
|
|
61
69
|
|
|
62
70
|
<!-- Right Side -->
|
|
63
|
-
<div class="flex align-items-center gap-row-05 m_flex-grow-1">
|
|
71
|
+
<div class="flex align-items-center gap-row-05 m_flex-grow-1 endNavTools">
|
|
64
72
|
<slot name="header-right" />
|
|
65
73
|
</div>
|
|
66
74
|
</header>
|
|
67
75
|
|
|
68
76
|
<!-- Page Content -->
|
|
69
|
-
<main class="pageContent flex-grow overflow
|
|
70
|
-
'px-1': !
|
|
77
|
+
<main class="pageContent flex-grow overflow pt-1 pb-05 w-100p m_p-05 m_scrollbar-gutter-stable-both m_vw100" :class="{
|
|
78
|
+
'px-1': !hasSidebarCard,
|
|
71
79
|
}">
|
|
72
80
|
<slot name="content">
|
|
73
81
|
<!-- Default slot for content without explicit template -->
|
|
74
82
|
<slot />
|
|
75
83
|
</slot>
|
|
76
84
|
</main>
|
|
77
|
-
|
|
85
|
+
</div>
|
|
78
86
|
</template>
|
|
79
87
|
|
|
80
88
|
<style>
|
|
@@ -98,27 +106,19 @@ const sidebarCardStyle = inject('sidebarCardStyle', { value: true })
|
|
|
98
106
|
</style>
|
|
99
107
|
|
|
100
108
|
<style scoped>
|
|
101
|
-
.paddingAppContent {
|
|
102
|
-
padding-inline-start: 0.5rem;
|
|
103
|
-
padding-inline-end: 1rem;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
body:has(.sidebar-collapsed) .paddingAppContent {
|
|
107
|
-
padding-inline-start: 1.5rem;
|
|
108
|
-
}
|
|
109
109
|
.app-content {
|
|
110
|
-
|
|
110
|
+
height: 100vh;
|
|
111
111
|
}
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
.app-header {
|
|
114
|
-
|
|
114
|
+
flex-shrink: 0;
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
main {
|
|
118
|
-
|
|
118
|
+
min-height: 0;
|
|
119
119
|
}
|
|
120
120
|
@media screen and (max-width: 910px) {
|
|
121
|
-
.
|
|
121
|
+
.app-header {
|
|
122
122
|
padding-inline: 0.5rem;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -38,11 +38,11 @@ function closeOnMobile() {
|
|
|
38
38
|
// Computed styles for main content margin
|
|
39
39
|
const mainContentStyles = computed(() => {
|
|
40
40
|
if (isMobile.value) {
|
|
41
|
-
return {
|
|
41
|
+
return { marginInlineStart: '0' }
|
|
42
42
|
}
|
|
43
43
|
const collapsedWidth = props.sidebarCardStyle ? '82px' : '66px'
|
|
44
44
|
return {
|
|
45
|
-
|
|
45
|
+
marginInlineStart: isOpen.value ? props.sidebarWidth : collapsedWidth
|
|
46
46
|
}
|
|
47
47
|
})
|
|
48
48
|
|
|
@@ -56,6 +56,9 @@ provide('menuState', {
|
|
|
56
56
|
sidebarCollapsedWidth: props.sidebarCardStyle ? '82px' : '66px'
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
// Provide sidebar card style based on prop
|
|
60
|
+
provide('sidebarCardStyle', { value: props.sidebarCardStyle })
|
|
61
|
+
|
|
59
62
|
// Initialize
|
|
60
63
|
onMounted(() => {
|
|
61
64
|
checkMobile()
|
|
@@ -101,6 +104,14 @@ onUnmounted(() => {
|
|
|
101
104
|
height: 100vh;
|
|
102
105
|
overflow: hidden;
|
|
103
106
|
}
|
|
107
|
+
.app-layout:has(.cardWrapSide) {
|
|
108
|
+
padding-inline-start: 0.5rem;
|
|
109
|
+
padding-inline-end: 1rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.app-layout:has(.sidebar-collapsed .cardWrapSide) {
|
|
113
|
+
padding-inline-start: 1.5rem;
|
|
114
|
+
}
|
|
104
115
|
|
|
105
116
|
/* Overlay for mobile */
|
|
106
117
|
.overlay {
|
|
@@ -114,11 +125,18 @@ onUnmounted(() => {
|
|
|
114
125
|
.main-content {
|
|
115
126
|
flex: 1;
|
|
116
127
|
overflow: hidden;
|
|
117
|
-
transition: margin-
|
|
128
|
+
transition: margin-inline-start 400ms ease;
|
|
118
129
|
min-height: 100vh
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
.page-content {
|
|
122
133
|
overflow: auto;
|
|
123
134
|
}
|
|
135
|
+
@media screen and (max-width: 910px) {
|
|
136
|
+
.app-layout:has(.cardWrapSide),
|
|
137
|
+
.app-layout:has(.sidebar-collapsed .cardWrapSide) {
|
|
138
|
+
padding-inline-start: 0rem;
|
|
139
|
+
padding-inline-end: 0rem;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
124
142
|
</style>
|
|
@@ -65,7 +65,7 @@ const sidebarStyles = computed(() => {
|
|
|
65
65
|
let width = '280px'
|
|
66
66
|
|
|
67
67
|
if (!menuState.isMobile.value) {
|
|
68
|
-
const collapsedWidth = props.card ? '82px' : '
|
|
68
|
+
const collapsedWidth = props.card ? '82px' : '68px'
|
|
69
69
|
width = menuState.isOpen.value ? menuState.sidebarWidth : collapsedWidth
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -100,7 +100,7 @@ function logout() {
|
|
|
100
100
|
...(props.card && { borderRadius: 'var(--card-border-radius)' }),
|
|
101
101
|
}"
|
|
102
102
|
:class="{
|
|
103
|
-
|
|
103
|
+
'card cardWrapSide': props.card,
|
|
104
104
|
'ps-05': !menuState.isOpen.value,
|
|
105
105
|
'scrollbar-gutter-both': menuState.isOpen.value,
|
|
106
106
|
aside_frame: props.frame,
|
|
@@ -267,5 +267,8 @@ function logout() {
|
|
|
267
267
|
.sidebar-mobile-closed {
|
|
268
268
|
transform: translateX(-100%);
|
|
269
269
|
}
|
|
270
|
+
[dir="rtl"] .sidebar-mobile-closed {
|
|
271
|
+
transform: translateX(100%);
|
|
272
|
+
}
|
|
270
273
|
}
|
|
271
274
|
</style>
|