umberto 10.2.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +9 -7
  2. package/package.json +1 -1
  3. package/src/api-builder/classes/doc-data-factory.js +10 -2
  4. package/src/data-converter/converters/typedoc/abstractparser.js +14 -0
  5. package/src/data-converter/converters/typedoc/accessorparser.js +1 -0
  6. package/src/data-converter/converters/typedoc/classparser.js +1 -0
  7. package/src/data-converter/converters/typedoc/constantparser.js +1 -0
  8. package/src/data-converter/converters/typedoc/eventparser.js +1 -0
  9. package/src/data-converter/converters/typedoc/functionparser.js +1 -0
  10. package/src/data-converter/converters/typedoc/interfaceparser.js +1 -0
  11. package/src/data-converter/converters/typedoc/methodparser.js +1 -0
  12. package/src/data-converter/converters/typedoc/propertyparser.js +1 -0
  13. package/src/data-converter/converters/typedoc/typeparser.js +1 -0
  14. package/src/helpers/create-filtering-data-attribs.js +4 -0
  15. package/themes/umberto/layout/gloria/_api-docs/_header/index.pug +2 -0
  16. package/themes/umberto/layout/gloria/_api-docs/_mixin/_badge-tag.pug +6 -0
  17. package/themes/umberto/layout/gloria/_api-docs/_mixin/_class-item.pug +2 -4
  18. package/themes/umberto/layout/gloria/_api-docs/_mixin/_method.pug +2 -4
  19. package/themes/umberto/layout/gloria/_api-docs/_mixin/_property.pug +2 -5
  20. package/themes/umberto/layout/gloria/_api-docs/_partial/filter.pug +4 -0
  21. package/themes/umberto/layout/gloria/_api-docs/_subheader/_style.scss +7 -0
  22. package/themes/umberto/layout/gloria/_api-docs/_subheader/index.pug +4 -0
  23. package/themes/umberto/layout/gloria/_components/tag/_style.scss +12 -0
  24. package/themes/umberto/layout/gloria/theme.pug +2 -0
  25. package/themes/umberto/layout/umberto/_api-docs/_partial/filter.pug +4 -0
  26. package/themes/umberto/src/gloria/js/modules/api-filter.js +19 -10
  27. package/themes/umberto/src/umberto/css/_badge.scss +1 -0
  28. package/themes/umberto/src/umberto/css/helpers/_color.scss +1 -0
  29. package/themes/umberto/src/umberto/js/_filtering.js +1 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## [10.3.0](https://github.com/cksource/umberto/compare/v10.2.0...v10.3.0) (April 3, 2026)
5
+
6
+ ### Features
7
+
8
+ * Added support for displaying and filtering experimental API items in TypeDoc-based API docs.
9
+
10
+ Experimental annotations now appear in page headers and member listings, and can be toggled with the API filters.
11
+
12
+
4
13
  ## [10.2.0](https://github.com/cksource/umberto/compare/v10.1.4...v10.2.0) (April 2, 2026)
5
14
 
6
15
  ### Features
@@ -38,13 +47,6 @@ Changelog
38
47
 
39
48
  * Removed unmaintained `hexo-renderer-markdown-it-plus` and `@ckeditor/jsdoc-plugins` dependencies by replacing them with maintained and local implementations, resolving the reported `markdown-it`-related vulnerabilities in Umberto's dependency tree.
40
49
 
41
-
42
- ## [10.1.1](https://github.com/cksource/umberto/compare/v10.1.0...v10.1.1) (March 4, 2026)
43
-
44
- ### Bug fixes
45
-
46
- * Fixed a deprecation warning displayed during documentation builds by migrating Sass compilation to the modern Dart Sass JavaScript API. The `legacy-js-api` warning is no longer emitted.
47
-
48
50
  ---
49
51
 
