@wikex/admin-kit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ export const TEXT_FONT_OPTIONS = [
2
+ { css: '', label: 'Mặc định', value: 'default' },
3
+ { css: 'var(--font-manrope), sans-serif', label: 'Manrope', value: 'manrope' },
4
+ { css: 'var(--font-inter), sans-serif', label: 'Inter', value: 'inter' },
5
+ { css: 'var(--font-roboto), sans-serif', label: 'Roboto', value: 'roboto' },
6
+ { css: 'var(--font-montserrat), sans-serif', label: 'Montserrat', value: 'montserrat' },
7
+ { css: 'var(--font-lora), serif', label: 'Lora', value: 'lora' },
8
+ {
9
+ css: 'var(--font-playfair-display), serif',
10
+ label: 'Playfair Display',
11
+ value: 'playfair-display',
12
+ },
13
+ ] as const
14
+
15
+ export const TEXT_LINE_HEIGHT_OPTIONS = [
16
+ { css: '', label: 'Mặc định', value: 'default' },
17
+ { css: '0.9', label: 'Rất chặt · 0.9', value: '0.9' },
18
+ { css: '1', label: 'Chặt · 1.0', value: '1' },
19
+ { css: '1.15', label: 'Gọn · 1.15', value: '1.15' },
20
+ { css: '1.35', label: 'Tiêu chuẩn · 1.35', value: '1.35' },
21
+ { css: '1.5', label: 'Thoáng · 1.5', value: '1.5' },
22
+ { css: '1.7', label: 'Rộng · 1.7', value: '1.7' },
23
+ ] as const
24
+
25
+ export type TextFontFamily = (typeof TEXT_FONT_OPTIONS)[number]['value']
26
+ export type TextLineHeight = (typeof TEXT_LINE_HEIGHT_OPTIONS)[number]['value']
27
+
28
+ export type TextStyleValue = {
29
+ fontFamily?: TextFontFamily
30
+ lineHeight?: TextLineHeight
31
+ /** Percentage of the containing block, written by the canvas resize handles. */
32
+ width?: number
33
+ /** Percentage offset from the containing block's left content edge. */
34
+ widthOffset?: number
35
+ }
36
+
37
+ export type TextStyleMap = Record<string, TextStyleValue>
38
+
39
+ export const EDITABLE_INTERACTIVE_SELECTOR = [
40
+ 'a',
41
+ 'button',
42
+ 'input:not([type="hidden"])',
43
+ 'select',
44
+ 'textarea',
45
+ '[role="button"]',
46
+ '[role="link"]',
47
+ '[tabindex]:not([tabindex="-1"])',
48
+ ].join(',')
49
+
50
+ export const EDITABLE_PREVIEW_SELECTOR = [
51
+ 'h1',
52
+ 'h2',
53
+ 'h3',
54
+ 'h4',
55
+ 'h5',
56
+ 'h6',
57
+ 'p',
58
+ 'li',
59
+ 'span',
60
+ 'strong',
61
+ EDITABLE_INTERACTIVE_SELECTOR,
62
+ ].join(',')
63
+
64
+ export const isSyntheticElementStyleKey = (value: string) => value.startsWith('@element:')
65
+
66
+ export const getEditableElementStyleKey = (element: HTMLElement) => {
67
+ const scopedRoot = element.closest<HTMLElement>('[data-wikex-scope]')
68
+ const root = scopedRoot ?? element.ownerDocument.body
69
+ const scope = scopedRoot?.dataset.wikexScope || 'body'
70
+ const segments: string[] = []
71
+ let current: HTMLElement | null = element
72
+
73
+ while (current && current !== root) {
74
+ const parentElement: HTMLElement | null = current.parentElement
75
+ if (!parentElement) break
76
+
77
+ const tagName = current.tagName.toLowerCase()
78
+ const sameTagSiblings = Array.from(parentElement.children).filter(
79
+ (sibling) => sibling.tagName === current?.tagName,
80
+ )
81
+ const index = sameTagSiblings.indexOf(current) + 1
82
+ segments.push(`${tagName}:nth-of-type(${Math.max(index, 1)})`)
83
+ current = parentElement
84
+ }
85
+
86
+ return `@element:${scope}:${segments.reverse().join('>')}`
87
+ }
88
+
89
+ const fontValues = new Set<string>(TEXT_FONT_OPTIONS.map((option) => option.value))
90
+ const lineHeightValues = new Set<string>(TEXT_LINE_HEIGHT_OPTIONS.map((option) => option.value))
91
+
92
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
93
+ Boolean(value) && typeof value === 'object' && !Array.isArray(value)
94
+
95
+ export const normalizeTextStyleValue = (value: unknown): TextStyleValue => {
96
+ if (!isRecord(value)) return {}
97
+
98
+ const fontFamily =
99
+ typeof value.fontFamily === 'string' && fontValues.has(value.fontFamily)
100
+ ? (value.fontFamily as TextFontFamily)
101
+ : undefined
102
+ const lineHeight =
103
+ typeof value.lineHeight === 'string' && lineHeightValues.has(value.lineHeight)
104
+ ? (value.lineHeight as TextLineHeight)
105
+ : undefined
106
+ const legacyWidth =
107
+ typeof value.width === 'string' && /^\d+(?:\.\d+)?%$/.test(value.width)
108
+ ? Number.parseFloat(value.width)
109
+ : undefined
110
+ const widthValue = typeof value.width === 'number' ? value.width : legacyWidth
111
+ const width =
112
+ typeof widthValue === 'number' && Number.isFinite(widthValue)
113
+ ? Math.min(100, Math.max(5, widthValue))
114
+ : undefined
115
+ const widthOffset =
116
+ typeof value.widthOffset === 'number' && Number.isFinite(value.widthOffset)
117
+ ? Math.min(width ? 100 - width : 95, Math.max(0, value.widthOffset))
118
+ : undefined
119
+
120
+ return { fontFamily, lineHeight, width, widthOffset }
121
+ }
122
+
123
+ export const normalizeTextStyleMap = (value: unknown): TextStyleMap => {
124
+ if (!isRecord(value)) return {}
125
+
126
+ return Object.fromEntries(
127
+ Object.entries(value).map(([fieldPath, fieldStyle]) => [
128
+ fieldPath,
129
+ normalizeTextStyleValue(fieldStyle),
130
+ ]),
131
+ )
132
+ }
133
+
134
+ export const getTextStyleCSS = (
135
+ value: TextStyleValue,
136
+ ): {
137
+ boxSizing?: 'border-box'
138
+ fontFamily?: string
139
+ lineHeight?: string
140
+ marginLeft?: string
141
+ marginRight?: string
142
+ maxWidth?: string
143
+ width?: string
144
+ } => {
145
+ const fontFamily = TEXT_FONT_OPTIONS.find((option) => option.value === value.fontFamily)?.css
146
+ const lineHeight = TEXT_LINE_HEIGHT_OPTIONS.find(
147
+ (option) => option.value === value.lineHeight,
148
+ )?.css
149
+ const width = typeof value.width === 'number' ? `${value.width}%` : undefined
150
+ const widthOffset = typeof value.widthOffset === 'number' ? `${value.widthOffset}%` : undefined
151
+
152
+ return {
153
+ boxSizing: width ? 'border-box' : undefined,
154
+ fontFamily: fontFamily || undefined,
155
+ lineHeight: lineHeight || undefined,
156
+ marginLeft: width ? widthOffset || '0%' : undefined,
157
+ marginRight: width ? 'auto' : undefined,
158
+ maxWidth: width ? 'none' : undefined,
159
+ width,
160
+ }
161
+ }