@flun/html-template 4.0.10

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.
Files changed (59) hide show
  1. package/.env +9 -0
  2. package/LICENSE +15 -0
  3. package/build.js +3 -0
  4. package/compile.js +349 -0
  5. package/copy-files.js +200 -0
  6. package/customize/account.js +726 -0
  7. package/customize/data.json +484 -0
  8. package/customize/functions.js +48 -0
  9. package/customize/hotReloadInjector.js +25 -0
  10. package/customize/routes.js +141 -0
  11. package/customize/users.json +44 -0
  12. package/customize/variables.js +70 -0
  13. package/dev-server.js +344 -0
  14. package/dev.js +4 -0
  15. package/f-CHANGELOG.md +4 -0
  16. package/f-README.md +485 -0
  17. package/index.d.ts +133 -0
  18. package/index.js +4 -0
  19. package/package.json +77 -0
  20. package/restoreDefaults.js +8 -0
  21. package/services/templateService.js +962 -0
  22. package/static/about.css +118 -0
  23. package/static/auth.js +27 -0
  24. package/static/constants.css +138 -0
  25. package/static/img/dark.png +0 -0
  26. package/static/img/favicon.ico +0 -0
  27. package/static/img/light.png +0 -0
  28. package/static/img/top.png +0 -0
  29. package/static/index.css +86 -0
  30. package/static/mouseOrTouch.js +156 -0
  31. package/static/public.css +288 -0
  32. package/static/script.css +318 -0
  33. package/static/script.js +392 -0
  34. package/static/styling.css +874 -0
  35. package/static/styling.js +933 -0
  36. package/static/themeImg.css +10 -0
  37. package/static/themeImg.js +19 -0
  38. package/static/themeModule.js +222 -0
  39. package/static/topImg.css +19 -0
  40. package/static/topImg.js +21 -0
  41. package/static/utils/browser13.js +270 -0
  42. package/static/utils/closebrackets.js +166 -0
  43. package/static/utils/css-lint.js +308 -0
  44. package/static/utils/custom-css-hint.js +876 -0
  45. package/static/utils/foldgutter.js +141 -0
  46. package/static/utils/match-highlighter.js +70 -0
  47. package/templates/about.html +236 -0
  48. package/templates/account/2fa.html +184 -0
  49. package/templates/account/forgot-password.html +226 -0
  50. package/templates/account/login.html +230 -0
  51. package/templates/account/profile.html +977 -0
  52. package/templates/account/register.html +224 -0
  53. package/templates/account/reset-password.html +205 -0
  54. package/templates/account/verify-email.html +163 -0
  55. package/templates/base.html +71 -0
  56. package/templates/footer-content.html +5 -0
  57. package/templates/index.html +140 -0
  58. package/templates/script.html +209 -0
  59. package/templates/test-include.html +11 -0
