File: //proc/self/cwd/wp-content/plugins/cornerstone/assets/js/app/csslint/parserlib-base.js
'use strict';
(() => {
//#region Properties
const GlobalKeywords = ['initial', 'inherit', 'revert', 'unset'];
const Properties = {
__proto__: null,
'accent-color': 'auto | <color>',
'align-items':
'normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ]',
'align-content': 'normal | <baseline-position> | <content-distribution> | ' +
'<overflow-position>? <content-position>',
'align-self':
'auto | normal | stretch | <baseline-position> | <overflow-position>? <self-position>',
'all': GlobalKeywords.join(' | '),
'alignment-baseline': 'auto | baseline | use-script | before-edge | text-before-edge | ' +
'after-edge | text-after-edge | central | middle | ideographic | alphabetic | ' +
'hanging | mathematical',
'animation': '[ <time> || <timing-function> || <time> || [ infinite | <num> ] || ' +
'<animation-direction> || <animation-fill-mode> || ' +
'[ running | paused ] || [ none | <custom-ident> | <string> ] ]#',
'animation-composition': '[ replace | add | accumulate ]#',
'animation-delay': '<time>#',
'animation-direction': '<animation-direction>#',
'animation-duration': '<time>#',
'animation-fill-mode': '<animation-fill-mode>#',
'animation-iteration-count': '[ <num> | infinite ]#',
'animation-name': '[ none | <keyframes-name> ]#',
'animation-play-state': '[ running | paused ]#',
'animation-timing-function': '<timing-function>#',
'appearance': 'none | auto',
'-moz-appearance':
'none | button | button-arrow-down | button-arrow-next | button-arrow-previous | ' +
'button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | ' +
'checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | ' +
'menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | ' +
'menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | ' +
'menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | ' +
'progresschunk | progresschunk-vertical | radio | radio-container | radio-label | ' +
'radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | ' +
'scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | ' +
'scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | ' +
'scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | ' +
'scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | ' +
'searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | ' +
'spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | ' +
'tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | ' +
'toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | ' +
'treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | ' +
'treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | ' +
'-moz-win-browsertabbar-toolbox | -moz-win-communicationstext | ' +
'-moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | ' +
'-moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | ' +
'-moz-window-button-box-maximized | -moz-window-button-close | ' +
'-moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | ' +
'-moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | ' +
'-moz-window-titlebar | -moz-window-titlebar-maximized',
'-ms-appearance':
'none | icon | window | desktop | workspace | document | tooltip | dialog | button | ' +
'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
'outline-tree | range | field | combo-box | signature | password | normal',
'-webkit-appearance':
'auto | none | button | button-bevel | caps-lock-indicator | caret | checkbox | ' +
'default-button | listbox | listitem | media-fullscreen-button | media-mute-button | ' +
'media-play-button | media-seek-back-button | media-seek-forward-button | media-slider | ' +
'media-sliderthumb | menulist | menulist-button | menulist-text | menulist-textfield | ' +
'push-button | radio | searchfield | searchfield-cancel-button | searchfield-decoration | ' +
'searchfield-results-button | searchfield-results-decoration | slider-horizontal | ' +
'slider-vertical | sliderthumb-horizontal | sliderthumb-vertical | square-button | ' +
'textarea | textfield | scrollbarbutton-down | scrollbarbutton-left | ' +
'scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | ' +
'scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | ' +
'scrollbartrack-horizontal | scrollbartrack-vertical',
'-o-appearance':
'none | window | desktop | workspace | document | tooltip | dialog | button | ' +
'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
'outline-tree | range | field | combo-box | signature | password | normal',
'aspect-ratio': 'auto || <ratio>',
'backdrop-filter': '<filter-function-list> | none',
'backface-visibility': '<vis-hid>',
'background': '[ <bg-layer> , ]* <final-bg-layer>',
'background-attachment': '<attachment>#',
'background-blend-mode': '<blend-mode>',
'background-clip': '[ <box> | text ]#',
'background-color': '<color>',
'background-image': '<bg-image>#',
'background-origin': '<box>#',
'background-position': '<bg-position>#',
'background-position-x': '[ center | [ left | right ]? <len-pct>? ]#',
'background-position-y': '[ center | [ top | bottom ]? <len-pct>? ]#',
'background-repeat': '<repeat-style>#',
'background-size': '<bg-size>#',
'baseline-shift': 'baseline | sub | super | <len-pct>',
'baseline-source': 'auto | first | last',
'block-size': '<width>',
'border-collapse': 'collapse | separate',
'border-image': '[ none | <image> ] || <border-image-slice> ' +
'[ / <border-image-width> | / <border-image-width>? / <border-image-outset> ]? || ' +
'<border-image-repeat>',
'border-image-outset': '[ <len> | <num> ]{1,4}',
'border-image-repeat': '[ stretch | repeat | round | space ]{1,2}',
'border-image-slice': '<border-image-slice>',
'border-image-source': '<image> | none',
'border-image-width': '[ <len-pct> | <num> | auto ]{1,4}',
'border-spacing': '<len>{1,2}',
'border-bottom-left-radius': '<len-pct>{1,2}',
'border-bottom-right-radius': '<len-pct>{1,2}',
'border-end-end-radius': '<len-pct>{1,2}',
'border-end-start-radius': '<len-pct>{1,2}',
'border-radius': '<len-pct0+>{1,4} [ / <len-pct0+>{1,4} ]?',
'border-start-end-radius': '<len-pct>{1,2}',
'border-start-start-radius': '<len-pct>{1,2}',
'border-top-left-radius': '<len-pct>{1,2}',
'border-top-right-radius': '<len-pct>{1,2}',
'bottom': '<width>',
'box-decoration-break': 'slice | clone',
'box-shadow': 'none | <shadow>#',
'box-sizing': 'content-box | border-box',
'break-after': '<break-inside> | always | left | right | page | column',
'break-before': '<break-after>',
'break-inside': 'auto | avoid | avoid-page | avoid-column',
'caret-color': 'auto | <color>',
'caption-side': 'top | bottom | inline-start | inline-end',
'clear': 'none | right | left | both | inline-start | inline-end',
'clip': '<rect> | auto',
'clip-path': '<uri> | [ <basic-shape> || <geometry-box> ] | none',
'clip-rule': 'nonzero | evenodd',
'color': '<color>',
'color-interpolation': 'auto | sRGB | linearRGB',
'color-interpolation-filters': '<color-interpolation>',
'color-profile': 1,
'color-rendering': 'auto | optimizeSpeed | optimizeQuality',
'color-scheme': 'normal | [ light | dark | <custom-ident> ]+ && only?',
'column-count': '<int> | auto',
'column-fill': 'auto | balance',
'column-gap': 'normal | <len-pct>',
'column-rule': '<border-shorthand>',
'column-rule-color': '<color>',
'column-rule-style': '<border-style>',
'column-rule-width': '<border-width>',
'column-span': 'none | all',
'column-width': '<len> | auto',
'columns': 1,
'contain': 'none | strict | content | [ size || layout || style || paint ]',
'contain-intrinsic-size': '<contain-intrinsic>{1,2}',
'container': '<container-name> [ / <container-type> ]?',
'container-name': 'none | <custom-ident>+',
'container-type': 'normal || [ size | inline-size ]',
'content': 'normal | none | <content-list> [ / <string> ]?',
'content-visibility': 'auto | <vis-hid>',
'counter-increment': '<counter>',
'counter-reset': '<counter>',
'counter-set': '<counter>',
'cursor': '[ <uri> [ <num> <num> ]? , ]* ' +
'[ auto | default | none | context-menu | help | pointer | progress | wait | ' +
'cell | crosshair | text | vertical-text | alias | copy | move | no-drop | ' +
'not-allowed | grab | grabbing | e-resize | n-resize | ne-resize | nw-resize | ' +
's-resize | se-resize | sw-resize | w-resize | ew-resize | ns-resize | ' +
'nesw-resize | nwse-resize | col-resize | row-resize | all-scroll | ' +
'zoom-in | zoom-out ]',
'cx': '<x>',
'cy': '<x>',
'd': 1,
'direction': 'ltr | rtl',
'display': '[ <display-outside> || <display-inside> ] | ' +
'<display-listitem> | <display-internal> | <display-box> | <display-legacy> | ' +
'-webkit-box | -webkit-inline-box | -ms-flexbox', // deprecated and nonstandard
'dominant-baseline': 'auto | text-bottom | alphabetic | ideographic | middle | central | ' +
'mathematical | hanging | text-top',
'empty-cells': 'show | hide',
'fill': '<paint>',
'fill-opacity': '<num0-1>',
'fill-rule': 'nonzero | evenodd',
'filter': '<filter-function-list> | <ie-function> | none',
'flex': '<flex-shorthand>',
'flex-basis': '<width>',
'flex-direction': 'row | row-reverse | column | column-reverse',
'flex-flow': '<flex-direction> || <flex-wrap>',
'flex-grow': '<num>',
'flex-shrink': '<num>',
'flex-wrap': 'nowrap | wrap | wrap-reverse',
'float': 'left | right | none | inline-start | inline-end',
'flood-color': 1,
'flood-opacity': '<num0-1>',
// matching no-pct first because Matcher doesn't retry for a longer match in nested definitions
'font': '<font-short-tweak-no-pct>? <font-short-core> | ' +
'[ <font-short-tweak-no-pct> || <pct> ]? <font-short-core> | ' +
'caption | icon | menu | message-box | small-caption | status-bar',
'font-family': '[ <generic-family> | <family-name> ]#',
'font-feature-settings': '[ <ascii4> [ <int0+> | on | off ]? ]# | normal',
'font-kerning': 'auto | normal | none',
'font-language-override': 'normal | <string>',
'font-optical-sizing': 'auto | none',
'font-palette': 'none | normal | light | dark | <custom-ident>',
'font-size': '<absolute-size> | <relative-size> | <len-pct0+>',
'font-size-adjust': '<num> | none',
'font-stretch': '<font-stretch-named> | <pct>',
'font-style': 'normal | italic | oblique <angle>?',
'font-synthesis': 'none | [ weight || style ]',
'font-synthesis-style': 'auto | none',
'font-synthesis-weight': 'auto | none',
'font-synthesis-small-caps': 'auto | none',
'font-variant': 'normal | none | [ ' +
'<font-variant-ligatures> || <font-variant-alternates> || ' +
'<font-variant-caps> || <font-variant-numeric> || <font-variant-east-asian> ]',
'font-variant-alternates': '<font-variant-alternates> | normal',
'font-variant-caps': '<font-variant-caps> | normal',
'font-variant-east-asian': '<font-variant-east-asian> | normal',
'font-variant-emoji': 'auto | text | emoji | unicode',
'font-variant-ligatures': '<font-variant-ligatures> | normal | none',
'font-variant-numeric': '<font-variant-numeric> | normal',
'font-variant-position': 'normal | sub | super',
'font-variation-settings': 'normal | [ <string> <num> ]#',
'font-weight': 'normal | bold | bolder | lighter | <num1-1000>',
'forced-color-adjust': 'auto | none | preserve-parent-color',
'gap': '<column-gap>{1,2}',
'grid':
'<grid-template> | <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>? | ' +
'[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>',
'grid-area': '<grid-line> [ / <grid-line> ]{0,3}',
'grid-auto-columns': '<track-size>+',
'grid-auto-flow': '[ row | column ] || dense',
'grid-auto-rows': '<track-size>+',
'grid-column': '<grid-line> [ / <grid-line> ]?',
'grid-column-end': '<grid-line>',
'grid-column-gap': -1,
'grid-column-start': '<grid-line>',
'grid-gap': -1,
'grid-row': '<grid-line> [ / <grid-line> ]?',
'grid-row-end': '<grid-line>',
'grid-row-gap': -1,
'grid-row-start': '<grid-line>',
'grid-template': 'none | [ <grid-template-rows> / <grid-template-columns> ] | ' +
'[ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?',
'grid-template-areas': 'none | <string>+',
'grid-template-columns': '<grid-template-rows>',
'grid-template-rows': 'none | <track-list> | <auto-track-list>',
'hanging-punctuation': 'none | [ first || [ force-end | allow-end ] || last ]',
'height': 'auto | <width-height>',
'hyphenate-character': '<string> | auto',
'hyphenate-limit-chars': '[ auto | <int> ]{1,3}',
'hyphens': 'none | manual | auto',
'image-orientation': 'from-image | none | [ <angle> || flip ]',
'image-rendering': 'auto | smooth | high-quality | crisp-edges | pixelated | ' +
'optimizeSpeed | optimizeQuality | -webkit-optimize-contrast',
'image-resolution': 1,
'inline-size': '<width>',
'inset': '<width>{1,4}',
'inset-block': '<width>{1,2}',
'inset-block-end': '<width>',
'inset-block-start': '<width>',
'inset-inline': '<width>{1,2}',
'inset-inline-end': '<width>',
'inset-inline-start': '<width>',
'isolation': 'auto | isolate',
'justify-content': 'normal | <content-distribution> | ' +
'<overflow-position>? [ <content-position> | left | right ]',
'justify-items': 'normal | stretch | <baseline-position> | ' +
'[ <overflow-position>? <self-position> ] | ' +
'[ legacy || [ left | right | center ] ]',
'justify-self': 'auto | normal | stretch | <baseline-position> | ' +
'<overflow-position>? [ <self-position> | left | right ]',
'left': '<width>',
'letter-spacing': '<len> | normal',
'lighting-color': '<color>',
'line-height': '<line-height>',
'line-break': 'auto | loose | normal | strict | anywhere',
'list-style': '<list-style-position> || <list-style-image> || <list-style-type>',
'list-style-image': '<image> | none',
'list-style-position': 'inside | outside',
'list-style-type': '<string> | disc | circle | square | decimal | decimal-leading-zero | ' +
'lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | ' +
'georgian | lower-alpha | upper-alpha | none | symbols()',
'math-depth': 'auto-add | add(<int>) | <int>',
'math-shift': '<math-style>',
'math-style': 'normal | compact',
'margin': '<width>{1,4}',
'margin-bottom': '<width>',
'margin-left': '<width>',
'margin-right': '<width>',
'margin-top': '<width>',
'margin-block': '<width>{1,2}',
'margin-block-end': '<width>',
'margin-block-start': '<width>',
'margin-inline': '<width>{1,2}',
'margin-inline-end': '<width>',
'margin-inline-start': '<width>',
'marker': -1,
'marker-end': 1,
'marker-mid': 1,
'marker-start': 1,
'mask': '[ [ none | <image> ] || <position> [ / <bg-size> ]? || <repeat-style> || ' +
'<geometry-box> || [ <geometry-box> | no-clip ] || ' +
'[ add | subtract | intersect | exclude ] || [ alpha | luminance | match-source ] ]#',
'mask-image': '[ none | <image> ]#',
'mask-type': 'luminance | alpha',
'max-height': 'none | <width-height>',
'max-width': 'none | <width-height>',
'min-height': 'auto | <width-height>',
'min-width': 'auto | <width-height>',
'max-block-size': '<len-pct> | none',
'max-inline-size': '<len-pct> | none',
'min-block-size': '<len-pct>',
'min-inline-size': '<len-pct>',
'mix-blend-mode': '<blend-mode>',
'object-fit': 'fill | contain | cover | none | scale-down',
'object-position': '<position>',
'object-view-box': 'none | <inset> | <rect> | <xywh>',
'offset':
'[ <offset-position>? <offset-path> [<len-pct> || <offset-rotate>]? | <offset-position> ] ' +
'[ / <offset-anchor> ]?',
'offset-anchor': 'auto | <position>',
'offset-distance': '<len-pct>',
'offset-path': 'none | ray() | path() | <uri> | [<basic-shape> && <coord-box>?] | <coord-box>',
'offset-position': 'auto | <position>',
'offset-rotate': '[ auto | reverse ] || <angle>',
'opacity': '<num0-1> | <pct>',
'order': '<int>',
'orphans': '<int>',
'outline': '[ <color> | invert ] || [ auto | <border-style> ] || <border-width>',
'outline-color': '<color> | invert',
'outline-offset': '<len>',
'outline-style': '<border-style> | auto',
'outline-width': '<border-width>',
'overflow': '<overflow>{1,2}',
'overflow-anchor': 'auto | none',
'overflow-block': '<overflow>',
'overflow-clip-margin': 'visual-box | <len0+>',
'overflow-inline': '<overflow>',
'overflow-wrap': 'normal | break-word | anywhere',
'overflow-x': '<overflow>',
'overflow-y': '<overflow>',
'overscroll-behavior': '<overscroll>{1,2}',
'overscroll-behavior-block': '<overscroll>',
'overscroll-behavior-inline': '<overscroll>',
'overscroll-behavior-x': '<overscroll>',
'overscroll-behavior-y': '<overscroll>',
'padding': '<len-pct0+>{1,4}',
'padding-block': '<len-pct0+>{1,2}',
'padding-block-end': '<len-pct0+>',
'padding-block-start': '<len-pct0+>',
'padding-bottom': '<len-pct0+>',
'padding-inline': '<len-pct0+>{1,2}',
'padding-inline-end': '<len-pct0+>',
'padding-inline-start': '<len-pct0+>',
'padding-left': '<len-pct0+>',
'padding-right': '<len-pct0+>',
'padding-top': '<len-pct0+>',
'page': 'auto | <custom-ident>',
'page-break-after': 'auto | always | avoid | left | right | recto | verso',
'page-break-before': '<page-break-after>',
'page-break-inside': 'auto | avoid',
'paint-order': 'normal | [ fill || stroke || markers ]',
'perspective': 'none | <len0+>',
'perspective-origin': '<position>',
'place-content': '<align-content> <justify-content>?',
'place-items': '[ normal | stretch | <baseline-position> | <self-position> ] ' +
'[ normal | stretch | <baseline-position> | <self-position> ]?',
'place-self': '<align-self> <justify-self>?',
'pointer-events': 'auto | none | visiblePainted | visibleFill | visibleStroke | visible | ' +
'painted | fill | stroke | all',
'position': 'static | relative | absolute | fixed | sticky',
'print-color-adjust': 'economy | exact',
'quotes': 1,
'r': 1, // SVG
'rx': '<x> | auto', // SVG
'ry': '<rx>', // SVG
'rendering-intent': 1, // SVG
'resize': 'none | both | horizontal | vertical | block | inline',
'right': '<width>',
'rotate': 'none | [ x | y | z | <num>{3} ]? && <angle>',
'row-gap': '<column-gap>',
'ruby-align': 1,
'ruby-position': 1,
'scale': 'none | <num-pct>{1,3}',
'scroll-behavior': 'auto | smooth',
'scroll-margin': '<len>{1,4}',
'scroll-margin-bottom': '<len>',
'scroll-margin-left': '<len>',
'scroll-margin-right': '<len>',
'scroll-margin-top': '<len>',
'scroll-margin-block': '<len>{1,2}',
'scroll-margin-block-end': '<len>',
'scroll-margin-block-start': '<len>',
'scroll-margin-inline': '<len>{1,2}',
'scroll-margin-inline-end': '<len>',
'scroll-margin-inline-start': '<len>',
'scroll-padding': '<width>{1,4}',
'scroll-padding-left': '<width>',
'scroll-padding-right': '<width>',
'scroll-padding-top': '<width>',
'scroll-padding-bottom': '<width>',
'scroll-padding-block': '<width>{1,2}',
'scroll-padding-block-end': '<width>',
'scroll-padding-block-start': '<width>',
'scroll-padding-inline': '<width>{1,2}',
'scroll-padding-inline-end': '<width>',
'scroll-padding-inline-start': '<width>',
'scroll-snap-align': '[ none | start | end | center ]{1,2}',
'scroll-snap-stop': 'normal | always',
'scroll-snap-type': 'none | [ x | y | block | inline | both ] [ mandatory | proximity ]?',
'scrollbar-color': 'auto | dark | light | <color>{2}',
'scrollbar-gutter': 'auto | stable && both-edges?',
'scrollbar-width': 'auto | thin | none',
'shape-image-threshold': '<num-pct>',
'shape-margin': '<len-pct>',
'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision',
'shape-outside': 'none | [ <basic-shape> || <shape-box> ] | <image>',
'speak': 'auto | never | always',
'stop-color': 1,
'stop-opacity': '<num0-1>',
'stroke': '<paint>',
'stroke-dasharray': 'none | <dasharray>',
'stroke-dashoffset': '<len-pct> | <num>',
'stroke-linecap': 'butt | round | square',
'stroke-linejoin': 'miter | miter-clip | round | bevel | arcs',
'stroke-miterlimit': '<num0+>',
'stroke-opacity': '<num0-1>',
'stroke-width': '<len-pct> | <num>',
'table-layout': 'auto | fixed',
'tab-size': '<num> | <len>',
'text-align': '<text-align> | justify-all',
'text-align-last': '<text-align> | auto',
'text-anchor': 'start | middle | end',
'text-combine-upright': 'none | all | [ digits <int2-4>? ]',
'text-decoration': '<text-decoration-line> || <text-decoration-style> || <color>',
'text-decoration-color': '<color>',
'text-decoration-line': 'none | [ underline || overline || line-through || blink ]',
'text-decoration-skip': 'none | auto',
'text-decoration-skip-ink': 'none | auto | all',
'text-decoration-style': 'solid | double | dotted | dashed | wavy',
'text-decoration-thickness': 'auto | from-font | <len-pct>',
'text-emphasis': '<text-emphasis-style> || <color>',
'text-emphasis-color': '<color>',
'text-emphasis-style': 'none | <string> | ' +
'[ [ filled | open ] || [ dot | circle | double-circle | triangle | sesame ] ]',
'text-emphasis-position': '[ over | under ] && [ right | left ]?',
'text-indent': '<len-pct> && hanging? && each-line?',
'text-justify': 'auto | none | inter-word | inter-character',
'text-orientation': 'mixed | upright | sideways',
'text-overflow': 'clip | ellipsis',
'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision',
'text-shadow': 'none | [ <color>? && <len>{2,3} ]#',
'text-size-adjust': 'auto | none | <pct0+>',
'text-transform': 'none | [ capitalize|uppercase|lowercase ] || full-width || full-size-kana',
'text-underline-offset': '<len-pct> | auto',
'text-underline-position': 'auto | [ under || [ left | right ] ]',
'text-wrap': 'wrap | nowrap | balance | stable | pretty',
'top': '<width>',
'touch-action':
'auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation',
'transform': 'none | <fn:transform>+',
'transform-box': 'border-box | fill-box | view-box',
'transform-origin': '[ left | center | right | <len-pct> ] ' +
'[ top | center | bottom | <len-pct> ] <len>? | ' +
'[ left | center | right | top | bottom | <len-pct> ] | ' +
'[ [ center | left | right ] && [ center | top | bottom ] ] <len>?',
'transform-style': 'flat | preserve-3d',
'transition':
'[ [ none | [ all | <custom-ident> ]# ] || <time> || <timing-function> || <time> ]#',
'transition-delay': '<time>#',
'transition-duration': '<time>#',
'transition-property': 'none | [ all | <custom-ident> ]#',
'transition-timing-function': '<timing-function>#',
'translate': 'none | <len-pct> [ <len-pct> <len>? ]?',
'unicode-range': '<unicode-range>#',
'unicode-bidi': 'normal | embed | isolate | bidi-override | isolate-override | plaintext',
'user-select': 'auto | text | none | contain | all',
'vertical-align': 'auto | use-script | baseline | sub | super | top | text-top | ' +
'central | middle | bottom | text-bottom | <len-pct>',
'visibility': '<vis-hid> | collapse',
'white-space': 'normal | pre | nowrap | pre-wrap | break-spaces | pre-line',
'widows': '<int>',
'width': 'auto | <width-height>',
'will-change': 'auto | <animateable-feature>#',
'word-break': 'normal | keep-all | break-all | break-word',
'word-spacing': '<len> | normal',
'word-wrap': 'normal | break-word | anywhere',
'writing-mode': 'horizontal-tb | vertical-rl | vertical-lr | ' +
'lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb',
'x': '<len-pct> | <num>',
'y': '<x>',
'z-index': '<int> | auto',
'zoom': '<num> | <pct> | normal',
// nonstandard https://compat.spec.whatwg.org/
'-webkit-box-reflect': '[ above | below | right | left ]? <len>? <image>?',
'-webkit-text-fill-color': '<color>',
'-webkit-text-stroke': '<border-width> || <color>',
'-webkit-text-stroke-color': '<color>',
'-webkit-text-stroke-width': '<border-width>',
'-webkit-user-modify': 'read-only | read-write | write-only',
};
const isOwn = Object.call.bind({}.hasOwnProperty);
const pick = (obj, keys, dst = {}) => keys.reduce((res, k) => (((res[k] = obj[k]), res)), dst);
const ScopedProperties = {
__proto__: null,
'counter-style': {
'additive-symbols': '<pad>#',
'fallback': '<ident-not-none>',
'negative': '<prefix>{1,2}',
'pad': '<int0+> && <prefix>',
'prefix': '<string> | <image> | <custom-ident>',
'range': '[ [ <int> | infinite ]{2} ]# | auto',
'speak-as': 'auto | bullets | numbers | words | spell-out | <ident-not-none>',
'suffix': '<prefix>',
'symbols': '<prefix>+',
'system': 'cyclic | numeric | alphabetic | symbolic | additive | [fixed <int>?] | ' +
'[ extends <ident-not-none> ]',
},
'font-face': pick(Properties, [
'font-family',
'font-size',
'font-variant',
'font-variation-settings',
'unicode-range',
], {
'ascent-override': '[ normal | <pct0+> ]{1,2}',
'descent-override': '[ normal | <pct0+> ]{1,2}',
'font-display': 'auto | block | swap | fallback | optional',
'font-stretch': 'auto | <font-stretch>{1,2}',
'font-style': 'auto | normal | italic | oblique <angle>{0,2}',
'font-weight': 'auto | [ normal | bold | <num1-1000> ]{1,2}',
'line-gap-override': '[ normal | <pct0+> ]{1,2}',
'size-adjust': '<pct0+>',
'src': '[ url() [ format( <string># ) ]? | local( <family-name> ) ]#',
}),
'font-palette-values': pick(Properties, ['font-family'], {
'base-palette': 'light | dark | <int0+>',
'override-colors': '[ <int0+> <color> ]#',
}),
'media': {
'<all>': true,
'any-hover': 'none | hover',
'any-pointer': 'none | coarse | fine',
'color': '<int>',
'color-gamut': 'srgb | p3 | rec2020',
'color-index': '<int>',
'grid': '<int0-1>',
'hover': 'none | hover',
'monochrome': '<int>',
'overflow-block': 'none | scroll | paged',
'overflow-inline': 'none | scroll',
'pointer': 'none | coarse | fine',
'resolution': '<resolution> | infinite',
'scan': 'interlace | progressive',
'update': 'none | slow | fast',
// deprecated
'device-aspect-ratio': '<ratio>',
'device-height': '<len>',
'device-width': '<len>',
},
'page': {
'<all>': true,
'bleed': 'auto | <len>',
'marks': 'none | [ crop || cross ]',
'size': '<len>{1,2} | auto | [ [ A3 | A4 | A5 | B4 | B5 | JIS-B4 | JIS-B5 | ' +
'ledger | legal | letter ] || [ portrait | landscape ] ]',
},
'property': {
'inherits': 'true | false',
'initial-value': 1,
'syntax': '<string>',
},
};
for (const [k, reps] of Object.entries({
'border': '{1,4}',
'border-bottom': '',
'border-left': '',
'border-right': '',
'border-top': '',
'border-block': '{1,2}',
'border-block-end': '',
'border-block-start': '',
'border-inline': '{1,2}',
'border-inline-end': '',
'border-inline-start': '',
})) {
Properties[k] = '<border-shorthand>';
Properties[`${k}-color`] = '<color>' + reps;
Properties[`${k}-style`] = '<border-style>' + reps;
Properties[`${k}-width`] = '<border-width>' + reps;
}
for (const k of ['width', 'height', 'block-size', 'inline-size']) {
Properties[`contain-intrinsic-${k}`] = '<contain-intrinsic>';
}
//#endregion
//#region Tokens & types
/**
* Based on https://www.w3.org/TR/css3-syntax/#lexical
* Each key is re-assigned to a sequential index, starting with EOF=0.
* Each value is converted into {name:string, text?:string} and stored as Tokens[index],
* e.g. AMP:'&' becomes AMP:1 and a new element is added at 1: {name:'AMP', text:'&'}.
*/
const Tokens = {
__proto__: null,
EOF: {}, // must be the first token
AMP: '&',
AT: {},
ATTR_EQ: ['|=', '~=', '^=', '*=', '$='],
CDCO: {}, // CDO and CDC
CHAR: {},
COLON: ':',
COMBINATOR: ['~', '||'], // "+" and ">" are also math ops
COMMA: ',',
COMMENT: {},
DELIM: '!',
DOT: '.',
EQUALS: '=',
EQ_CMP: ['>=', '<='],
FUNCTION: {},
GT: '>',
HASH: '#',
IDENT: {},
INVALID: {},
LBRACE: '{',
LBRACKET: '[',
LPAREN: '(',
MINUS: '-',
PIPE: '|',
PLUS: '+',
RBRACE: '}',
RBRACKET: ']',
RPAREN: ')',
SEMICOLON: ';',
STAR: '*',
STRING: {},
URANGE: {},
URI: {},
UVAR: {}, /*[[userstyles-org-variable]]*/
WS: {},
// numbers
ANGLE: {},
DIMENSION: {},
FLEX: {},
FREQUENCY: {},
LENGTH: {},
NUMBER: {},
PCT: {},
RESOLUTION: {},
TIME: {},
};
const TokenIdByCode = [];
for (let id = 0, arr = Object.keys(Tokens), key, val, text; (key = arr[id]); id++) {
text = ((val = Tokens[key]).slice ? val = {text: val} : val).text;
Tokens[val.name = key] = id;
Tokens[id] = val;
if (text) {
for (const str of typeof text === 'string' ? [text] : text) {
if (str.length === 1) TokenIdByCode[str.charCodeAt(0)] = id;
}
}
}
const {ANGLE, IDENT, LENGTH, NUMBER, PCT, STRING, TIME} = Tokens;
const Units = {__proto__: null};
const UnitTypeIds = {__proto__: null};
for (const [id, units] of [
[ANGLE, 'deg,grad,rad,turn'],
[Tokens.FLEX, 'fr'],
[Tokens.FREQUENCY, 'hz,khz'],
[LENGTH, 'cap,ch,em,ex,ic,lh,rlh,rem,' +
'cm,mm,in,pc,pt,px,q,' +
'cqw,cqh,cqi,cqb,cqmin,cqmax,' + // containers
'vb,vi,vh,vw,vmin,vmax' +
'dvb,dvi,dvh,dvw,dvmin,dvmax' +
'lvb,lvi,lvh,lvw,lvmin,lvmax' +
'svb,svi,svh,svw,svmin,svmax'],
[Tokens.RESOLUTION, 'dpcm,dpi,dppx,x'],
[TIME, 'ms,s'],
]) {
const type = Tokens[id].name.toLowerCase();
for (const u of units.split(',')) Units[u] = type;
UnitTypeIds[type] = id;
}
const Combinators = [];
/* \t */ Combinators[9] =
/* \n */ Combinators[10] =
/* \f */ Combinators[12] =
/* \r */ Combinators[13] =
/* " " */ Combinators[32] = 'descendant';
/* > */ Combinators[62] = 'child';
/* + */ Combinators[43] = 'adjacent-sibling';
/* ~ */ Combinators[126] = 'sibling';
/* || */ Combinators[124] = 'column';
/** Much faster than flat array or regexp */
class Bucket {
constructor(src) {
this.addFrom(src);
}
/**
* @param {string|string[]} src - length < 100
* @return {Bucket}
*/
addFrom(src) {
for (let str of typeof src === 'string' ? [src] : src) {
let c = (str = str.toLowerCase()).charCodeAt(0);
if (c === 34 /* " */) c = (str = str.slice(1, -1)).charCodeAt(0);
src = this[c = c * 100 + str.length];
if (src == null) this[c] = str;
else if (typeof src === 'string') this[c] = [src, str];
else src.push(str);
}
return this;
}
/** @return {string} */
join(sep) {
let res = '';
for (const v of Object.values(this)) {
res += `${res ? sep : ''}${typeof v === 'string' ? v : v.join(sep)}`;
}
return res;
}
/**
* @param {Token} tok
* @param {number} [c] - first char code
* @param {string} [lowText] - text to use instead of token's text
* @return {boolean | any}
*/
has(tok, c = tok.code, lowText) {
const len = (lowText || tok).length;
if (!isOwn(this, c = c * 100 + len)) return false;
if (len === 1) return true;
const val = this[c];
const low = lowText || tok.lowText || (tok.lowText = tok.text.toLowerCase());
return typeof val === 'string' ? val === low : val.includes(low);
}
}
/**
* CSS2 system colors: https://www.w3.org/TR/css3-color/#css2-system
* CSS4 system colors: https://drafts.csswg.org/css-color-4/#css-system-colors
*/
const NamedColors = ('currentColor,transparent,' +
'aliceblue,antiquewhite,aqua,aquamarine,azure,' +
'beige,bisque,black,blanchedalmond,blue,blueviolet,brown,burlywood,' +
'cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk,crimson,cyan,' +
'darkblue,darkcyan,darkgoldenrod,darkgray,darkgrey,darkgreen,darkkhaki,' +
'darkmagenta,darkolivegreen,darkorange,darkorchid,darkred,darksalmon,' +
'darkseagreen,darkslateblue,darkslategray,darkslategrey,darkturquoise,' +
'darkviolet,deeppink,deepskyblue,dimgray,dimgrey,dodgerblue,' +
'firebrick,floralwhite,forestgreen,fuchsia,' +
'gainsboro,ghostwhite,gold,goldenrod,gray,grey,green,greenyellow,' +
'honeydew,hotpink,indianred,indigo,ivory,khaki,' +
'lavender,lavenderblush,lawngreen,lemonchiffon,lightblue,lightcoral,lightcyan,' +
'lightgoldenrodyellow,lightgray,lightgrey,lightgreen,lightpink,lightsalmon,lightseagreen,' +
'lightskyblue,lightslategray,lightslategrey,lightsteelblue,lightyellow,lime,limegreen,linen,' +
'magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple,mediumseagreen,' +
'mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,' +
'midnightblue,mintcream,mistyrose,moccasin,navajowhite,navy,' +
'oldlace,olive,olivedrab,orange,orangered,orchid,' +
'palegoldenrod,palegreen,paleturquoise,palevioletred,' +
'papayawhip,peachpuff,peru,pink,plum,powderblue,purple,' +
'rebeccapurple,red,rosybrown,royalblue,' +
'saddlebrown,salmon,sandybrown,seagreen,seashell,sienna,silver,' +
'skyblue,slateblue,slategray,slategrey,snow,springgreen,steelblue,' +
'tan,teal,thistle,tomato,turquoise,violet,wheat,white,whitesmoke,yellow,yellowgreen,' +
'ActiveBorder,ActiveCaption,ActiveText,AppWorkspace,' +
'Background,ButtonBorder,ButtonFace,ButtonHighlight,ButtonShadow,ButtonText,' +
'Canvas,CanvasText,CaptionText,Field,FieldText,GrayText,Highlight,HighlightText,' +
'InactiveBorder,InactiveCaption,InactiveCaptionText,InfoBackground,InfoText,' +
'LinkText,Mark,MarkText,Menu,MenuText,Scrollbar,ThreeDDarkShadow,ThreeDFace,ThreeDHighlight,' +
'ThreeDLightShadow,ThreeDShadow,VisitedText,Window,WindowFrame,WindowText').split(',');
const buAlpha = new Bucket('alpha');
const buGlobalKeywords = new Bucket(GlobalKeywords);
const rxAltSep = /\s*\|\s*/;
//#endregion
//#region Grammar
const VTComplex = {
__proto__: null,
'<absolute-size>': 'xx-small | x-small | small | medium | large | x-large | xx-large',
'<alpha>': '/ <num-pct-none>',
'<animateable-feature>': 'scroll-position | contents | <animateable-feature-name>',
'<animation-direction>': 'normal | reverse | alternate | alternate-reverse',
'<animation-fill-mode>': 'none | forwards | backwards | both',
'<attachment>': 'scroll | fixed | local',
'<auto-repeat>':
'repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
'<auto-track-list>':
'[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>? <auto-repeat> ' +
'[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>?',
'<baseline-position>': '[ first | last ]? baseline',
'<basic-shape>':
'<inset> | ' +
'circle( <len-pct-side>? [ at <position> ]? ) | ' +
'ellipse( [ <len-pct-side>{2} ]? [ at <position> ]? ) | ' +
'path( [ [ nonzero | evenodd ] , ]? <string> ) | ' +
'polygon( [ [ nonzero | evenodd | inherit ] , ]? [ <len-pct> <len-pct> ]# )',
'<bg-image>': '<image> | none',
'<bg-layer>': '<bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || ' +
'<attachment> || <box>{1,2}',
'<bg-position>':
'[ center | [ left | right ] <len-pct>? ] && [ center | [ top | bottom ] <len-pct>? ] | ' +
'[ left | center | right | <len-pct> ] [ top | center | bottom | <len-pct> ] | ' +
'[ left | center | right | top | bottom | <len-pct> ]',
'<bg-size>': '[ <len-pct> | auto ]{1,2} | cover | contain',
'<blend-mode>': 'normal | multiply | screen | overlay | darken | lighten | color-dodge | ' +
'color-burn | hard-light | soft-light | difference | exclusion | hue | ' +
'saturation | color | luminosity | plus-darker | plus-lighter',
'<border-image-slice>': M => M.many([true],
// [<num> | <pct>]{1,4} && fill?
// but 'fill' can appear between any of the numbers
['<num-pct0+>', '<num-pct0+>', '<num-pct0+>', '<num-pct0+>', 'fill'].map(M.term)),
'<border-radius-round>': 'round <border-radius>',
'<border-shorthand>': '<border-width> || <border-style> || <color>',
'<border-style>':
'none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset',
'<border-width>': '<len> | thin | medium | thick',
'<box>': 'padding-box | border-box | content-box',
'<box-fsv>': 'fill-box | stroke-box | view-box',
'<color>': '<named-or-hex-color> | <fn:color>',
'<coord-box>': '<box> | <box-fsv>',
'<contain-intrinsic>': 'none | <len> | auto <len>',
'<content-distribution>': 'space-between | space-around | space-evenly | stretch',
'<content-list>':
'[ <string> | <image> | <attr> | ' +
'content( text | before | after | first-letter | marker ) | ' +
'counter() | counters() | leader() | ' +
'open-quote | close-quote | no-open-quote | no-close-quote | ' +
'target-counter() | target-counters() | target-text() ]+',
'<content-position>': 'center | start | end | flex-start | flex-end',
'<counter>': '[ <ident-not-none> <int>? ]+ | none',
'<dasharray>': M => M.alt([M.term('<len-pct0+>'), M.term('<num0+>')])
.braces(1, Infinity, '#', M.term(',').braces(0, 1, '?')),
'<display-box>': 'contents | none',
'<display-inside>': 'flow | flow-root | table | flex | grid | ruby',
'<display-internal>': 'table-row-group | table-header-group | table-footer-group | ' +
'table-row | table-cell | table-column-group | table-column | table-caption | ' +
'ruby-base | ruby-text | ruby-base-container | ruby-text-container',
'<display-legacy>': 'inline-block | inline-table | inline-flex | inline-grid',
'<display-listitem>': '<display-outside>? && [ flow | flow-root ]? && list-item',
'<display-outside>': 'block | inline | run-in',
'<explicit-track-list>': '[ <line-names>? <track-size> ]+ <line-names>?',
'<family-name>': '<string> | <custom-ident>+',
// https://drafts.fxtf.org/filter-effects/#supported-filter-functions
// Value may be omitted in which case the default is used
'<filter-function-list>': '[ <fn:filter> | <uri> ]+',
'<final-bg-layer>': '<color> || <bg-image> || <bg-position> [ / <bg-size> ]? || ' +
'<repeat-style> || <attachment> || <box>{1,2}',
'<fixed-repeat>': 'repeat( [ <int1+> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
'<fixed-size>':
'<len-pct> | minmax( <len-pct> , <track-breadth> | <inflexible-breadth> , <len-pct> )',
'<flex-direction>': 'row | row-reverse | column | column-reverse',
'<flex-shorthand>': 'none | [ <num>{1,2} || <width> ]',
'<flex-wrap>': 'nowrap | wrap | wrap-reverse',
'<font-short-core>': '<font-size> [ / <line-height> ]? <font-family>',
'<font-short-tweak-no-pct>':
'<font-style> || [ normal | small-caps ] || <font-weight> || <font-stretch-named>',
'<font-stretch-named>': 'normal | ultra-condensed | extra-condensed | condensed | ' +
'semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded',
'<font-variant-alternates>': 'stylistic() || historical-forms || styleset() || ' +
'character-variant() || swash() || ornaments() || annotation()',
'<font-variant-caps>':
'small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps',
'<font-variant-east-asian>': '[ jis78|jis83|jis90|jis04|simplified|traditional ] || ' +
'[ full-width | proportional-width ] || ruby',
'<font-variant-ligatures>': '[ common-ligatures | no-common-ligatures ] || ' +
'[ discretionary-ligatures | no-discretionary-ligatures ] || ' +
'[ historical-ligatures | no-historical-ligatures ] || ' +
'[ contextual | no-contextual ]',
'<font-variant-numeric>': '[ lining-nums | oldstyle-nums ] || ' +
'[ proportional-nums | tabular-nums ] || ' +
'[ diagonal-fractions | stacked-fractions ] || ' +
'ordinal || slashed-zero',
'<generic-family>': 'serif | sans-serif | cursive | fantasy | monospace | system-ui | ' +
'emoji | math | fangsong | ui-serif | ui-sans-serif | ui-monospace | ui-rounded',
'<geometry-box>': '<shape-box> | <box-fsv>',
'<gradient>': 'radial-gradient() | linear-gradient() | conic-gradient() | gradient() | ' +
'repeating-radial-gradient() | repeating-linear-gradient() | repeating-conic-gradient() | ' +
'repeating-gradient()',
'<grid-line>': 'auto | [ <int> && <ident-for-grid>? ] | <ident-for-grid> | ' +
'[ span && [ <int> || <ident-for-grid> ] ]',
'<image>': '<uri> | <gradient> | -webkit-cross-fade()',
'<inflexible-breadth>': '<len-pct> | min-content | max-content | auto',
'<inset>': 'inset( <len-pct>{1,4} <border-radius-round>? )',
'<len-pct-side>': '<len-pct> | closest-side | farthest-side',
'<line-height>': '<num> | <len-pct> | normal',
'<line-names>': '"[" <ident-for-grid> "]"',
'<overflow-position>': 'unsafe | safe',
'<overflow>': '<vis-hid> | clip | scroll | auto | overlay', // TODO: warning about `overlay`
'<overscroll>': 'contain | none | auto',
'<paint>': 'none | <color> | <uri> [ none | <color> ]? | context-fill | context-stroke',
// Because our `alt` combinator is ordered, we need to test these
// in order from longest possible match to shortest.
'<position>':
'[ [ left | right ] <len-pct> ] && [ [ top | bottom ] <len-pct> ] | ' +
'[ left | center | right | <len-pct> ] ' +
'[ top | center | bottom | <len-pct> ]? | ' +
'[ left | center | right ] || [ top | center | bottom ]',
'<ratio>': '<num0+> [ / <num0+> ]?',
'<rect>': 'rect( [ <len> | auto ]#{4} <border-radius-round>? )',
'<relative-size>': 'smaller | larger',
'<repeat-style>': 'repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}',
'<rgb-xyz>': 'srgb|srgb-linear|display-p3|a98-rgb|prophoto-rgb|rec2020|xyz|xyz-d50|xyz-d65',
'<self-position>': 'center | start | end | self-start | self-end | flex-start | flex-end',
'<shadow>': 'inset? && [ <len>{2,4} && <color>? ]',
'<shape-box>': '<box> | margin-box',
'<timing-function>': 'linear|ease|ease-in|ease-out|ease-in-out|step-start|step-end | ' +
'cubic-bezier( <num0-1> , <num> , <num0-1> , <num> ) | ' +
'steps( <int> [ , [ jump-start | jump-end | jump-none | jump-both | start | end ] ]? )',
'<text-align>': 'start | end | left | right | center | justify | match-parent',
'<track-breadth>': '<len-pct> | <flex> | min-content | max-content | auto',
'<track-list>': '[ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?',
'<track-repeat>': 'repeat( [ <int1+> ] , [ <line-names>? <track-size> ]+ <line-names>? )',
'<track-size>': '<track-breadth> | minmax( <inflexible-breadth> , <track-breadth> ) | ' +
'fit-content( <len-pct> )',
'<vis-hid>': 'visible | hidden',
'<width-height>': '<len-pct> | min-content | max-content | fit-content | ' +
'-moz-available | -webkit-fill-available | fit-content( <len-pct> )',
'<xywh>': 'xywh( <len-pct>{2} <len-pct0+>{2} <border-radius-round>? )',
};
const VTFunctions = {
color: {
__proto__: null,
'color-mix': 'in [ srgb | srgb-linear | lab | oklab | xyz | xyz-d50 | xyz-d65 ' +
'| [ hsl | hwb | lch | oklch ] [ [ shorter | longer | increasing | decreasing ] hue ]? ' +
'] , [ <color> && <pct0-100>? ]#{2}',
'color': 'from <color> [ ' +
'<custom-prop> [ <num-pct-none> <custom-ident> ]# | ' +
'<rgb-xyz> [ <num-pct-none> | r | g | b | x | y | z ]{3} ' +
'] [ / <num-pct-none> | r | g | b | x | y | z ]? | ' +
'[ <rgb-xyz> <num-pct-none>{3} | <custom-prop> <num-pct-none># ] <alpha>?',
'hsl': '<hue> , <pct>#{2} [ , <num-pct0+> ]? | ' +
'[ <hue> | none ] <num-pct-none>{2} <alpha>? | ' +
'from <color> [ <hue> | <rel-hsl> ] <rel-hsl-num-pct>{2} [ / <rel-hsl-num-pct> ]?',
'hwb': '[ <hue> | none ] <num-pct-none>{2} <alpha>? | ' +
'from <color> [ <hue> | <rel-hwb> ] <rel-hwb-num-pct>{2} [ / <rel-hwb-num-pct> ]?',
'lab': '<num-pct-none>{3} <alpha>? | ' +
'from <color> <rel-lab-num-pct>{3} [ / <rel-lab-num-pct> ]?',
'lch': '<num-pct-none>{2} [ <hue> | none ] <alpha>? | ' +
'from <color> <rel-lch-num-pct>{2} [ <hue> | <rel-lch> ] [ / <rel-lch-num-pct> ]?',
'rgb': '[ <num>#{3} | <pct>#{3} ] [ , <num-pct0+> ]? | ' +
'<num-pct-none>{3} <alpha>? | ' +
'from <color> <rel-rgb-num-pct>{3} [ / <rel-rgb-num-pct> ]?',
},
filter: {
__proto__: null,
'blur': '<len>?',
'brightness': '<num-pct>?',
'contrast': '<num-pct>?',
'drop-shadow': '[ <len>{2,3} && <color>? ]?',
'grayscale': '<num-pct>?',
'hue-rotate': '<angle-or-0>?',
'invert': '<num-pct>?',
'opacity': '<num-pct>?',
'saturate': '<num-pct>?',
'sepia': '<num-pct>?',
},
transform: {
__proto__: null,
matrix: '<num>#{6}',
matrix3d: '<num>#{16}',
perspective: '<len0+> | none',
rotate: '<angle-or-0> | none',
rotate3d: '<num>#{3} , <angle-or-0>',
rotateX: '<angle-or-0>',
rotateY: '<angle-or-0>',
rotateZ: '<angle-or-0>',
scale: '[ <num-pct> ]#{1,2} | none',
scale3d: '<num-pct>#{3}',
scaleX: '<num-pct>',
scaleY: '<num-pct>',
scaleZ: '<num-pct>',
skew: '<angle-or-0> [ , <angle-or-0> ]?',
skewX: '<angle-or-0>',
skewY: '<angle-or-0>',
translate: '<len-pct>#{1,2} | none',
translate3d: '<len-pct>#{2} , <len>',
translateX: '<len-pct>',
translateY: '<len-pct>',
translateZ: '<len>',
},
};
{
let obj = VTFunctions.color;
for (const k of ['hsl', 'rgb']) obj[k + 'a'] = obj[k];
for (const k of ['lab', 'lch']) obj['ok' + k] = obj[k];
obj = VTFunctions.transform;
for (const key in obj) {
const low = key.toLowerCase();
if (low !== key) Object.defineProperty(obj, low, {value: obj[key], writable: true});
}
}
const VTSimple = {
__proto__: null,
'<animateable-feature-name>': customIdentChecker('will-change,auto,scroll-position,contents'),
'<angle>': p => p.isCalc || p.id === ANGLE,
'<angle-or-0>': p => p.isCalc || p.is0 || p.id === ANGLE,
'<ascii4>': p => p.id === STRING && p.length === 4 && !/[^\x20-\x7E]/.test(p.text),
'<attr>': p => p.isAttr,
'<custom-ident>': p => p.id === IDENT && !buGlobalKeywords.has(p),
'<custom-prop>': p => p.type === 'custom-prop',
'<flex>': p => p.isCalc || p.units === 'fr' && p.number >= 0,
'<hue>': p => p.isCalc || p.id === NUMBER || p.id === ANGLE,
'<ident-for-grid>': customIdentChecker('span,auto'),
'<ident-not-none>': p => p.id === IDENT && !p.isNone,
'<ie-function>': p => p.ie,
'<int>': p => p.isCalc || p.isInt,
'<int0-1>': p => p.isCalc || p.is0 || p.isInt && p.number === 1,
'<int0+>': p => p.isCalc || p.isInt && p.number >= 0,
'<int1+>': p => p.isCalc || p.isInt && p.number > 0,
'<int2-4>': p => p.isCalc || p.isInt && (p = p.number) >= 2 && p <= 4,
'<len>': p => p.isCalc || p.is0 || p.id === LENGTH,
'<len0+>': p => p.isCalc || p.is0 || p.id === LENGTH && p.number >= 0,
'<len-pct>': p => p.isCalc || p.is0 || p.id === LENGTH || p.id === PCT,
'<len-pct0+>': p => p.isCalc || p.is0 || p.number >= 0 && (p.id === PCT || p.id === LENGTH),
'<named-or-hex-color>': p => p.type === 'color',
'<num>': p => p.isCalc || p.id === NUMBER,
'<num0+>': p => p.isCalc || p.id === NUMBER && p.number >= 0,
'<num0-1>': p => p.isCalc || p.id === NUMBER && (p = p.number) >= 0 && p <= 1,
'<num1-1000>': p => p.isCalc || p.id === NUMBER && (p = p.number) >= 1 && p <= 1000,
'<num-pct>': p => p.isCalc || p.id === NUMBER || p.id === PCT,
'<num-pct0+>': p => p.isCalc || p.number >= 0 && (p.id === NUMBER || p.id === PCT),
'<num-pct-none>': p => p.isCalc || p.isNone || p.id === NUMBER || p.id === PCT,
'<pct>': p => p.isCalc || p.is0 || p.id === PCT,
'<pct0+>': p => p.isCalc || p.is0 || p.number >= 0 && p.id === PCT,
'<pct0-100>': p => p.isCalc || p.is0 || p.id === PCT && (p = p.number) >= 0 && p <= 100,
'<keyframes-name>': customIdentChecker('', p => p.id === STRING),
'<resolution>': p => p.id === Tokens.RESOLUTION,
'<string>': p => p.id === STRING,
'<time>': p => p.isCalc || p.id === TIME,
'<unicode-range>': p => p.id === Tokens.URANGE,
'<uri>': p => p.uri != null,
'<width>': p => p.isAuto || p.isCalc || p.is0 || p.id === LENGTH || p.id === PCT,
};
for (const type of ['hsl', 'hwb', 'lab', 'lch', 'rgb']) {
const letters = {};
for (let i = 0; i < type.length;) letters[type.charCodeAt(i++)] = 1;
VTSimple[`<rel-${type}>`] = p => p.isNone
|| (p.length === 1 ? isOwn(letters, p.code) : p.length === 5 && buAlpha.has(p));
VTSimple[`<rel-${type}-num-pct>`] = p => p.isNone
|| p.isCalc || p.id === NUMBER || p.id === PCT
|| (p.length === 1 ? isOwn(letters, p.code) : p.length === 5 && buAlpha.has(p));
}
//#endregion
//#region StringSource
class StringSource {
constructor(text) {
// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
this._break = (
this.string = text.replace(/\r\n?|\f/g, '\n')
).indexOf('\n');
this.line = 1;
this.col = 1;
this.offset = 0;
}
eof() {
return this.offset >= this.string.length;
}
/** @return {number} */
peek(distance = 1) {
return this.string.charCodeAt(this.offset + distance - 1);
}
mark() {
this._bookmark = {o: this.offset, l: this.line, c: this.col, b: this._break};
}
reset() {
const b = this._bookmark;
if (b) {
({o: this.offset, l: this.line, c: this.col, b: this._break} = b);
this._bookmark = null;
}
}
/**
* Reads characters that match either text or a regular expression and returns those characters.
* If a match is found, the row and column are adjusted.
* @param {RegExp} m - must be `sticky`
* @param {boolean} [asRe]
* @return {string|RegExpExecArray|void}
*/
readMatch(m, asRe) {
const res = (m.lastIndex = this.offset, m.exec(this.string));
if (res) return (m = res[0]) && this.read(m.length, m) && (asRe ? res : m);
}
/** @param {number} code */
readMatchCode(code) {
if (code === this.string.charCodeAt(this.offset)) {
return this.read();
}
}
/** @param {string} m */
readMatchStr(m) {
const len = m.length;
const {offset: i, string: str} = this;
if (!len || str.charCodeAt(i) === m.charCodeAt(0) && (
len === 1 ||
str.charCodeAt(i + len - 1) === m.charCodeAt(len - 1) && str.substr(i, len) === m
)) {
return m && this.read(len, m);
}
}
/**
* Reads a given number of characters. If the end of the input is reached,
* it reads only the remaining characters and does not throw an error.
* @param {number} count The number of characters to read.
* @param {string} [text] Use an already extracted text and only increment the cursor
* @return {string}
*/
read(count = 1, text) {
let {offset: i, _break: br, string} = this;
if (count <= 0 || text == null && !(text = string.substr(i, count))) return '';
this.offset = i += (count = text.length); // may be less than requested
if (i <= br || br < 0) {
this.col += count;
} else {
let brPrev;
let {line} = this;
do ++line; while ((br = string.indexOf('\n', (brPrev = br) + 1)) >= 0 && br < i);
this._break = br;
this.line = line;
this.col = i - brPrev;
}
return text;
}
/** @return {number|undefined} */
readCode() {
const c = this.string.charCodeAt(this.offset++);
if (c === 10) {
this.col = 1;
this.line++;
this._break = this.string.indexOf('\n', this.offset);
} else if (c >= 0) { // fast NaN check
this.col++;
} else {
this.offset--; // restore EOF
return;
}
return c;
}
}
//#endregion
//#region EventTarget
class EventTarget {
constructor() {
/** @type {Record<string,Set>} */
this._listeners = {__proto__: null};
}
addListener(type, fn) {
(this._listeners[type] || (this._listeners[type] = new Set())).add(fn);
}
fire(event) {
const type = typeof event === 'object' && event.type;
const list = this._listeners[type || event];
if (!list) return;
if (!type) event = {type};
list.forEach(fn => fn(event));
}
removeListener(type, fn) {
const list = this._listeners[type];
if (list) list.delete(fn);
}
}
//#endregion
//#region Matcher
const rxAndAndSep = /\s*&&\s*/y;
const rxBraces = /{\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?}/y; // {n,} = {n,Infinity}
const rxFuncBegin = /([-\w]+)\(\s*(\))?/y;
const rxFuncEnd = /\s*\)/y;
const rxGroupBegin = /\[\s*/y;
const rxGroupEnd = /\s*]/y;
const rxOrOrSep = /\s*\|\|\s*/y;
const rxOrSep = /\s*\|(?!\|)\s*/y;
const rxPlainTextAlt = /[-\w]+(?:\s*\|\s*[-\w]+)*(?=\s*\|(?!\|)\s*|\s*]|\s+\)|\s*$)/y;
const rxSeqSep = /\s+(?![&|)\]])/y;
const rxTerm = /<[^>\s]+>|"[^"]*"|'[^']*'|[^\s?*+#{}()[\]|&]+/y;
/**
* This class implements a combinator library for matcher functions.
* https://developer.mozilla.org/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
*/
class Matcher {
/**
* @param {(this: Matcher, expr: PropValueIterator, p?: Token) => boolean} matchFunc
* @param {string|function} toString
* @param {?} [arg]
* @param {boolean} [isMeta] - true for alt/seq/many/braces that control matchers
*/
constructor(matchFunc, toString, arg, isMeta) {
this.matchFunc = matchFunc;
if (arg != null) this.arg = arg;
if (isMeta) this.isMeta = isMeta;
if (toString.call) this.toString = toString; else this._string = toString;
}
/**
* @param {PropValueIterator} expr
* @param {Token} [p]
* @return {boolean}
*/
match(expr, p) {
const {i} = expr; if (!p && !(p = expr.parts[i])) return this.arg.min === 0;
const isMeta = this.isMeta;
const res = !isMeta && p.isVar ||
this.matchFunc(expr, p) ||
!isMeta && expr.tryAttr && p.isAttr;
if (!res) expr.i = i;
else if (!isMeta && expr.i < expr.parts.length) ++expr.i;
return res;
}
toString() {
return this._string;
}
/** Matcher for one or more juxtaposed words, which all must occur, in the given order. */
static alt(ms) {
let str; // Merging stringArray hubs
for (let SAT = Matcher.stringArrTest, m, i = 0; (m = ms[i]);) {
if (m.matchFunc === SAT) {
str = (str ? str + ' | ' : '') + m._string;
ms.splice(i, 1);
} else i++;
}
if (str) ms.unshift(Matcher.term(str));
return !ms[1] ? ms[0] : new Matcher(Matcher.altTest, Matcher.altToStr, ms, true);
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean|void}
*/
static altTest(expr, p) {
for (let i = 0, m; (m = this.arg[i++]);) {
if (m.match(expr, p)) return true;
}
}
/** @this {Matcher} */
static altToStr(prec) {
return (prec = prec > Matcher.ALT ? '[ ' : '') +
this.arg.map(m => m.toString(Matcher.ALT)).join(' | ') +
(prec ? ' ]' : '');
}
braces(min, max, marker, sep) {
return new Matcher(Matcher.bracesTest, Matcher.bracesToStr, {
m: this,
min, max, marker,
sep: sep && Matcher.seq([sep.matchFunc ? sep : Matcher.term(sep), this]),
}, true);
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean|number}
*/
static bracesTest(expr, p) {
let i = 0;
const {min, max, sep, m} = this.arg;
while (i < max && (i && sep || m).match(expr, p)) {
p = undefined; // clearing because expr points to the next part now
i++;
}
return i >= min && (i || true);
}
/** @this {Matcher} */
static bracesToStr() {
const {marker, min, max, m} = this.arg;
return m.toString(Matcher.MOD) + (marker || '') + (
!marker || marker === '#' && !(min === 1 || max === Infinity)
? `{${min}${min === max ? '' : `,${max === Infinity ? '' : max}`}}`
: '');
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean|number|void}
*/
static funcTest(expr, p) {
const pn = p.name; if (!pn) return;
const pnv = (p.prefix || '') + pn;
const {name, body, list} = this.arg;
const m = list ? list[pn] || list[pnv]
: name === pn || name === pnv ? (body || '')
: null; if (m == null) return;
const e = p.expr; if (!e && m) return;
const vi = m && !e.isVar && new PropValueIterator(e); // eslint-disable-line no-use-before-define
const mm = !vi || m.matchFunc ? m :
list[pn] = (m.call ? m(Matcher) : Matcher.cache[m] || Matcher.parse(m));
return !vi || mm.match(vi) && vi.i >= vi.parts.length || !(expr.badFunc = [e, mm]);
}
/** @this {Matcher} */
static funcToStr(prec) {
const {name, body, list} = this.arg;
return name ? `${name}(${body || ''})` :
(prec = prec > Matcher.ALT ? '[ ' : '') +
Object.keys(list).join('() | ') +
(prec ? '() ]' : '()');
}
static many(req, ms) {
if (!ms[1]) return ms[0];
const m = new Matcher(Matcher.manyTest, Matcher.manyToStr, ms, true);
m.req = req === true ? Array(ms.length).fill(true) :
req == null ? ms.map(m => !m.arg || m.arg.marker !== '?')
: req;
return m;
}
/**
* Matcher for two or more options: double bar (||) and double ampersand (&&) operators,
* as well as variants of && where some of the alternatives are optional.
* This will backtrack through even successful matches to try to
* maximize the number of items matched.
* @this {Matcher}
* @param {PropValueIterator} expr
* @return {!boolean}
*/
static manyTest(expr) {
const state = [];
state.expr = expr;
state.max = 0;
// If couldn't get a complete match, retrace our steps to make the
// match with the maximum # of required elements.
if (!this.manyTestRun(state, 0)) this.manyTestRun(state, 0, true);
if (!this.req) return state.max > 0;
// Use finer-grained specification of which matchers are required.
for (let i = 0; i < this.req.length; i++) {
if (this.req[i] && !state[i]) return false;
}
return true;
}
manyTestRun(state, count, retry) {
for (let i = 0, {expr} = state, ms = this.arg, m, ei, x; (m = ms[i]); i++) {
if (!state[i] && (
(ei = expr.i) + 1 > expr.parts.length ||
(x = m.match(expr)) && (x > 1 || x === 1 || m.arg.min !== 0)
// Seeing only real matches e.g. <foo> inside <foo>? or <foo>* or <foo>#{0,n}
// Not using `>=` because `true>=1` and we don't want booleans here
)) {
state[i] = true;
if (this.manyTestRun(state, count + (!this.req || this.req[i] ? 1 : 0), retry)) {
return true;
}
state[i] = false;
expr.i = ei;
}
}
if (retry) return count === state.max;
state.max = Math.max(state.max, count);
return count === this.arg.length;
}
/** @this {Matcher} */
static manyToStr(prec) {
const {req} = this;
const p = Matcher[req ? 'ANDAND' : 'OROR'];
const s = this.arg.map((m, i) =>
!req || req[i]
? m.toString(p)
: m.toString(Matcher.MOD).replace(/[^?]$/, '$&?')
).join(req ? ' && ' : ' || ');
return prec > p ? `[ ${s} ]` : s;
}
/** Simple recursive-descent parseAlt to build matchers from strings. */
static parse(str) {
const source = new StringSource(str);
const res = Matcher.parseAlt(source);
if (!source.eof()) {
const {offset: i, string} = source;
throw new Error(`Internal grammar error. Unexpected "${
clipString(string.slice(i, 31), 30)}" at position ${i} in "${string}".`);
}
Matcher.cache[str] = res;
return res;
}
/**
* ALT: OROR [ " | " OROR ]* (exactly one matches)
* OROR: ANDAND [ " || " ANDAND ]* (at least one matches in any order)
* ANDAND: SEQ [ " && " SEQ ]* (all match in any order)
* SEQ: TERM [" " TERM]* (all match in specified order)
* TERM: [ "<" type ">" | literal | "[ " ALT " ]" | fn "()" | fn "( " ALT " )" ] MOD?
* MOD: "?" | "*" | "+" | "#" | [ "{" | "#{" ] <num>[,[<num>]?]? "}" ]
* The specified literal spaces like " | " are optional except " " in SEQ (i.e. \s+)
* @param {StringSource} src
* @return {Matcher}
*/
static parseAlt(src) {
const alts = [];
do {
const pt = src.readMatch(rxPlainTextAlt);
if (pt) {
alts.push(Matcher.term(pt));
} else {
const ors = [];
do {
const ands = [];
do {
const seq = [];
do seq.push(Matcher.parseTerm(src));
while (src.readMatch(rxSeqSep));
ands.push(Matcher.seq(seq));
} while (src.readMatch(rxAndAndSep));
ors.push(Matcher.many(null, ands));
} while (src.readMatch(rxOrOrSep));
alts.push(Matcher.many(false, ors));
}
} while (src.readMatch(rxOrSep));
return Matcher.alt(alts);
}
/**
* @param {StringSource} src
* @return {Matcher}
*/
static parseTerm(src) {
let m, fn;
if (src.readMatch(rxGroupBegin)) {
m = Matcher.parseAlt(src);
if (!src.readMatch(rxGroupEnd)) Matcher.parsingFailed(src, rxGroupEnd);
} else if ((fn = src.readMatch(rxFuncBegin, true))) {
m = new Matcher(Matcher.funcTest, Matcher.funcToStr, {
name: fn[1].toLowerCase(),
body: !fn[2] && Matcher.parseAlt(src),
});
if (!fn[2] && !src.readMatch(rxFuncEnd)) Matcher.parsingFailed(src, rxFuncEnd);
} else {
m = Matcher.term(src.readMatch(rxTerm) || Matcher.parsingFailed(src, rxTerm));
}
fn = src.peek();
if (fn === 123/* { */ || fn === 35/* # */ && src.peek(2) === 123) {
const hash = fn === 35 ? src.read() : '';
const [, a, comma, b = comma ? Infinity : a] = src.readMatch(rxBraces, true)
|| Matcher.parsingFailed(src, rxBraces);
return m.braces(+a, +b, hash, hash && ',');
}
switch (fn) {
case 63: /* ? */ return m.braces(0, 1, src.read());
case 42: /* * */ return m.braces(0, Infinity, src.read());
case 43: /* + */ return m.braces(1, Infinity, src.read());
case 35: /* # */ return m.braces(1, Infinity, src.read(), ',');
}
return m;
}
/**
* @param {StringSource} src
* @param {RegExp|string} m
* @throws
*/
static parsingFailed(src, m) {
throw new Error('Internal grammar error. ' +
`Expected ${m} at ${src.offset} in ${src.string}`);
}
static seq(ms) {
return !ms[1] ? ms[0] : new Matcher(Matcher.seqTest, Matcher.seqToStr, ms, true);
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean|void}
*/
static seqTest(expr, p) {
let min1, i, m, res;
for (i = 0; (m = this.arg[i++]); p = undefined) {
if (!(res = m.match(expr, p))) return;
if (!min1 && (m.arg.min !== 0 || res === 1 || res > 1)) min1 = true;
// a number >= 1 is returned only from bracesTest
}
return true;
}
/** @this {Matcher} */
static seqToStr(prec) {
return (prec = prec > Matcher.SEQ ? '[ ' : '') +
this.arg.map(m => m.toString(Matcher.SEQ)).join(' ') +
(prec ? ' ]' : '');
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean}
*/
static simpleTest(expr, p) {
return !!this.arg(p);
}
/**
* @this {Matcher}
* @param {PropValueIterator} expr
* @param {Token} p
* @return {!boolean|void}
*/
static stringArrTest(expr, p) {
// If the bucket has -vendor-prefixed-text we will use the token as-is without unprefixing it
return this.arg.has(p) || p.vendorCode &&
(expr = this.arg[p.vendorCode * 100 + p.length - p.vendorPos]) &&
(p = p.text.slice(p.vendorPos).toLowerCase()) &&
(typeof expr === 'string' ? expr === p : expr.includes(p));
}
/** @this {Matcher} */
static stringArrToStr(prec) {
return (prec = prec > Matcher.ALT && this._string.includes(' ') ? '[ ' : '') +
this._string + (prec ? ' ]' : '');
}
/** Matcher for a single type */
static term(str) {
let m = Matcher.cache[str = str.toLowerCase()]; if (m) return m;
if (str[0] !== '<') {
m = new Matcher(Matcher.stringArrTest, Matcher.stringArrToStr,
new Bucket(str.split(rxAltSep)));
m._string = str;
} else if (str.startsWith('<fn:')) {
m = new Matcher(Matcher.funcTest, Matcher.funcToStr, {list: VTFunctions[str.slice(4, -1)]});
} else if ((m = VTSimple[str])) {
m = new Matcher(Matcher.simpleTest, str, m);
} else {
m = VTComplex[str] || Properties[str.slice(1, -1)];
m = m.matchFunc ? m : m.call ? m(Matcher) : Matcher.cache[m] || Matcher.parse(m);
}
Matcher.cache[str] = m;
return m;
}
}
/** @type {{[key:string]: Matcher}} */
Matcher.cache = {__proto__:null};
// Precedence of combinators.
Matcher.MOD = 5;
Matcher.SEQ = 4;
Matcher.ANDAND = 3;
Matcher.OROR = 2;
Matcher.ALT = 1;
//#endregion
//#region Validation
const validationCache = new Map();
/** @property {Array} [badFunc] */
class PropValueIterator {
/** @param {TokenValue} value */
constructor(value) {
this.i = 0;
this.parts = value.parts;
this.value = value;
}
get hasNext() {
return this.i + 1 < this.parts.length;
}
/** @returns {?Token} */
next() {
if (this.i < this.parts.length) return this.parts[++this.i];
}
}
class ValidationError extends Error {
constructor(message, pos) {
super();
this.col = pos.col;
this.line = pos.line;
this.offset = pos.offset;
this.message = message;
}
}
/**
* @param {Token} tok
* @param {TokenValue} value
* @param {TokenStream} stream
* @param {string|Object} Props
* @return {ValidationError|void}
*/
function validateProperty(tok, value, stream, Props) {
const pp = value.parts;
const p0 = pp[0];
if (p0.type === 'ident' && buGlobalKeywords.has(p0)) {
return pp[1] && vtFailure(pp[1], true);
}
Props = typeof Props === 'string' ? ScopedProperties[Props] : Props || Properties;
let spec, res, vp;
let prop = tok.lowText || tok.text.toLowerCase();
do spec = Props[prop] || Props['<all>'] && (Props = Properties)[prop];
while (!spec && !res && (vp = tok.vendorPos) && (res = prop = prop.slice(vp)));
if (typeof spec === 'number' || !spec && vp) {
return;
}
if (!spec) {
prop = Props === Properties || !Properties[prop] ? 'Unknown' : 'Misplaced';
return new ValidationError(`${prop} property "${tok}".`, tok);
}
if (value.isVar) {
return;
}
const valueSrc = value.text.trim();
let known = validationCache.get(prop);
if (known && known.has(valueSrc)) {
return;
}
// Property-specific validation.
const expr = new PropValueIterator(value);
let m = Matcher.cache[spec] || Matcher.parse(spec);
res = m.match(expr, p0);
if ((!res || expr.hasNext) && /\battr\(/i.test(valueSrc)) {
if (!res) {
expr.i = 0;
expr.tryAttr = true;
res = m.match(expr);
}
for (let p; (p = expr.parts[expr.i]) && p.isAttr;) {
expr.next();
}
}
if (expr.hasNext && (res || expr.i)) return vtFailure(expr.parts[expr.i]);
if (!res && (m = expr.badFunc)) return vtFailure(m[0], vtDescribe(spec, m[1]));
if (!res) return vtFailure(expr.value, vtDescribe(spec));
if (!known) validationCache.set(prop, (known = new Set()));
known.add(valueSrc);
}
function vtDescribe(type, m) {
if (!m) m = VTComplex[type] || type[0] === '<' && Properties[type.slice(1, -1)];
return m instanceof Matcher ? m.toString(0) : vtExplode(m || type);
}
function vtExplode(text) {
return !text.includes('<') ? text
: (Matcher.cache[text] || Matcher.parse(text)).toString(0);
}
function vtFailure(unit, what) {
if (!what || what === true ? (what = 'end of value') : !unit.isVar) {
return new ValidationError(`Expected ${what} but found "${clipString(unit)}".`, unit);
}
}
//#endregion
function clipString(s, len = 30) {
return (s = `${s}`).length > len ? s.slice(0, len) + '...' : s;
}
function customIdentChecker(str = '', alt) {
const b = new Bucket(GlobalKeywords);
if (str) b.addFrom(str.split(','));
return p => p.id === IDENT && !b.has(p) || alt && alt(p);
}
/** @namespace parserlib */
const parserlib = {
css: {
Combinators,
GlobalKeywords,
NamedColors,
Properties,
ScopedProperties,
Tokens,
Units,
},
util: {
Bucket,
EventTarget,
Matcher,
StringSource,
TokenIdByCode,
VTComplex,
VTFunctions,
VTSimple,
UnitTypeIds,
clipString,
describeProp: vtExplode,
isOwn,
pick,
validateProperty,
},
};
if (typeof self !== 'undefined') self.parserlib = parserlib;
else module.exports = parserlib; // eslint-disable-line no-undef
})();