50
52
  To see all releases, visit the [release page](https://github.com/cksource/umberto/releases).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -62,6 +62,7 @@ export class DocDataFactory {
62
62
  access: doclet.access,
63
63
  inherited: doclet.mixed ? false : doclet.inherited,
64
64
  mixed: doclet.mixed,
65
+ experimental: doclet.experimental || false,
65
66
  deprecated: doclet.deprecated,
66
67
  since: doclet.since,
67
68
  file: doclet.file,
@@ -264,6 +265,7 @@ export class DocDataFactory {
264
265
  */
265
266
  getClassFull( doclet ) {
266
267
  const classMembersCollection = this._dataCollection.get( `memberof:${ doclet.longname }` );
268
+ const base = this._getBase( doclet, { headingDecreaseLevel: 2 } );
267
269
  const configOptions = classMembersCollection.get( 'cfg' );
268
270
  const properties = classMembersCollection.get( 'member' );
269
271
  const methods = classMembersCollection.get( 'function' );
@@ -328,7 +330,7 @@ export class DocDataFactory {
328
330
  augmentsNested.unshift( doclet.longname );
329
331
  augmentsNested.reverse();
330
332
 
331
- return Object.assign( this._getBase( doclet, { headingDecreaseLevel: 2 } ), {
333
+ return Object.assign( base, {
332
334
  configOptions: configOptionsProcessed,
333
335
  properties: propertiesProcessed,
334
336
  staticProperties,
@@ -644,6 +646,13 @@ function prepareBadges( item, context ) {
644
646
  badges.push( deprecatedBadge );
645
647
  }
646
648
 
649
+ if ( item.experimental ) {
650
+ badges.push( {
651
+ class: 'cyan badge--experimental',
652
+ text: 'experimental'
653
+ } );
654
+ }
655
+
647
656
  if ( item.chainable ) {
648
657
  badges.push( {
649
658
  class: 'green',
@@ -724,7 +733,6 @@ function prepareBadges( item, context ) {
724
733
 
725
734
  return badges;
726
735
  }
727
-
728
736
  function sortLongnamesAlphabetically( arr ) {
729
737
  if ( !arr || !arr.length ) {
730
738
  return;
@@ -370,6 +370,20 @@ export class AbstractParser {
370
370
  return false;
371
371
  }
372
372
 
373
+ /**
374
+ * Checks if the member is marked as `experimental` one.
375
+ *
376
+ * @param {BaseReflection} item
377
+ * @returns {Boolean}
378
+ */
379
+ isExperimental( item ) {
380
+ if ( item.comment && item.comment.modifierTags ) {
381
+ return item.comment.modifierTags.some( tag => tag === '@experimental' );
382
+ }
383
+
384
+ return false;
385
+ }
386
+
373
387
  /**
374
388
  * Checks if the member is marked by the `@deprecated` JSDoc tag.
375
389
  *
@@ -53,6 +53,7 @@ export class AccessorParser extends AbstractParser {
53
53
  description: this.getComment( getSignature || setSignature ),
54
54
  file: this.getFile( item ),
55
55
  skipSource: this.shouldSkipSource( getSignature || setSignature ),
56
+ experimental: this.isExperimental( getSignature || setSignature ),
56
57
  deprecated: this.isDeprecated( getSignature || setSignature ),
57
58
  inherited: !!item.inheritedFrom,
58
59
 
@@ -37,6 +37,7 @@ export class ClassParser extends AbstractParser {
37
37
  description: this.getComment( item ),
38
38
  file: this.getFile( item ),
39
39
  skipSource: this.shouldSkipSource( item ),
40
+ experimental: this.isExperimental( item ),
40
41
  deprecated: this.isDeprecated( item ),
41
42
 
42
43
  // Properties specified below will be mapped to module names once the entire project is transformed.
@@ -35,6 +35,7 @@ export class ConstantParser extends AbstractParser {
35
35
  extraId: `constant-${ item.name }`,
36
36
  file: this.getFile( item ),
37
37
  skipSource: this.shouldSkipSource( item ),
38
+ experimental: this.isExperimental( item ),
38
39
  description: this.getComment( item ),
39
40
  deprecated: this.isDeprecated( item ),
40
41
 
@@ -38,6 +38,7 @@ export class EventParser extends AbstractParser {
38
38
  description: this.getComment( item ),
39
39
  file: this.getFile( item ),
40
40
  skipSource: this.shouldSkipSource( item ),
41
+ experimental: this.isExperimental( item ),
41
42
  deprecated: this.isDeprecated( item ),
42
43
  inherited: !!item.inheritedFrom,
43
44
  see: this.getRelated( item, parentName ),
@@ -38,6 +38,7 @@ export class FunctionParser extends AbstractParser {
38
38
  description: this.getComment( signature ),
39
39
  file: this.getFile( item, index ),
40
40
  skipSource: this.shouldSkipSource( signature ),
41
+ experimental: this.isExperimental( signature ),
41
42
  deprecated: this.isDeprecated( signature ),
42
43
  see: this.getRelated( signature, parentName ),
43
44
 
@@ -36,6 +36,7 @@ export class InterfaceParser extends AbstractParser {
36
36
  description: this.getComment( item ),
37
37
  file: this.getFile( item ),
38
38
  skipSource: this.shouldSkipSource( item ),
39
+ experimental: this.isExperimental( item ),
39
40
  deprecated: this.isDeprecated( item ),
40
41
 
41
42
  typeParameters: null,
@@ -39,6 +39,7 @@ export class MethodParser extends AbstractParser {
39
39
  description: this.getComment( signature ),
40
40
  file: this.getFile( item, index ),
41
41
  skipSource: this.shouldSkipSource( signature ),
42
+ experimental: this.isExperimental( signature ),
42
43
  deprecated: this.isDeprecated( signature ),
43
44
  inherited: !!signature.inheritedFrom,
44
45
  see: this.getRelated( signature, parentName ),
@@ -40,6 +40,7 @@ export class PropertyParser extends AbstractParser {
40
40
  description: this.getComment( item ),
41
41
  file: this.getFile( item ),
42
42
  skipSource: this.shouldSkipSource( item ),
43
+ experimental: this.isExperimental( item ),
43
44
  deprecated: this.isDeprecated( item ),
44
45
  inherited: !!item.inheritedFrom,
45
46
  see: this.getRelated( item, parentName ),
@@ -32,6 +32,7 @@ export class TypeParser extends AbstractParser {
32
32
  extraId: this.getExtraId( item ),
33
33
  file: this.getFile( item ),
34
34
  skipSource: this.shouldSkipSource( item ),
35
+ experimental: this.isExperimental( item ),
35
36
  description: this.getComment( item ),
36
37
  deprecated: this.isDeprecated( item ),
37
38
  firedBy: this.getFiredBy( item, parentName ),
@@ -28,5 +28,9 @@ export const createFilteringDataAttribs = item => {
28
28
  attrData[ 'data-deprecated' ] = 'true';
29
29
  }
30
30
 
31
+ if ( item.experimental ) {
32
+ attrData[ 'data-experimental' ] = 'true';
33
+ }
34
+
31
35
  return attrData;
32
36
  };
@@ -37,6 +37,8 @@ mixin apiHeader( config )
37
37
  +checkbox({ id:'f7', name: 'name4', className: 'js-api-filter-checkbox', label: 'Internal', value: 'internal', expanded: true })
38
38
  +dropdown-item({ narrow: true })
39
39
  +checkbox({ id:'f5', name: 'name7', className: 'js-api-filter-checkbox', label: 'Deprecated', value: 'deprecated', expanded: true, checked: true })
40
+ +dropdown-item({ narrow: true })
41
+ +checkbox({ id:'f8', name: 'name8', className: 'js-api-filter-checkbox', label: 'Experimental', value: 'experimental', expanded: true, checked: true })
40
42
 
41
43
  +button({
42
44
  icon: 'unordered-list',
@@ -0,0 +1,6 @@
1
+ mixin badgeTag( badge, options = {} )
2
+ - const iconNameByBadgeText = { private: 'lock', observable: 'eye', experimental: 'experiment' };
3
+ - const iconName = iconNameByBadgeText[ badge.text ] || null;
4
+ - const linkTo = badge && badge.linkTo ? badge.linkTo : false;
5
+ - const className = [ options.className || '', linkTo ? 'tag--link' : '', badge.text == 'experimental' ? 'tag--experimental' : '' ].filter( Boolean ).join( ' ' );
6
+ +tag({ label: badge.text, icon: iconName, iconSize: options.iconSize || 'base', href: linkTo, className })
@@ -1,5 +1,6 @@
1
1
  include ./_link-or-text.pug
2
2
  include ./_badge.pug
3
+ include ./_badge-tag.pug
3
4
  include ./_dev-names.pug
4
5
  include ./_api-see-source.pug
5
6
  include ../../_components/icon/index
@@ -22,10 +23,7 @@ mixin classItem( item )
22
23
  if isNonEmptyArray( item.badges )
23
24
  +tagWrapper()
24
25
  each badge in item.badges
25
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
26
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
27
- - const linkClassName = linkTo ? 'tag--link' : '';
28
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
26
+ +badgeTag( badge, { iconSize: 'sm' } )
29
27
 
30
28
  +devNames( item.longname )
31
29
 
@@ -1,4 +1,5 @@
1
1
  include _badge
2
+ include _badge-tag.pug
2
3
  include _type
3
4
  include _params
4
5
  include _return
@@ -52,10 +53,7 @@ mixin method( met )
52
53
  if isNonEmptyArray( met.badges )
53
54
  +tagWrapper()
54
55
  each badge in met.badges
55
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
56
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
57
- - const linkClassName = linkTo ? 'tag--link' : '';
58
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
56
+ +badgeTag( badge, { iconSize: 'sm' } )
59
57
 
60
58
  +devNames( met.longname, met.label )
61
59
 
@@ -1,6 +1,7 @@
1
1
  include _params
2
2
  include ./_type.pug
3
3
  include ./_badge.pug
4
+ include ./_badge-tag.pug
4
5
  include ./_dev-names.pug
5
6
  include ./_api-see-source.pug
6
7
  include ./_related.pug
@@ -26,10 +27,7 @@ mixin property( prop )
26
27
  if isNonEmptyArray( prop.badges )
27
28
  +tagWrapper()
28
29
  each badge in prop.badges
29
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
30
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
31
- - const linkClassName = linkTo ? 'tag--link' : '';
32
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
30
+ +badgeTag( badge, { iconSize: 'sm' } )
33
31
 
34
32
  +devNames( prop.longname )
35
33
 
@@ -54,4 +52,3 @@ mixin property( prop )
54
52
 
55
53
  if isNonEmptyArray( prop.see )
56
54
  +related( prop.see )
57
-
@@ -38,3 +38,7 @@ div( class="notice notice__filter api-props-filter hidden-loading" )
38
38
  input( type="checkbox" id="f5" value="deprecated" )
39
39
  label( for="f5" )
40
40
  span( class="badge badge--red" ) Deprecated
41
+ li
42
+ input( type="checkbox" id="f8" value="experimental" checked )
43
+ label( for="f8" )
44
+ span( class="badge badge--cyan" ) Experimental
@@ -2,4 +2,11 @@
2
2
  .api-subheader {
3
3
  width: 100%;
4
4
  margin-top: var(--spacing-2);
5
+
6
+ &__badges {
7
+ display: flex;
8
+ align-items: center;
9
+ flex-wrap: wrap;
10
+ gap: var(--spacing-2);
11
+ }
5
12
  }
@@ -1,10 +1,14 @@
1
1
  include ../_mixin/_badge
2
+ include ../_mixin/_badge-tag.pug
2
3
 
3
4
  mixin apiSubheader()
4
5
  - const iconName = "api-" + data.kind.toLowerCase();
5
6
  - const iconClassName = "api-icon api-icon--" + data.kind.toLowerCase();
6
7
  - const tagClassName = "tag--api tag--api-" + data.kind.toLowerCase();
8
+ - const experimentalBadge = isNonEmptyArray( data.badges ) ? data.badges.find( badge => badge.text == 'experimental' ) : null;
7
9
 
8
10
  div(class="api-subheader hidden-loading")
9
11
  div.api-subheader__badges
10
12
  +tag({ label: data.kind, icon : iconName, className: tagClassName })
13
+ if experimentalBadge
14
+ +badgeTag( experimentalBadge )
@@ -20,6 +20,18 @@
20
20
  }
21
21
  }
22
22
 
23
+ &--experimental {
24
+ color: var(--color-cyan-600);
25
+ border-color: var(--color-cyan-600);
26
+ background-color: var(--color-cyan-50);
27
+
28
+ &.tag--link {
29
+ &:hover {
30
+ background-color: var(--color-cyan-100);
31
+ }
32
+ }
33
+ }
34
+
23
35
  &__icon {
24
36
 
25
37
  + .tag__text {
@@ -191,6 +191,8 @@ div
191
191
  +checkbox({ id:'f5', name: 'name7', label: 'Deprecated', value: 'deprecated', expanded: true })
192
192
  +dropdown-item()
193
193
  +checkbox({ id:'f7', name: 'name4', label: 'Internal', value: 'internal', expanded: true })
194
+ +dropdown-item()
195
+ +checkbox({ id:'f8', name: 'name8', label: 'Experimental', value: 'experimental', expanded: true, checked: true })
194
196
  +dropdown-item()
195
197
  +checkbox({ id:'f6', name: 'name3', label: 'Mixed', value: 'mixed', expanded: true })
196
198
 
@@ -38,3 +38,7 @@ div( class="notice notice__filter api-props-filter hidden-loading" )
38
38
  input( type="checkbox" id="f5" value="deprecated" )
39
39
  label( for="f5" )
40
40
  span( class="badge badge--red" ) Deprecated
41
+ li
42
+ input( type="checkbox" id="f8" value="experimental" checked )
43
+ label( for="f8" )
44
+ span( class="badge badge--cyan" ) Experimental
@@ -139,21 +139,23 @@ export class ApiFilter extends BaseComponent {
139
139
  * Sets the initial state of the API filter into local storage when there is no set yet.
140
140
  */
141
141
  setInitialApiFilterState() {
142
- const { apiFilterState } = this.state;
143
142
  const { filterDropdown } = this.elements;
143
+ const apiFilterState = { ...this.state.apiFilterState };
144
144
 
145
- if ( Object.keys( apiFilterState ).length === 0 ) {
146
- Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
147
- const isChecked = input.checked;
148
- const filterType = input.value;
145
+ Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
146
+ const isChecked = input.checked;
147
+ const filterType = input.value;
149
148
 
150
- this.saveApiFilterState( filterType, isChecked );
151
- } );
152
- }
149
+ if ( apiFilterState[ filterType ] === undefined ) {
150
+ apiFilterState[ filterType ] = isChecked;
151
+ }
152
+ } );
153
+
154
+ window.localStorage.setItem( API_FILTER_LOCAL_STORAGE_KEY, JSON.stringify( apiFilterState ) );
153
155
 
154
156
  this.setState( {
155
- apiFilterState: JSON.parse( localStorage.getItem( API_FILTER_LOCAL_STORAGE_KEY ) ) || {},
156
- activeFilters: Object.keys( this.state.apiFilterState ).filter( key => this.state.apiFilterState[ key ] )
157
+ apiFilterState,
158
+ activeFilters: Object.keys( apiFilterState ).filter( key => apiFilterState[ key ] )
157
159
  } );
158
160
  }
159
161
 
@@ -163,14 +165,21 @@ export class ApiFilter extends BaseComponent {
163
165
  initialApplyApiFilterState() {
164
166
  const { apiFilterState } = this.state;
165
167
  const { filterDropdown } = this.elements;
168
+ const activeFilters = [];
166
169
 
167
170
  Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
168
171
  const isChecked = input.checked;
169
172
  const filterType = input.value;
170
173
 
171
174
  input.checked = apiFilterState[ filterType ] !== undefined ? apiFilterState[ filterType ] : isChecked;
175
+
176
+ if ( input.checked ) {
177
+ activeFilters.push( filterType );
178
+ }
172
179
  } );
173
180
 
181
+ this.setState( { activeFilters } );
182
+
174
183
  // this.dispatchInitialFolderStateDoneEvent();
175
184
  }
176
185
 
@@ -51,6 +51,7 @@
51
51
  @include badge( 'red' );
52
52
  @include badge( 'orange' );
53
53
  @include badge( 'purple' );
54
+ @include badge( 'cyan' );
54
55
 
55
56
  /* #357. */
56
57
  &.badge--observable {
@@ -32,6 +32,7 @@ $u-colors: (
32
32
  'badge-red': #E50037,
33
33
  'badge-black': #212121,
34
34
  'badge-purple': #AB47BC,
35
+ 'badge-cyan': hsl(189, 100%, 31%),
35
36
 
36
37
  // Hints
37
38
  'hint-info': #64B5F6,
@@ -14,6 +14,7 @@ export function enableFiltering() {
14
14
  protected: storagePrefix + 'protected',
15
15
  private: storagePrefix + 'private',
16
16
  deprecated: storagePrefix + 'deprecated',
17
+ experimental: storagePrefix + 'experimental',
17
18
  tree: 'filtering:tree'
18
19
  };
19
20