@@ -0,0 +1,876 @@
1
+ // 根目录/utils/custom-css-hint.js
2
+ (function () {
3
+ 'use strict';
4
+ // ---------- CSS 自动补全数据源 ----------
5
+ const GLOBAL_KEYWORDS = ['inherit', 'initial', 'unset', 'revert', 'revert-layer', 'var()'],
6
+
7
+ // 常用颜色名
8
+ COLOR_KEYWORDS = [
9
+ 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10
+ 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk',
11
+ 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki',
12
+ 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue',
13
+ 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',
14
+ 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod',
15
+ 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
16
+ 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
17
+ 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray',
18
+ 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine',
19
+ 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise',
20
+ 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
21
+ 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
22
+ 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue',
23
+ 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray',
24
+ 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat',
25
+ 'white', 'whitesmoke', 'yellow', 'yellowgreen', 'transparent',
26
+ // 系统颜色
27
+ 'ActiveText', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight',
28
+ 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'
29
+ ],
30
+
31
+ // 常规 CSS 属性名 (约200个)
32
+ CSS_PROPERTIES = [
33
+ 'align-content', 'align-items', 'align-self', 'all', 'animation', 'animation-delay', 'animation-direction',
34
+ 'animation-duration', 'animation-fill-mode', 'animation-iteration-count', 'animation-name', 'animation-play-state',
35
+ 'animation-timing-function', 'backdrop-filter', 'backface-visibility', 'background', 'background-attachment',
36
+ 'background-blend-mode', 'background-clip', 'background-color', 'background-image', 'background-origin',
37
+ 'background-position', 'background-repeat', 'background-size',
38
+ 'border', 'border-bottom', 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', 'border-collapse',
39
+ 'border-color', 'border-image', 'border-image-outset', 'border-image-repeat', 'border-image-slice', 'border-image-source',
40
+ 'border-image-width', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-radius',
41
+ 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius',
42
+ 'border-right', 'border-right-color', 'border-right-style', 'border-right-width', 'border-spacing', 'border-style',
43
+ 'border-top', 'border-top-color', 'border-top-style', 'border-top-width', 'border-width', 'bottom',
44
+ 'box-shadow', 'box-sizing', 'break-after', 'break-before', 'break-inside',
45
+ 'caption-side', 'caret-color', 'clear', 'clip', 'clip-path', 'color', 'column-count', 'column-fill', 'column-gap',
46
+ 'column-rule', 'column-rule-color', 'column-rule-style', 'column-rule-width', 'column-span', 'column-width',
47
+ 'columns', 'content', 'counter-increment', 'counter-reset', 'cursor', 'direction', 'display', 'empty-cells',
48
+ 'filter', 'flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap', 'float', 'font',
49
+ 'font-family', 'font-feature-settings', 'font-variation-settings', 'font-kerning', 'font-size', 'font-size-adjust',
50
+ 'font-stretch', 'font-style', 'font-synthesis', 'font-variant', 'font-variant-alternates', 'font-variant-caps',
51
+ 'font-variant-east-asian', 'font-variant-ligatures', 'font-variant-numeric', 'font-variant-position', 'font-weight',
52
+ 'gap', 'grid', 'grid-area', 'grid-auto-columns', 'grid-auto-flow', 'grid-auto-rows', 'grid-column', 'grid-column-end',
53
+ 'grid-column-gap', 'grid-column-start', 'grid-gap', 'grid-row', 'grid-row-end', 'grid-row-gap', 'grid-row-start',
54
+ 'grid-template', 'grid-template-areas', 'grid-template-columns', 'grid-template-rows',
55
+ 'height', 'hyphens', 'image-rendering', 'isolation', 'justify-content', 'justify-items', 'justify-self',
56
+ 'left', 'letter-spacing', 'line-height', 'list-style', 'list-style-image', 'list-style-position', 'list-style-type',
57
+ 'margin', 'margin-bottom', 'margin-left', 'margin-right', 'margin-top', 'mask', 'mask-clip', 'mask-composite',
58
+ 'mask-image', 'mask-mode', 'mask-origin', 'mask-position', 'mask-repeat', 'mask-size', 'mask-type',
59
+ 'max-height', 'max-width', 'min-height', 'min-width', 'mix-blend-mode',
60
+ 'object-fit', 'object-position', 'opacity', 'order', 'orphans', 'outline', 'outline-color', 'outline-offset',
61
+ 'outline-style', 'outline-width', 'overflow', 'overflow-x', 'overflow-y',
62
+ 'padding', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top', 'page-break-after',
63
+ 'page-break-before', 'page-break-inside', 'perspective', 'perspective-origin', 'pointer-events', 'position',
64
+ 'quotes', 'resize', 'right', 'row-gap', 'scroll-behavior',
65
+ 'tab-size', 'table-layout', 'text-align', 'text-align-last', 'text-decoration', 'text-decoration-color',
66
+ 'text-decoration-line', 'text-decoration-skip', 'text-decoration-skip-ink', 'text-decoration-style', 'text-indent',
67
+ 'text-justify', 'text-overflow', 'text-rendering', 'text-shadow', 'text-transform', 'text-underline-offset',
68
+ 'text-underline-position', 'top', 'transform', 'transform-origin', 'transform-style', 'transition', 'transition-delay',
69
+ 'transition-duration', 'transition-property', 'transition-timing-function',
70
+ 'unicode-bidi', 'user-select', 'vertical-align', 'visibility',
71
+ 'white-space', 'widows', 'width', 'will-change', 'word-break', 'word-spacing', 'word-wrap', 'writing-mode', 'z-index'
72
+ ],
73
+
74
+ // 常规 CSS公用值数组
75
+ BORDER_STYLE_VALUES = ['none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'],
76
+ BORDER_WIDTH_VALUES = ['/* 长度值; 例如: 10px */', 'thin', 'medium', 'thick'],
77
+ LENGTH_AND_PERCENTAGE = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */'],
78
+ MARGIN_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'auto'],
79
+ MIN_WIDTH_HEIGHT_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'min-content', 'max-content', 'fit-content'],
80
+ MAX_WIDTH_HEIGHT_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'min-content', 'max-content', 'fit-content',
81
+ 'none'],
82
+ WIDTH_HEIGHT_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'min-content', 'max-content', 'fit-content',
83
+ 'auto'],
84
+ GRID_TEMPLATE_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'min-content', 'max-content', 'auto',
85
+ 'minmax(/* 2个参数:最小值,最大值;例如: 100px, 1fr */)', 'repeat(/* 2个参数:重复次数,轨道列表;例如: 3, 1fr */)', 'none'],
86
+ GAP_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'normal'],
87
+ OVERFLOW_VALUES = ['visible', 'hidden', 'clip', 'scroll', 'auto'],
88
+ SPACING_VALUES = ['normal', '/* 长度值; 例如: 10px */'],
89
+ PAGE_BREAK_SIDES_VALUES = ['auto', 'always', 'avoid', 'left', 'right', 'recto', 'verso'],
90
+ FILTER_VALUES = ['blur(/* 1个参数:长度值;例如: 5px */)', 'brightness(/* 1个参数:数字或百分比;例如: 0.5 或 50% */)',
91
+ 'contrast(/* 1个参数:数字或百分比;例如: 200% */)', 'url(/* 1个参数:字符串;例如: "image.png" */)',
92
+ 'drop-shadow(/* 2-4个参数:偏移x, 偏移y, 模糊半径(可选), 颜色(可选);例如: 5px 5px 5px black */)',
93
+ 'grayscale(/* 1个参数:数字或百分比;例如: 0.5 */)', 'hue-rotate(/* 1个参数:角度值;例如: 90deg */)',
94
+ 'invert(/* 1个参数:数字或百分比;例如: 100% */)', 'none', 'opacity(/* 1个参数:数字或百分比;例如: 0.5 */)',
95
+ 'saturate(/* 1个参数:数字或百分比;例如: 200% */)', 'sepia(/* 1个参数:数字或百分比;例如: 0.5 */)'],
96
+
97
+ TIME_VALUES = ['/* 时间值; 例如: 2s */'],
98
+ TIMING_FUNCTION_VALUES = ['ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out', 'step-start', 'step-end',
99
+ 'steps(/* 2个参数:步数,方向(可选);例如: 5, start */)', 'cubic-bezier(/* 4个0~1参数:x1,y1,x2,y2;例如: 0.25, 0.1, 0.25, 1 */)'],
100
+ SHADOW_VALUES = ['none', '/* 长度值; 例如: 10px */', 'inset', ...COLOR_KEYWORDS],
101
+ BORDER_SHORTHAND_VALUES = ['none', '/* 长度值; 例如: 10px */', 'inset', 'outset', 'thin', 'medium', 'thick', 'solid',
102
+ 'dashed', 'dotted', 'double', 'groove', 'ridge', ...COLOR_KEYWORDS],
103
+ FONT_FEATURE_VALUES = ['normal', '/* OpenType 特性值; 例如: "kern" 1 */'],
104
+ FONT_VARIATION_VALUES = ['normal', '/* 字符串+数字; 例如: "wght" 700 */'],
105
+
106
+ // 常规 CSS 属性值映射
107
+ PROPERTY_VALUES = {
108
+ // 颜色类
109
+ 'color': COLOR_KEYWORDS, 'background-color': COLOR_KEYWORDS, 'border-color': COLOR_KEYWORDS, 'outline-color': COLOR_KEYWORDS,
110
+ 'text-decoration-color': COLOR_KEYWORDS, 'column-rule-color': COLOR_KEYWORDS, 'border-top-color': COLOR_KEYWORDS,
111
+ 'border-right-color': COLOR_KEYWORDS, 'border-bottom-color': COLOR_KEYWORDS, 'border-left-color': COLOR_KEYWORDS,
112
+
113
+ // 边框样式
114
+ 'border-style': BORDER_STYLE_VALUES, 'border-top-style': BORDER_STYLE_VALUES, 'border-right-style': BORDER_STYLE_VALUES,
115
+ 'border-bottom-style': BORDER_STYLE_VALUES, 'border-left-style': BORDER_STYLE_VALUES, 'outline-style': BORDER_STYLE_VALUES,
116
+
117
+ // 边框宽度
118
+ 'border-width': BORDER_WIDTH_VALUES, 'border-top-width': BORDER_WIDTH_VALUES, 'border-right-width': BORDER_WIDTH_VALUES,
119
+ 'border-bottom-width': BORDER_WIDTH_VALUES, 'border-left-width': BORDER_WIDTH_VALUES, 'outline-width': BORDER_WIDTH_VALUES,
120
+
121
+ // 外边距
122
+ 'margin': MARGIN_VALUES, 'margin-top': MARGIN_VALUES, 'margin-right': MARGIN_VALUES,
123
+ 'margin-bottom': MARGIN_VALUES, 'margin-left': MARGIN_VALUES,
124
+
125
+ // 内边距和边框圆角
126
+ 'padding': LENGTH_AND_PERCENTAGE, 'padding-top': LENGTH_AND_PERCENTAGE, 'padding-right': LENGTH_AND_PERCENTAGE,
127
+ 'padding-bottom': LENGTH_AND_PERCENTAGE, 'padding-left': LENGTH_AND_PERCENTAGE, 'border-radius': LENGTH_AND_PERCENTAGE,
128
+ 'border-top-left-radius': LENGTH_AND_PERCENTAGE, 'border-top-right-radius': LENGTH_AND_PERCENTAGE,
129
+ 'border-bottom-right-radius': LENGTH_AND_PERCENTAGE, 'border-bottom-left-radius': LENGTH_AND_PERCENTAGE,
130
+
131
+ // 尺寸
132
+ 'width': WIDTH_HEIGHT_VALUES, 'height': WIDTH_HEIGHT_VALUES,
133
+ 'min-width': MIN_WIDTH_HEIGHT_VALUES, 'min-height': MIN_WIDTH_HEIGHT_VALUES,
134
+ 'max-width': MAX_WIDTH_HEIGHT_VALUES, 'max-height': MAX_WIDTH_HEIGHT_VALUES,
135
+
136
+ // 显示与定位
137
+ 'display': ['block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'inline-grid', 'flow-root', 'none',
138
+ 'contents', 'table', 'table-row', 'table-cell', 'list-item'],
139
+ 'position': ['static', 'relative', 'absolute', 'fixed', 'sticky'],
140
+ 'float': ['left', 'right', 'inline-start', 'inline-end', 'none'],
141
+ 'clear': ['left', 'right', 'inline-start', 'inline-end', 'none', 'both'],
142
+ 'visibility': ['visible', 'hidden', 'collapse'],
143
+
144
+ // 溢出
145
+ 'overflow': OVERFLOW_VALUES, 'overflow-x': OVERFLOW_VALUES, 'overflow-y': OVERFLOW_VALUES,
146
+
147
+ // 弹性布局
148
+ 'flex-direction': ['row', 'row-reverse', 'column', 'column-reverse'],
149
+ 'flex-wrap': ['nowrap', 'wrap', 'wrap-reverse'],
150
+ 'flex-basis': ['auto', 'content', 'min-content', 'max-content', 'fit-content', '/* 长度值; 例如: 10px */',
151
+ '/* 百分比值; 例如: 50% */',],
152
+ 'justify-content': ['center', 'space-between', 'space-around', 'space-evenly', 'start', 'end', 'left', 'right',
153
+ 'flex-start', 'flex-end'],
154
+ 'align-content': ['center', 'space-between', 'space-around', 'space-evenly', 'stretch', 'flex-start', 'flex-end',],
155
+ 'align-items': ['center', 'baseline', 'stretch', 'start', 'end', 'flex-start', 'flex-end'],
156
+ 'align-self': ['center', 'baseline', 'stretch', 'start', 'end', 'flex-start', 'flex-end', 'auto'],
157
+
158
+
159
+ // 网格布局
160
+ 'grid-template-columns': GRID_TEMPLATE_VALUES, 'grid-template-rows': GRID_TEMPLATE_VALUES,
161
+ 'gap': GAP_VALUES, 'row-gap': GAP_VALUES, 'column-gap': GAP_VALUES,
162
+
163
+ // 文本相关
164
+ 'text-align': ['left', 'right', 'center', 'justify', 'start', 'end'],
165
+ 'vertical-align': ['baseline', 'sub', 'super', 'text-top', 'text-bottom', 'middle', 'top', 'bottom',
166
+ '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */'],
167
+ 'white-space': ['normal', 'nowrap', 'pre', 'pre-wrap', 'pre-line', 'break-spaces'],
168
+ 'word-break': ['normal', 'break-all', 'keep-all', 'break-word'],
169
+ 'font-style': ['normal', 'italic', 'oblique'],
170
+ 'font-weight': ['normal', 'bold', 'lighter', 'bolder', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
171
+ 'font-size': ['small', 'x-small', 'xx-small', 'medium', 'large', 'x-large', 'xx-large', 'smaller', 'larger',
172
+ '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */'],
173
+ 'font-family': ['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui', 'Arial', 'Helvetica',
174
+ 'Times New Roman', 'Courier New'],
175
+ 'font-feature-settings': FONT_FEATURE_VALUES,
176
+ 'font-variation-settings': FONT_VARIATION_VALUES,
177
+ 'text-transform': ['none', 'capitalize', 'uppercase', 'lowercase', 'full-width'],
178
+ 'text-decoration-line': ['none', 'underline', 'overline', 'line-through', 'blink'],
179
+ 'text-decoration-style': ['solid', 'double', 'dotted', 'dashed', 'wavy'],
180
+ 'text-decoration-thickness': ['auto', 'from-font', '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */'],
181
+ 'line-height': ['normal', '/* 数字值; 例如: 1.5 */', '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */'],
182
+ 'letter-spacing': SPACING_VALUES, 'word-spacing': SPACING_VALUES,
183
+ 'cursor': ['auto', 'default', 'none', 'context-menu', 'help', 'pointer', 'progress', 'wait', 'cell', 'crosshair', 'text',
184
+ 'vertical-text', 'alias', 'copy', 'move', 'no-drop', 'not-allowed', 'grab', 'grabbing', 'all-scroll', 'col-resize',
185
+ 'row-resize', 'n-resize', 'e-resize', 's-resize', 'w-resize', 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize',
186
+ 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out'],
187
+
188
+ // 列表
189
+ 'list-style-type': ['disc', 'circle', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman',
190
+ 'lower-greek', 'lower-alpha', 'upper-alpha', 'none'],
191
+
192
+ // 盒模型
193
+ 'box-sizing': ['content-box', 'border-box'],
194
+ 'object-fit': ['fill', 'contain', 'cover', 'none', 'scale-down'],
195
+
196
+ // 背景
197
+ 'background-size': ['auto', 'cover', 'contain'],
198
+ 'background-attachment': ['scroll', 'fixed', 'local'],
199
+ 'background-origin': ['border-box', 'padding-box', 'content-box'],
200
+ 'background-clip': ['border-box', 'padding-box', 'content-box', 'text'],
201
+ 'background-repeat': ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space', 'round'],
202
+
203
+
204
+ // 表格
205
+ 'border-collapse': ['collapse', 'separate'],
206
+ 'caption-side': ['top', 'bottom'],
207
+ 'empty-cells': ['show', 'hide'],
208
+ 'table-layout': ['auto', 'fixed'],
209
+
210
+ // 书写模式
211
+ 'direction': ['ltr', 'rtl'],
212
+ 'unicode-bidi': ['normal', 'embed', 'isolate', 'bidi-override', 'isolate-override', 'plaintext'],
213
+ 'writing-mode': ['horizontal-tb', 'vertical-rl', 'vertical-lr', 'sideways-rl', 'sideways-lr'],
214
+
215
+ // 分页
216
+ 'page-break-before': PAGE_BREAK_SIDES_VALUES, 'page-break-after': PAGE_BREAK_SIDES_VALUES,
217
+ 'page-break-inside': ['auto', 'avoid'],
218
+
219
+ // 效果
220
+ 'z-index': ['auto', '/* 整数值; 例如: 5 */'],
221
+ 'opacity': ['/* 数字值; 例如: 0.5 */'],
222
+ 'transform': ['none', 'matrix(/* 6个参数: a, b, c, d, tx, ty;例如: 1, 0, 0, 1, 0, 0 */)',
223
+ 'matrix3d(/* 16个参数: 矩阵值;例如: 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 */)',
224
+ 'translate(/* 1-2个参数: 长度值X, 长度值Y(可选);例如: 10px, 20px */)',
225
+ 'translate3d(/* 3个参数: tx, ty, tz;例如: 10px, 20px, 30px */)', 'translateX(/* 1个参数: 长度值;例如: 10px */)',
226
+ 'translateY(/* 1个参数: 长度值;例如: 10px */)', 'translateZ(/* 1个参数: 长度值;例如: 10px */)',
227
+ 'scale(/* 1-2个参数: 缩放倍数X, 缩放倍数Y(可选);例如: 2 */)', 'scale3d(/* 3个参数: sx, sy, sz;例如: 2, 0.5, 1 */)',
228
+ 'scaleX(/* 1个参数: 缩放倍数;例如: 2 */)', 'scaleY(/* 1个参数: 缩放倍数;例如: 2 */)',
229
+ 'scaleZ(/* 1个参数: 缩放倍数;例如: 2 */)', 'rotate(/* 1个参数: 角度值;例如: 45deg */)',
230
+ 'rotate3d(/* 4个参数: x, y, z, angle;例如: 1, 1, 0, 45deg */)', 'rotateX(/* 1个参数: 角度值;例如: 45deg */)',
231
+ 'rotateY(/* 1个参数: 角度值;例如: 45deg */)', 'rotateZ(/* 1个参数: 角度值;例如: 45deg */)',
232
+ 'skew(/* 1-2个参数: 角度X, 角度Y(可选);例如: 10deg, 20deg */)', 'skewX(/* 1个参数: 角度值;例如: 10deg */)',
233
+ 'skewY(/* 1个参数: 角度值;例如: 10deg */)', 'perspective(/* 1个参数: 长度值;例如: 100px */)'],
234
+ 'filter': FILTER_VALUES, 'backdrop-filter': FILTER_VALUES,
235
+ 'clip-path': ['none', 'inset(/* 1-4个参数: 长度值(上右下左);例如: 10px 20px */)',
236
+ 'circle(/* 1-2个参数: 半径值, at 圆心位置(可选);例如: 50% */)',
237
+ 'ellipse(/* 1-3个参数: rx ry, at 圆心位置(可选);例如: 50% 30% */)',
238
+ 'polygon(/* 2个以上参数: 填充规则(可选) + 坐标对列表;例如: 0% 0%, 100% 0%, 100% 100% */)',
239
+ 'path(/* 1个参数: 路径字符串;例如: "M 0 0 L 100 100" */)', 'url(/* 1个参数: 字符串;例如: "clip.svg#id" */)'],
240
+ 'mask-mode': ['alpha', 'luminance', 'match-source'],
241
+ 'mask-type': ['alpha', 'luminance'],
242
+ 'isolation': ['auto', 'isolate'],
243
+ 'mix-blend-mode': ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'color',
244
+ 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'luminosity', 'plus-darker', 'plus-lighter'],
245
+ 'background-blend-mode': ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn',
246
+ 'color', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'luminosity'],
247
+
248
+ // 过渡与动画
249
+ 'transition-property': ['all', 'none', ...CSS_PROPERTIES],
250
+ 'animation-name': ['none', ...CSS_PROPERTIES],
251
+ 'transition-timing-function': TIMING_FUNCTION_VALUES, 'animation-timing-function': TIMING_FUNCTION_VALUES,
252
+ 'transition-duration': TIME_VALUES, 'transition-delay': TIME_VALUES,
253
+ 'animation-duration': TIME_VALUES, 'animation-delay': TIME_VALUES,
254
+ 'animation-iteration-count': ['infinite', '/* 数字值; 例如: 1.0 */'],
255
+ 'animation-direction': ['normal', 'reverse', 'alternate', 'alternate-reverse'],
256
+ 'animation-fill-mode': ['none', 'forwards', 'backwards', 'both'],
257
+ 'animation-play-state': ['running', 'paused'],
258
+
259
+ // 缩写属性
260
+ 'border': BORDER_SHORTHAND_VALUES, 'border-top': BORDER_SHORTHAND_VALUES, 'border-right': BORDER_SHORTHAND_VALUES,
261
+ 'border-bottom': BORDER_SHORTHAND_VALUES, 'border-left': BORDER_SHORTHAND_VALUES, 'outline': BORDER_SHORTHAND_VALUES,
262
+
263
+ 'background': ['none', 'transparent',
264
+ 'url(/* 1个参数: 字符串;例如: "image.png" */)',
265
+ 'linear-gradient(/* 2个以上参数: 方向(可选) + 颜色停止列表;例如: to right, red, blue */)',
266
+ 'radial-gradient(/* 2个以上参数: 形状/位置(可选) + 颜色停止列表;例如: circle, red, blue */)',
267
+ 'repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'scroll', 'fixed', 'local', 'cover', 'contain', 'auto',
268
+ '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'left', 'center', 'right', 'top', 'bottom', ...COLOR_KEYWORDS],
269
+ 'font': ['normal', 'italic', 'oblique', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700',
270
+ '800', '900', 'small-caps', '/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', 'xx-small', 'x-small', 'small',
271
+ 'medium', 'large', 'x-large', 'xx-large', 'smaller', 'larger', 'serif', 'sans-serif', 'monospace', 'cursive',
272
+ 'fantasy', 'system-ui'],
273
+ 'list-style': ['none', 'disc', 'circle', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman',
274
+ 'lower-greek', 'lower-alpha', 'upper-alpha', 'armenian', 'georgian', 'inside', 'outside',
275
+ 'url(/* 1个参数: 图像路径,用于自定义列表标记图像;例如: "marker.png" */)'],
276
+ 'text-decoration': ['none', 'underline', 'overline', 'line-through', 'blink', 'solid', 'double', 'dotted', 'dashed',
277
+ 'wavy', ...COLOR_KEYWORDS],
278
+ 'transition': ['none', 'all', '/* 属性名; 例如: color */', '/* 时间值; 例如: 2s */', 'ease', 'linear', 'ease-in',
279
+ 'ease-out', 'ease-in-out', 'step-start', 'step-end'],
280
+ 'animation': ['none', '/* 动画名称; 例如: myanimation */', '/* 时间值; 例如: 2s */', 'ease', 'linear', 'ease-in',
281
+ 'ease-out', 'ease-in-out', 'step-start', 'step-end', 'infinite', 'normal', 'reverse', 'alternate-reverse',
282
+ 'alternate', 'forwards', 'backwards', 'both', 'running', 'paused'],
283
+ 'box-shadow': SHADOW_VALUES, 'text-shadow': SHADOW_VALUES
284
+ },
285
+
286
+ // 伪类 (以 : 开头)
287
+ PSEUDO_CLASSES = [':active', ':any-link', ':blank', ':checked', ':current', ':default', ':defined',
288
+ ':dir(/* 1个参数: 文字方向;例如: ltr或rtl */)', ':disabled', ':empty', ':enabled',
289
+ ':first', ':first-child', ':first-of-type', ':fullscreen', ':future', ':focus', ':focus-visible', ':focus-within',
290
+ ':has(/* 1个参数: 选择器;例如: img */)', ':host', ':host(/* 1个参数: 选择器;例如: div */)',
291
+ ':host-context(/* 1个参数: 选择器;例如: .theme */)', ':hover', ':indeterminate', ':in-range', ':invalid',
292
+ ':is(/* 1个选择器列表参数;例如: h1, h2, h3 */)', ':lang(/* 1个参数: 语言代码;例如: en */)',
293
+ ':last-child', ':last-of-type', ':left', ':link', ':local-link', ':not(/* 1个参数: 选择器;例如: .hidden */)',
294
+ ':nth-child(/* 1个匹配位置参数: an+b 表达式或关键字;例如: 2n+1 */)', ':nth-col(/* 1个匹配位置参数: an+b 表达式;例如: 2n */)',
295
+ ':nth-last-child(/* 1个匹配位置参数: an+b 表达式;例如: 2n+1 */)', ':nth-of-type(/* 1个匹配位置参数: an+b 表达式;例如: 2n+1 */)',
296
+ ':nth-last-of-type(/* 1个匹配位置参数: an+b 表达式;例如: 2n+1 */)', ':only-child', ':only-of-type', ':optional',
297
+ ':out-of-range', ':past', ':paused', ':picture-in-picture', ':placeholder-shown', ':playing', ':read-only',
298
+ ':read-write', ':required', ':right', ':root', ':scope', ':target', ':user-invalid', ':valid', ':visited',
299
+ ':where(/* 1个选择器列表参数;例如: h1, h2 */)'
300
+ ],
301
+ // @page 专用伪类
302
+ PAGE_PSEUDO_CLASSES = [':first', ':left', ':right', ':blank'],
303
+
304
+ // 伪元素 (以 :: 开头)
305
+ PSEUDO_ELEMENTS = [
306
+ '::after', '::backdrop', '::before', '::cue', '::cue-region', '::first-letter', '::first-line', '::grammar-error',
307
+ '::marker', '::part(/* 1个参数: 部件名;例如: button */)', '::placeholder', '::selection', '::spelling-error',
308
+ '::slotted(/* 1个参数: 选择器;例如: span */)'
309
+ ],
310
+
311
+ // HTML 元素名
312
+ HTML_ELEMENTS = [
313
+ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button',
314
+ 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog',
315
+ 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form',
316
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html',
317
+ 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'meta', 'meter',
318
+ 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby',
319
+ 's', 'samp', 'script', 'section', 'select', 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup',
320
+ 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul',
321
+ 'var', 'video', 'wbr'
322
+ ],
323
+
324
+ // @规则名
325
+ AT_RULES = [
326
+ '@charset', '@color-profile', '@container', '@counter-style', '@document', '@font-face', '@font-feature-values',
327
+ '@import', '@keyframes', '@layer', '@media', '@namespace', '@page', '@property', '@scroll-timeline',
328
+ '@supports', '@viewport', '@scope', '@starting-style', '@view-transition', '@font-palette-values'
329
+ ],
330
+
331
+ // @media媒体特性名
332
+ MEDIA_FEATURES = [
333
+ 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height',
334
+ 'aspect-ratio', 'min-aspect-ratio', 'max-aspect-ratio', 'orientation', 'color', 'color-index', 'monochrome',
335
+ 'resolution', 'min-resolution', 'max-resolution', 'scan', 'grid', 'update', 'overflow-block', 'overflow-inline',
336
+ 'hover', 'any-hover', 'pointer', 'any-pointer', 'prefers-color-scheme', 'prefers-reduced-motion', 'prefers-contrast',
337
+ 'dynamic-range', 'video-dynamic-range', 'scripting', 'display-mode', 'forced-colors', 'inverted-colors', 'prefers-reduced-data'
338
+ ],
339
+
340
+ // @media 媒体类型
341
+ MEDIA_TYPES = ['all', 'print', 'screen', 'speech', 'tv', 'projection', 'handheld', 'braille', 'embossed', 'tty'],
342
+ // @规则运算符
343
+ MEDIA_OPERATORS = ['only', 'not', 'and'],
344
+ SUPPORTS_OPERATORS = ['not', 'and', 'or'],
345
+
346
+
347
+ // @media媒体通用值
348
+ MEDIA_GENERIC_VALUES = ['/* 长度值; 例如: 10px */', '/* 百分比值; 例如: 50% */', '/* 分辨率值; 例如: 300dpi */',
349
+ '/* 整数值; 例如: 5 */', '/* 数字值; 例如: 0.5 */', 'portrait', 'landscape', 'progressive', 'interlace', 'infinite'],
350
+
351
+ // ----- 媒体特性共用值 -----
352
+ INTEGER_VALUES = ['/* 整数值; 例如: 5 */'],
353
+ HOVER_VALUES = ['none', 'hover'],
354
+ POINTER_VALUES = ['none', 'coarse', 'fine'],
355
+ DYNAMIC_RANGE_VALUES = ['standard', 'high'],
356
+
357
+ // @media媒体特性值映射
358
+ MEDIA_FEATURE_VALUES = {
359
+ 'orientation': ['portrait', 'landscape'],
360
+ 'scan': ['progressive', 'interlace'],
361
+ 'grid': ['0', '1'],
362
+ 'color': INTEGER_VALUES, 'color-index': INTEGER_VALUES, 'monochrome': INTEGER_VALUES,
363
+ 'resolution': ['/* 分辨率值; 例如: 300dpi */', 'infinite'],
364
+ 'update': ['none', 'slow', 'normal'],
365
+ 'overflow-inline': ['none', 'scroll'],
366
+ 'overflow-block': ['none', 'scroll', 'optional-paged'],
367
+ 'hover': HOVER_VALUES, 'any-hover': HOVER_VALUES,
368
+ 'pointer': POINTER_VALUES, 'any-pointer': POINTER_VALUES,
369
+ 'prefers-color-scheme': ['light', 'dark'],
370
+ 'prefers-reduced-motion': ['no-preference', 'reduce'],
371
+ 'prefers-contrast': ['no-preference', 'high', 'low', 'forced'],
372
+ 'dynamic-range': DYNAMIC_RANGE_VALUES, 'video-dynamic-range': DYNAMIC_RANGE_VALUES,
373
+ 'scripting': ['none', 'initial-only', 'enabled'],
374
+ 'display-mode': ['fullscreen', 'standalone', 'minimal-ui', 'browser'],
375
+ 'forced-colors': ['none', 'active'],
376
+ 'inverted-colors': ['none', 'inverted'],
377
+ 'prefers-reduced-data': ['no-preference', 'reduce']
378
+ },
379
+
380
+ // @规则块内专有属性名
381
+ AT_RULE_BLOCK_DESCRIPTORS = {
382
+ 'font-face': ['font-family', 'src', 'font-weight', 'font-style', 'font-stretch', 'unicode-range', 'font-variant',
383
+ 'font-feature-settings', 'font-variation-settings'],
384
+ 'page': ['size', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'marks', 'bleed'],
385
+ 'counter-style': ['system', 'symbols', 'additive-symbols', 'negative', 'prefix', 'suffix', 'range', 'pad',
386
+ 'speak-as', 'fallback'],
387
+ 'property': ['syntax', 'inherits', 'initial-value'],
388
+ 'font-palette-values': ['font-family', 'base-palette', 'override-colors']
389
+ },
390
+
391
+ // @规则括号内专有特性
392
+ AT_RULE_PARENS_DESCRIPTORS = {
393
+ 'media': MEDIA_FEATURES, 'container': MEDIA_FEATURES, 'scope': MEDIA_FEATURES, 'supports': CSS_PROPERTIES
394
+ },
395
+
396
+ // @规则专有属性值映射
397
+ AT_RULE_DESCRIPTOR_VALUES = {
398
+ // @font-face 描述符值
399
+ 'src': ['url(/* 1个参数: 字符串;例如: "font.woff2" */)', 'local(/* 1个参数: 字体名称;例如: "Arial" */)'],
400
+ 'font-stretch': ['normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded',
401
+ 'expanded', 'extra-expanded', 'ultra-expanded'],
402
+ 'font-variant': ['normal', 'small-caps'],
403
+ 'font-feature-settings': FONT_FEATURE_VALUES,
404
+ 'font-variation-settings': FONT_VARIATION_VALUES,
405
+
406
+ // @counter-style 描述符值
407
+ 'system': ['cyclic', 'numeric', 'alphabetic', 'symbolic', 'additive', 'fixed', 'extends'],
408
+ 'negative': ['/* 字符串序列; 例如: "-" ")" */'],
409
+ 'prefix': ['/* 前缀字符串(一个或多个),在计数器符号前添加;例如: "第" 或 "(" */'],
410
+ 'suffix': ['/* 后缀字符串(一个或多个),在计数器符号后添加;例如: "、" 或 ". " 或 ")" */'],
411
+ 'range': ['auto', '/* 整数或 infinite 范围; 例如: [ 1 5 ] */'],
412
+ 'pad': ['/* 整数+字符串; 例如: 5 "★" */'],
413
+ 'speak-as': ['auto', 'bullets', 'numbers', 'words', 'spell-out', '/* 计数器样式名; 例如: mycounter */'],
414
+ 'fallback': ['/* 计数器样式名; 例如: mycounter */'],
415
+
416
+ // @property 描述符值
417
+ 'syntax': [`'<length>'`, `'<number>'`, `'<percentage>'`, `'<color>'`, `'®'`, `'<integer>'`, `'<angle>'`,
418
+ `'<time>'`, `'<resolution>'`, `'<transform-function>'`, `'<custom-ident>'`, `'<string>'`],
419
+ 'inherits': ['true', 'false'],
420
+ 'initial-value': ['/* 任意合法初始值; 例如: 10px */'],
421
+
422
+ // @page 描述符值
423
+ 'size': ['auto', 'landscape', 'portrait', '/* 长度值(1-2个); 例如: 10cm 20cm */', 'A5', 'A4', 'A3', 'B5', 'B4', 'JIS-B5', 'JIS-B4', 'letter',
424
+ 'legal', 'ledger'],
425
+ 'marks': ['none', 'crop', 'cross', 'crop cross'],
426
+ 'bleed': ['auto', '/* 长度值; 例如: 10px */'],
427
+
428
+ // @font-palette-values 描述符值
429
+ 'base-palette': ['/* 整数值; 例如: 3 */', 'light', 'dark'],
430
+ 'override-colors': ['/* 整数+颜色; 例如: 3 #ff0000 */']
431
+ },
432
+
433
+ // 关键帧选择器
434
+ KEYFRAME_SELECTORS = (() => {
435
+ const selectors = ['from', 'to'];
436
+ for (let i = 0; i <= 100; i += 10) selectors.push(i + '%');
437
+ return selectors;
438
+ })(),
439
+
440
+ // ---------- 集合定义 ----------
441
+ allAtRules = new Set(['media', 'supports', 'font-face', 'keyframes', 'page', 'counter-style', 'property', 'container',
442
+ 'scope', 'starting-style', 'view-transition', 'font-palette-values']),
443
+ descriptorAtRules = new Set(['font-face', 'counter-style', 'property', 'page', 'font-palette-values']),
444
+ parensAtRules = new Set(['media', 'supports', 'container', 'scope']),
445
+ whitespace = /\s/, commentRegex = /\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, stringRegex = /(["'])(?:\\.|(?!\1)[^\\])*\1/g,
446
+ PU = /[-\w\p{L}\p{N}]/u;
447
+
448
+ // ---------- 工具函数 ----------
449
+ // 移除CSS中的注释和字符串
450
+ function stripCommentsAndStrings(cssText) {
451
+ return cssText.replace(commentRegex, '').replace(stringRegex, '');
452
+ }
453
+ // 判断字符串是否包含指定模式
454
+ function strIncludesAny(str, substrings) {
455
+ if (str == null) return false;
456
+ return substrings.some(sub => str.includes(sub));
457
+ }
458
+ // 判断字符是否为单词字符(用于值补全中的边界识别)
459
+ function isWordChar(c) {
460
+ return c && /[^\s;:(){}]/.test(c);
461
+ }
462
+
463
+ // 判断从指定位置开始是否为逻辑运算符
464
+ function isLogicOpStart(line, pos) {
465
+ return SUPPORTS_OPERATORS.some(op => line.slice(pos, pos + op.length).toLowerCase() === op);
466
+ }
467
+
468
+ // 在字符串中向前或向后查找单词边界
469
+ function findWordBoundary(line, lineLen, start, direction = 'forward', charTest = PU) {
470
+ let pos = start;
471
+ if (direction === 'forward') while (pos < lineLen && charTest.test(line[pos])) pos++;
472
+ else while (pos >= 0 && charTest.test(line[pos])) pos--;
473
+ return pos;
474
+ }
475
+
476
+ // 获取属性值候选列表
477
+ function getValueCandidates(insideParensWithAtRule, atRuleType, featureName) {
478
+ if (insideParensWithAtRule && atRuleType === 'media') return MEDIA_FEATURE_VALUES[featureName] || MEDIA_GENERIC_VALUES;
479
+ if (descriptorAtRules.has(atRuleType)) return AT_RULE_DESCRIPTOR_VALUES[featureName] || PROPERTY_VALUES[featureName] || [];
480
+ return PROPERTY_VALUES[featureName] || [];
481
+ }
482
+
483
+ // 获取非值候选项
484
+ function getNonValueCandidates(atRuleType, insideBlock, insideParensWithAtRule, atMatch) {
485
+ if (insideParensWithAtRule) {
486
+ if (AT_RULE_PARENS_DESCRIPTORS[atRuleType]) return AT_RULE_PARENS_DESCRIPTORS[atRuleType];
487
+ if (atMatch) return atMatch[1].toLowerCase() === 'supports' ? CSS_PROPERTIES : MEDIA_FEATURES;
488
+ return [];
489
+ }
490
+ if (insideBlock) {
491
+ if (atRuleType === 'keyframes') return KEYFRAME_SELECTORS;
492
+ if (descriptorAtRules.has(atRuleType)) return AT_RULE_BLOCK_DESCRIPTORS[atRuleType] || [];
493
+ return HTML_ELEMENTS;
494
+ }
495
+ return HTML_ELEMENTS;
496
+ }
497
+
498
+ // 获取当前光标所在位置的上下文信息
499
+ function getContextInfo(type, sliceLine, state, posCh, atMatch) {
500
+ const colonIndex = sliceLine.lastIndexOf(':'), afterColon = sliceLine.slice(colonIndex + 1);
501
+ let ctx = state?.context, atRuleType = null, insideBlock = false, insideParensWithAtRule = false, inValue = false;
502
+
503
+ // 追踪上下文栈
504
+ while (ctx) {
505
+ const ctxType = ctx.type, ctxPrev = ctx.prev;
506
+ if (allAtRules.has(ctxType)) atRuleType = ctxType;
507
+ if (ctxType === 'block') insideBlock = true;
508
+ if (ctxType === 'parens' && parensAtRules.has(ctxPrev?.type)) insideParensWithAtRule = true;
509
+ ctx = ctxPrev;
510
+ }
511
+
512
+ if (!insideParensWithAtRule && atMatch) insideParensWithAtRule = true;
513
+ if (!atRuleType && atMatch) atRuleType = atMatch[1].toLowerCase();
514
+
515
+ if (strIncludesAny(type, ['def', 'variable-2'])) inValue = true;
516
+ else if (!insideBlock && !insideParensWithAtRule) inValue = false;
517
+ else if (colonIndex === -1) inValue = false;
518
+ else if (insideParensWithAtRule && afterColon.includes(')')) inValue = false;
519
+ else if (strIncludesAny(afterColon, [';', '}'])) inValue = false;
520
+ else inValue = posCh > colonIndex;
521
+
522
+ return { inValue, atRuleType, insideBlock, insideParensWithAtRule };
523
+ }
524
+
525
+ // 根据输入前缀过滤候选项,并转换为 CodeMirror 补全项格式
526
+ function filterHints(prefix, candidates, transform = item => item) {
527
+ return candidates.filter(item => {
528
+ const matchStr = transform(item).toLowerCase();
529
+ return !prefix || matchStr.startsWith(prefix.toLowerCase());
530
+ }).map(text => ({ text, displayText: text }));
531
+ }
532
+
533
+ // 获取伪类/伪元素上下文信息
534
+ function getPseudoContext(line, lineLen, posCh) {
535
+ // 向左找到最后一个冒号的位置
536
+ let colonPos = -1;
537
+ for (let i = posCh - 1; i >= 0; i--) {
538
+ if (line[i] === ':') {
539
+ colonPos = i;
540
+ break;
541
+ }
542
+ }
543
+ if (colonPos === -1) return { pType: 'none' };
544
+
545
+ // 从 colonPos 向左找到连续冒号的起始
546
+ let pStart = colonPos;
547
+ while (pStart > 0 && line[pStart - 1] === ':') pStart--;
548
+
549
+ // 判断类型:连续冒号个数 >=2 为伪元素,否则为伪类
550
+ const colonCount = colonPos - pStart + 1, pType = colonCount >= 2 ? 'element' : 'class',
551
+ pPrefix = line.slice(colonPos + 1, posCh);
552
+
553
+ // 括号闭合检查:光标位于已闭合的伪类括号后则不补全
554
+ let parenLevel = 0, hasClosedParen = false;
555
+ for (let i = colonPos + 1; i < posCh; i++) {
556
+ const c = line[i];
557
+ if (c === '(') parenLevel++;
558
+ else if (c === ')') {
559
+ parenLevel--;
560
+ if (parenLevel === 0) hasClosedParen = true;
561
+ }
562
+ }
563
+ if (hasClosedParen && parenLevel === 0) return { pType: 'none' };
564
+
565
+ // 处理语法结构和表达式内容,边界
566
+ let pEnd = pStart, depth = 0;
567
+ while (pEnd < lineLen && line[pEnd] === ':') pEnd++;
568
+ while (pEnd < lineLen) {
569
+ const c = line[pEnd];
570
+ if (c === '(') depth++;
571
+ else if (c === ')') depth--;
572
+ if (depth === 0) {
573
+ if (c === ':' || /[;{}>,+~\[\]]/.test(c)) break;
574
+ }
575
+ pEnd++;
576
+ }
577
+
578
+ if (posCh > pEnd) return { pType: 'none' };
579
+ return { pType, pStart, pPrefix, pEnd };
580
+ }
581
+
582
+ // 检测光标是否在var()内部,并返回右括号的位置
583
+ function getVarCallInfo(line, posCh, sliceLine) {
584
+ const lastVarIndex = sliceLine.lastIndexOf('var(');
585
+ if (lastVarIndex === -1) return { inside: false, closed: false, closePos: -1 };
586
+
587
+ let balance = 0, closed = false, closePos = -1;
588
+ for (let i = lastVarIndex + 4; i < line.length; i++) {
589
+ const ch = line[i];
590
+ if (ch === '(') balance++;
591
+ else if (ch === ')') {
592
+ if (balance === 0) {
593
+ closed = true, closePos = i;
594
+ break;
595
+ }
596
+ else balance--;
597
+ }
598
+ }
599
+
600
+ const inside = lastVarIndex + 4 <= posCh && (!closed || posCh <= closePos);
601
+ return { inside, closed, closePos };
602
+ }
603
+
604
+ // 获取当前光标所在值单词的范围
605
+ function getReplacementRangeForValue(line, lineLen, posLine, posCh, inside, insideParensWithAtRule) {
606
+ if (inside) {
607
+ let start = posCh;
608
+ while (start > 0 && PU.test(line[start - 1])) start--;
609
+ let end = posCh;
610
+ while (end < lineLen && PU.test(line[end])) end++;
611
+ return { from: CodeMirror.Pos(posLine, start), to: CodeMirror.Pos(posLine, end) };
612
+ }
613
+
614
+ // 普通值逻辑
615
+ let colonIndex = -1;
616
+ for (let i = posCh - 1; i >= 0; i--) {
617
+ if (line[i] === ':') {
618
+ let j = i - 1;
619
+ while (j >= 0 && whitespace.test(line[j])) j--;
620
+ let end = j;
621
+ while (j >= 0 && isWordChar(line[j])) j--;
622
+ if (j < end) {
623
+ colonIndex = i;
624
+ break;
625
+ }
626
+ }
627
+ }
628
+
629
+ if (colonIndex === -1) {
630
+ let end = posCh;
631
+ while (end < lineLen && isWordChar(line[end])) end++;
632
+ return {
633
+ from: CodeMirror.Pos(posLine, Math.min(posCh, end)), to: CodeMirror.Pos(posLine, end)
634
+ };
635
+ }
636
+
637
+ let start = colonIndex + 1;
638
+ while (start < lineLen && whitespace.test(line[start])) start++;
639
+
640
+ let end = start;
641
+ while (end < lineLen) {
642
+ const char = line[end];
643
+ if (insideParensWithAtRule) {
644
+ if (char === ')' || char === ';' || char === '}') break;
645
+ if (isLogicOpStart(line, end) && (end === start || whitespace.test(line[end - 1]))) break;
646
+ }
647
+ else if (char === ';' || char === '}') break;
648
+ end++;
649
+ }
650
+
651
+ while (end > start && whitespace.test(line[end - 1])) end--;
652
+ return {
653
+ from: CodeMirror.Pos(posLine, Math.min(start, posCh, end)), to: CodeMirror.Pos(posLine, end)
654
+ };
655
+ }
656
+
657
+ // 提取 CSS 变量名
658
+ function extractCSSVariables(cssText) {
659
+ if (!cssText) return [];
660
+ const cleanCss = stripCommentsAndStrings(cssText), varRegex = /--([-\w\p{L}\p{N}]+)\s*:/gu, varNames = new Set();
661
+ let match;
662
+ while ((match = varRegex.exec(cleanCss)) !== null) varNames.add(`--${match[1]}`);
663
+ return Array.from(varNames);
664
+ }
665
+
666
+ /**
667
+ * 检查从 startPos 开始(含)之后,第一个非空白字符是否为分号
668
+ * @param {string} line 当前行完整字符串
669
+ * @param {number} startPos 起始索引(包含)
670
+ * @returns {boolean} 若找到的第一个非空白字符是分号,返回 true,否则返回 false
671
+ */
672
+ function hasSemicolonAfter(line, startPos) {
673
+ const after = line.slice(startPos);
674
+ for (let i = 0; i < after.length; i++) {
675
+ const ch = after[i];
676
+ if (ch === ';') return true;
677
+ if (!whitespace.test(ch)) return false;
678
+ }
679
+ return false;
680
+ }
681
+
682
+ /**
683
+ * 尝试获取当前光标所在位置的 @规则类型,优先使用传入的 atRuleType,
684
+ * 若不可信则通过向上查找括号匹配来获取真正的规则名。
685
+ * @param {string|null} atRuleType 初始的规则类型
686
+ * @param {boolean} insideBlock 是否在块内
687
+ * @param {CodeMirror} cm CodeMirror 实例
688
+ * @param {number} posLine 光标行号
689
+ * @param {number} posCh 光标列号
690
+ * @returns {string|null} 有效的规则名(小写),若未找到则返回 null
691
+ */
692
+ function resolveAtRuleType(atRuleType, insideBlock, cm, posLine, posCh) {
693
+ let effective = atRuleType;
694
+ if (insideBlock && (!effective || !descriptorAtRules.has(effective))) {
695
+ let sLine = posLine;
696
+ while (sLine >= 0) {
697
+ const lineText = cm.getLine(sLine), atRegex = /@([-\w\p{L}\p{N}]+)\s*([^{]*)?\{/gu;
698
+ let match;
699
+ while ((match = atRegex.exec(lineText)) !== null) {
700
+ const ruleName = match[1].toLowerCase(), bPos = match.index + match[0].length - 1,
701
+ startPos = { line: sLine, ch: bPos }, endMatch = cm.findMatchingBracket(startPos);
702
+ if (endMatch?.match) {
703
+ const { line: endL, ch: endC } = endMatch.to;
704
+ if ((sLine < posLine || (sLine === posLine && bPos < posCh)) && (endL > posLine
705
+ || (endL === posLine && endC > posCh))) return ruleName;
706
+ }
707
+ }
708
+ sLine--;
709
+ }
710
+ }
711
+ return effective;
712
+ }
713
+
714
+ // ---------- 主 hint 函数 ----------
715
+ CodeMirror.registerHelper('hint', 'css', cm => {
716
+ const pos = cm.getCursor(), posLine = pos.line, line = cm.getLine(posLine), posCh = pos.ch;
717
+ if (posCh > 0 && whitespace.test(line[posCh - 1])) return null;
718
+
719
+ const sliceLine = line.slice(0, posCh), { state, type, start, end, string } = cm.getTokenAt(pos), lineLen = line.length,
720
+ cssText = cm.getValue(), oFrom = CodeMirror.Pos(posLine, start), oTo = CodeMirror.Pos(posLine, end),
721
+ atMatch = sliceLine.match(/@(media|supports|container|scope)\s*\([^)]*$/i),
722
+ { inValue, atRuleType, insideBlock, insideParensWithAtRule } = getContextInfo(type, sliceLine, state, posCh, atMatch);
723
+
724
+ let prefix = string.trim(), candidates = [];
725
+ // @ 规则补全
726
+ if (prefix.startsWith('@')) {
727
+ const list = filterHints(prefix, AT_RULES);
728
+ if (list.length > 0) return { list, from: oFrom, to: oTo };
729
+ }
730
+
731
+ // 属性值补全
732
+ if (inValue) {
733
+ const { inside, closed, closePos } = getVarCallInfo(line, posCh, sliceLine), range = getReplacementRangeForValue(
734
+ line, lineLen, posLine, posCh, inside, insideParensWithAtRule);
735
+ let from, to, needSemicolon = false;
736
+ if (range) from = range.from, to = range.to, needSemicolon = range.needSemicolon || false;
737
+ else from = to = pos;
738
+
739
+ const match = sliceLine.match(/([-\w\p{L}\p{N}]+)\s*:\s*[^;)]*$/u), featureName = match ? match[1] : null;
740
+ let effectiveAtRuleType = resolveAtRuleType(atRuleType, insideBlock, cm, posLine, posCh),
741
+ specificCandidates = getValueCandidates(insideParensWithAtRule, effectiveAtRuleType, featureName);
742
+
743
+ candidates = [...new Set([...specificCandidates, ...GLOBAL_KEYWORDS])];
744
+ // CSS 变量补全
745
+ const varNames = extractCSSVariables(cssText), shouldAddVarHints = (prefix?.startsWith('--')) || inside;
746
+ if (shouldAddVarHints && varNames.length) {
747
+ const varCandidates = filterHints(prefix, varNames);
748
+ candidates = [...new Set([...candidates, ...varCandidates.map(c => c.text)])];
749
+ }
750
+
751
+ if (candidates.length === 0) return null;
752
+ if (prefix === ':' || prefix === '::' || !prefix) prefix = '';
753
+ let list = filterHints(prefix, candidates);
754
+ if (list.length) {
755
+ let suffix = '';
756
+ if (inside) {
757
+ if (closed) {
758
+ const afterClosePos = closePos + 1, hasSemicolon = hasSemicolonAfter(line, afterClosePos);
759
+ if (!hasSemicolon) to = CodeMirror.Pos(posLine, afterClosePos), suffix = ');';
760
+ } else {
761
+ const hasSemicolon = hasSemicolonAfter(line, posCh);
762
+ suffix = hasSemicolon ? ')' : ');';
763
+ }
764
+ } else suffix = ';';
765
+ if (suffix) list = list.map(item => ({ text: `${item.text}${suffix}`, displayText: item.displayText }));
766
+ return { list, from, to };
767
+ }
768
+ return null;
769
+ }
770
+ // 非值补全
771
+ else {
772
+ // 1. 处理各种 @规则 后面的运算符或名称补全
773
+ const atRuleMatch = sliceLine.match(/@(media|supports|container|layer|keyframes|page)\s+([^;{]*)$/i);
774
+ if (atRuleMatch) {
775
+ const atRule = atRuleMatch[1].toLowerCase(), after = atRuleMatch[2], hasOpenParen = after.includes('('),
776
+ hasOpenBrace = after.includes('{'), commonMatch = after.match(/([\w-]*)$/),
777
+ commonPrefix = commonMatch ? commonMatch[1] : '', commonWordStart = posCh - commonPrefix.length;
778
+
779
+ function buildCompletion(prefix, candidates, wordStart = commonWordStart) {
780
+ const list = filterHints(prefix, candidates);
781
+ if (list.length)
782
+ return { list, from: CodeMirror.Pos(posLine, wordStart), to: CodeMirror.Pos(posLine, posCh) };
783
+ return null;
784
+ }
785
+
786
+ if (atRule === 'media' && !hasOpenParen && !hasOpenBrace) {
787
+ if (after.includes(':')) return null;
788
+ let candidates;
789
+ if (commonPrefix && MEDIA_OPERATORS.some(op => op.startsWith(commonPrefix.toLowerCase())))
790
+ candidates = MEDIA_OPERATORS;
791
+ else candidates = MEDIA_TYPES;
792
+ const result = buildCompletion(commonPrefix, candidates);
793
+ if (result) return result;
794
+ }
795
+
796
+ if ((atRule === 'supports' || atRule === 'container') && !hasOpenParen && !hasOpenBrace) {
797
+ if (commonPrefix && /^\w*$/.test(commonPrefix)) {
798
+ const result = buildCompletion(commonPrefix, SUPPORTS_OPERATORS);
799
+ if (result) return result;
800
+ }
801
+ }
802
+
803
+ if (atRule === 'layer' || atRule === 'keyframes') {
804
+ const cleanCss = stripCommentsAndStrings(cssText), regex = atRule === 'layer'
805
+ ? /@layer\s+([-\w\p{L}\p{N}]+)(?=\s*[;{,]|\s*\{)/gu : /@keyframes\s+([-\w\p{L}\p{N}]+)(?=\s*\{)/gu,
806
+ matches = [...cleanCss.matchAll(regex)], names = [...new Set(matches.map(m => m[1]))],
807
+ result = buildCompletion(commonPrefix, names);
808
+ if (result) return result;
809
+ }
810
+
811
+ if (atRule === 'page' && !hasOpenBrace) {
812
+ const colonMatch = after.match(/:([\w-]*)$/);
813
+ if (colonMatch) {
814
+ const prefix = colonMatch[1], wordStart = posCh - prefix.length - 1,
815
+ rawCandidates = PAGE_PSEUDO_CLASSES.map(p => p.slice(1)),
816
+ list = filterHints(prefix, rawCandidates).map(item => ({
817
+ text: `: ${item.text}`, displayText: `: ${item.displayText}`
818
+ }));
819
+ if (list.length)
820
+ return { list, from: CodeMirror.Pos(posLine, wordStart), to: CodeMirror.Pos(posLine, posCh) };
821
+ }
822
+ }
823
+ }
824
+
825
+ // 2. 伪类/伪元素补全
826
+ const { pType, pStart, pPrefix, pEnd } = getPseudoContext(line, lineLen, posCh);
827
+ if (pType !== 'none') {
828
+ const [source, stripCount] = pType === 'element' ? [PSEUDO_ELEMENTS, 2] : [PSEUDO_CLASSES, 1];
829
+ let list = filterHints(pPrefix, source, item => item.slice(stripCount));
830
+ if (list.length) {
831
+ let from = CodeMirror.Pos(posLine, pStart), to = CodeMirror.Pos(posLine, pEnd);
832
+ if (!pPrefix) {
833
+ let wordEnd = findWordBoundary(line, lineLen, pEnd);
834
+ if (wordEnd > pEnd) to = CodeMirror.Pos(posLine, wordEnd);
835
+ }
836
+ return { list, from, to };
837
+ }
838
+ }
839
+
840
+ // 3. ID/类选择器补全
841
+ if (prefix.startsWith('#') || prefix.startsWith('.')) {
842
+ const candidatesSet = new Set(), regex = /([#.](?![#.\d])[-\w\p{L}\p{N}]+)(?=[,\s>+~[:{@(]|$)/gmu,
843
+ cleanCss = stripCommentsAndStrings(cssText);
844
+ let match, braceDepth = 0, lastIndex = 0;
845
+ while ((match = regex.exec(cleanCss)) !== null) {
846
+ const segment = cleanCss.slice(lastIndex, match.index);
847
+ for (let i = 0; i < segment.length; i++) {
848
+ if (segment[i] === '{') braceDepth++;
849
+ else if (segment[i] === '}') braceDepth--;
850
+ }
851
+ lastIndex = match.index;
852
+ if (braceDepth === 0) {
853
+ const fullMatch = match[1];
854
+ if ((prefix.startsWith('#') && fullMatch[0] === '#') || (prefix.startsWith('.') && fullMatch[0] === '.'))
855
+ candidatesSet.add(fullMatch);
856
+ }
857
+ }
858
+ const candidates = Array.from(candidatesSet), list = filterHints(prefix, candidates);
859
+ return list.length ? { list, from: oFrom, to: oTo } : null;
860
+ }
861
+
862
+ // 4. 属性名/其他元素补全
863
+ if (strIncludesAny(type, ['property', 'variable-2'])) {
864
+ let effectiveAtRuleType = resolveAtRuleType(atRuleType, insideBlock, cm, posLine, posCh);
865
+
866
+ if (insideBlock && descriptorAtRules.has(effectiveAtRuleType))
867
+ candidates = AT_RULE_BLOCK_DESCRIPTORS[effectiveAtRuleType] || [];
868
+ else candidates = CSS_PROPERTIES;
869
+ }
870
+ else candidates = getNonValueCandidates(atRuleType, insideBlock, insideParensWithAtRule, atMatch);
871
+
872
+ let list = filterHints(prefix, candidates);
873
+ return list.length ? { list, from: oFrom, to: oTo } : null;
874
+ }
875
+ });
876
+ })();