@rulecms/source-components-angular 0.3.0 → 0.4.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 (37) hide show
  1. package/fesm2022/rulecms-source-components-angular.mjs +364 -33
  2. package/fesm2022/rulecms-source-components-angular.mjs.map +1 -1
  3. package/lib/base-components/display/component-attributes/component-name-to-modify-attribute.d.ts.map +1 -1
  4. package/lib/base-components/display/components/r-accordion/accordion-attributes.d.ts +1 -0
  5. package/lib/base-components/display/components/r-accordion/accordion-attributes.d.ts.map +1 -1
  6. package/lib/base-components/display/components/r-accordion/attribute-edit-mode-configs.d.ts.map +1 -1
  7. package/lib/base-components/display/components/r-accordion/default-attribute-values.d.ts.map +1 -1
  8. package/lib/base-components/display/components/r-accordion/r-accordion.component.d.ts +1 -0
  9. package/lib/base-components/display/components/r-accordion/r-accordion.component.d.ts.map +1 -1
  10. package/lib/base-components/display/components/r-list/attribute-edit-mode-configs.d.ts.map +1 -1
  11. package/lib/base-components/display/components/r-list/default-attribute-values.d.ts.map +1 -1
  12. package/lib/base-components/display/components/r-list/list-attributes.d.ts +1 -0
  13. package/lib/base-components/display/components/r-list/list-attributes.d.ts.map +1 -1
  14. package/lib/base-components/display/components/r-list/r-list.component.d.ts +1 -0
  15. package/lib/base-components/display/components/r-list/r-list.component.d.ts.map +1 -1
  16. package/lib/base-components/display/components/r-text/attribute-edit-mode-configs.d.ts.map +1 -1
  17. package/lib/base-components/display/components/r-text/default-attribute-values.d.ts.map +1 -1
  18. package/lib/base-components/display/components/r-text/r-text.component.d.ts +5 -0
  19. package/lib/base-components/display/components/r-text/r-text.component.d.ts.map +1 -1
  20. package/lib/shared/inline-markup/inline-markup.component.d.ts +7 -1
  21. package/lib/shared/inline-markup/inline-markup.component.d.ts.map +1 -1
  22. package/lib/shared/inline-markup/parse-inline-markers.d.ts +9 -3
  23. package/lib/shared/inline-markup/parse-inline-markers.d.ts.map +1 -1
  24. package/lib/shared/inline-markup/serialize-inline-markers.d.ts.map +1 -1
  25. package/lib/shared/span-styles/index.d.ts +8 -0
  26. package/lib/shared/span-styles/index.d.ts.map +1 -0
  27. package/lib/shared/span-styles/parse-style-declarations.d.ts +28 -0
  28. package/lib/shared/span-styles/parse-style-declarations.d.ts.map +1 -0
  29. package/lib/shared/span-styles/resolve-class-names.d.ts +23 -0
  30. package/lib/shared/span-styles/resolve-class-names.d.ts.map +1 -0
  31. package/lib/shared/span-styles/resolve-span-style-attributes.d.ts +26 -0
  32. package/lib/shared/span-styles/resolve-span-style-attributes.d.ts.map +1 -0
  33. package/lib/shared/span-styles/span-style-item.d.ts +43 -0
  34. package/lib/shared/span-styles/span-style-item.d.ts.map +1 -0
  35. package/package.json +1 -1
  36. package/public-api.d.ts +2 -0
  37. package/public-api.d.ts.map +1 -1
@@ -19,6 +19,10 @@ const defaultAttributeValues$9 = [
19
19
  name: 'links-3-resolutions',
20
20
  defaultValues: {},
21
21
  },
22
+ {
23
+ name: 'inline-styles-3-resolutions',
24
+ defaultValues: {},
25
+ },
22
26
  ];
23
27
 
24
28
  /**
@@ -351,13 +355,266 @@ function resolveLinkAttributes(item) {
351
355
  }
352
356
 
353
357
  /**
354
- * `{` + optional mark letters + optional index + `|` + text + `}`.
358
+ * Runtime guard for entries arriving through componentAttributes (untyped,
359
+ * editor-authored data).
360
+ *
361
+ * Only object shape is checked, for the same reason as `isLinkItem`: one bad
362
+ * field must cost that field rather than the whole entry, so per-field
363
+ * validation belongs to `resolveSpanStyleAttributes` where each value is turned
364
+ * into something an element can carry.
365
+ */
366
+ function isSpanStyleItem(value) {
367
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
368
+ }
369
+ /**
370
+ * True for a cleared slot. Clearing an entry stores `{}` rather than removing
371
+ * the array element, because marker indexes are 1-based positions: dropping an
372
+ * element would silently repoint every later marker.
373
+ *
374
+ * A `label` alone is a cleared slot. It is the author's note to themselves
375
+ * about which entry this is, and an entry that only has a name applies nothing.
376
+ */
377
+ function isEmptySpanStyleItem(item) {
378
+ if (!isSpanStyleItem(item)) {
379
+ return true;
380
+ }
381
+ return [item.classNames, item.declarations].every((value) => typeof value !== 'string' || value.trim() === '');
382
+ }
383
+ /**
384
+ * The entry a `{.n|…}` marker points at, by 1-based position. Tolerant of bad
385
+ * data on every axis — a non-array list, a fractional or out-of-range index, a
386
+ * hole in the array — because none of it may throw during a render.
387
+ */
388
+ function getSpanStyleItemAt(spanStyles, oneBasedIndex) {
389
+ if (!Array.isArray(spanStyles)) {
390
+ return undefined;
391
+ }
392
+ if (!Number.isInteger(oneBasedIndex) || oneBasedIndex < 1) {
393
+ return undefined;
394
+ }
395
+ const entry = spanStyles[oneBasedIndex - 1];
396
+ return isSpanStyleItem(entry) ? entry : undefined;
397
+ }
398
+
399
+ /**
400
+ * A class name is a *selector* into CSS the customer already ships. This
401
+ * library cannot see those rules, so it cannot validate a name against
402
+ * anything — the only useful question is whether the token could damage the
403
+ * document it lands in, and the answer is almost always no: every renderer sets
404
+ * `class` through a DOM property or a framework binding, never by pasting into
405
+ * markup.
406
+ *
407
+ * So the filter is deliberately permissive. Utility frameworks make class names
408
+ * that look like code — `md:hover:text-lg`, `w-1/2`, `text-[#c00]`,
409
+ * `[&>svg]:size-4` — and a tidier-looking allowlist would reject those and be
410
+ * read as a bug.
411
+ */
412
+ /** Whitespace separates tokens, so it cannot appear inside one. */
413
+ const TOKEN_SEPARATOR = /\s+/;
414
+ /**
415
+ * Control characters, and the two brace characters. Braces are not dangerous in
416
+ * a class attribute; they are rejected because a token containing one is nearly
417
+ * always an inline marker the author pasted into the wrong field, and silently
418
+ * accepting it would style nothing and explain nothing.
419
+ */
420
+ // eslint-disable-next-line no-control-regex -- control characters are what this matches
421
+ const REJECTED_IN_TOKEN = /[\u0000-\u001f\u007f{}]/;
422
+ /**
423
+ * Bounds, so a pathological value cannot become a pathological `class`
424
+ * attribute. Both are far past anything a person types by hand.
425
+ */
426
+ const MAX_TOKENS = 50;
427
+ const MAX_TOKEN_LENGTH = 120;
428
+ const isUsableToken = (token) =>
429
+ // Splitting a blank string yields one empty token, which is not a class name.
430
+ token !== '' && token.length <= MAX_TOKEN_LENGTH && !REJECTED_IN_TOKEN.test(token);
431
+ /**
432
+ * The `class` value for a styling entry, or `undefined` when it has none worth
433
+ * setting.
434
+ *
435
+ * Duplicates collapse and order is the author's. One bad token costs that
436
+ * token: the rest of a mostly-fine list still applies, which is the same
437
+ * per-field tolerance `resolveLinkAttributes` gives a link entry.
438
+ */
439
+ function resolveClassNames(value) {
440
+ if (typeof value !== 'string') {
441
+ return undefined;
442
+ }
443
+ const tokens = value.trim().split(TOKEN_SEPARATOR).filter(isUsableToken);
444
+ if (tokens.length === 0) {
445
+ return undefined;
446
+ }
447
+ return Array.from(new Set(tokens)).slice(0, MAX_TOKENS).join(' ');
448
+ }
449
+
450
+ /**
451
+ * Declarations are split on `;` without tracking quotes, so a value containing
452
+ * a semicolon inside a string — `content: "a;b"` — is cut in half and both
453
+ * halves are then rejected as malformed. That is an acceptable trade: the
454
+ * properties worth setting on a run of words do not take string values, and a
455
+ * quote-aware splitter is a second CSS parser to keep correct.
456
+ */
457
+ const DECLARATION_SEPARATOR = ';';
458
+ /** `--custom-property`, or an ordinary property with an optional vendor prefix. */
459
+ const PROPERTY_NAME = /^(--[a-zA-Z0-9-]+|-?[a-zA-Z][a-zA-Z0-9-]*)$/;
460
+ /**
461
+ * Properties that do something other than style text, whatever their value.
462
+ *
463
+ * `behavior` and `-moz-binding` attach script to an element through CSS in old
464
+ * engines. They are long dead, and they are named here anyway: the cost is one
465
+ * line, and the alternative is relying on every reader of this file knowing
466
+ * that CSS was once executable.
467
+ */
468
+ const REJECTED_PROPERTIES = new Set(['behavior', '-moz-binding']);
469
+ /**
470
+ * Constructs that make a declaration do more than describe appearance.
471
+ *
472
+ * `url(` is rejected rather than filtered because every reason to allow it is
473
+ * better served elsewhere: a background image is a component, and a `url()` in
474
+ * authored text is either a tracking pixel, a `data:` payload, or a
475
+ * `javascript:` scheme an older engine will still run. A backslash is rejected
476
+ * for the same reason in one step: CSS escapes let `\75 rl(` spell `url(`, so
477
+ * allowing escapes would mean decoding them before every other check.
478
+ */
479
+ const REJECTED_IN_VALUE = [
480
+ 'url(',
481
+ 'expression(',
482
+ 'javascript:',
483
+ 'vbscript:',
484
+ '@',
485
+ '\\',
486
+ '<',
487
+ '>',
488
+ '/*',
489
+ '*/',
490
+ ];
491
+ /** Control characters, and the braces that would end a declaration list. */
492
+ // eslint-disable-next-line no-control-regex -- control characters are what this matches
493
+ const REJECTED_CHARACTERS = /[\u0000-\u001f\u007f{}]/;
494
+ /**
495
+ * React drops `!important` from a style object and warns about it, while the
496
+ * string-based ports would honour it — one authored value behaving differently
497
+ * per platform. It comes off everywhere instead, and the class-names field is
498
+ * the supported way to win a specificity fight.
499
+ */
500
+ const IMPORTANT_FLAG = /\s*!\s*important\s*$/i;
501
+ /** Bounds well past anything typed by hand, so a pasted stylesheet cannot land here. */
502
+ const MAX_DECLARATIONS = 40;
503
+ const MAX_VALUE_LENGTH = 400;
504
+ const isUsableProperty = (property) => PROPERTY_NAME.test(property) && !REJECTED_PROPERTIES.has(property.toLowerCase());
505
+ const isUsableValue = (value) => {
506
+ if (value === '' || value.length > MAX_VALUE_LENGTH) {
507
+ return false;
508
+ }
509
+ if (REJECTED_CHARACTERS.test(value)) {
510
+ return false;
511
+ }
512
+ const lowered = value.toLowerCase();
513
+ return !REJECTED_IN_VALUE.some((rejected) => lowered.includes(rejected));
514
+ };
515
+ /**
516
+ * A declaration to the pair it means, or `undefined` when it is malformed or
517
+ * carries something this module will not apply.
518
+ */
519
+ const parseDeclaration = (declaration) => {
520
+ const separator = declaration.indexOf(':');
521
+ if (separator === -1) {
522
+ return undefined;
523
+ }
524
+ // Only the first colon splits: `background: linear-gradient(…)` keeps the rest.
525
+ const property = declaration.slice(0, separator).trim();
526
+ const value = declaration.slice(separator + 1).replace(IMPORTANT_FLAG, '').trim();
527
+ if (!isUsableProperty(property) || !isUsableValue(value)) {
528
+ return undefined;
529
+ }
530
+ return { property, value };
531
+ };
532
+ /**
533
+ * An authored CSS declaration list to the declarations that will be applied.
534
+ *
535
+ * The list is a denylist over values and an allowlist over property names,
536
+ * which is the same shape as `text-safety/validate-text-content.ts` in the
537
+ * composer and for the same reason: a person writing `color: #c00` should never
538
+ * have to think about this function, while the handful of constructs that turn
539
+ * a declaration into a request or a script are named and refused.
540
+ *
541
+ * A rejected declaration costs only itself. `color: red; behavior: url(x)`
542
+ * still sets the colour, because dropping the whole entry over one bad
543
+ * declaration would lose work the author can see is fine.
544
+ */
545
+ function parseStyleDeclarations(value) {
546
+ if (typeof value !== 'string') {
547
+ return [];
548
+ }
549
+ const parsed = [];
550
+ const seen = new Set();
551
+ for (const declaration of value.split(DECLARATION_SEPARATOR)) {
552
+ if (declaration.trim() === '') {
553
+ continue;
554
+ }
555
+ const pair = parseDeclaration(declaration);
556
+ if (pair === undefined) {
557
+ continue;
558
+ }
559
+ // Last one wins, as it would in a real declaration list.
560
+ if (seen.has(pair.property)) {
561
+ parsed[parsed.findIndex((existing) => existing.property === pair.property)] = pair;
562
+ continue;
563
+ }
564
+ seen.add(pair.property);
565
+ parsed.push(pair);
566
+ if (parsed.length === MAX_DECLARATIONS) {
567
+ break;
568
+ }
569
+ }
570
+ return parsed;
571
+ }
572
+ /**
573
+ * Declarations back to a CSS string, for the places that want a `style`
574
+ * attribute rather than an object — which in this port is every one of them.
575
+ */
576
+ function formatStyleDeclarations(declarations) {
577
+ return declarations.map(({ property, value }) => `${property}: ${value}`).join('; ');
578
+ }
579
+
580
+ /**
581
+ * Turns a stored styling entry into element attributes, or `undefined` when the
582
+ * entry cannot produce any — missing, cleared, or holding nothing that survived
583
+ * the checks in `resolveClassNames` and `parseStyleDeclarations`. Callers render
584
+ * the plain text in that case, exactly as they do for a link that resolves to
585
+ * nothing.
586
+ *
587
+ * Knows nothing about text, markers, or any one component: this is the piece
588
+ * every component rendering inline markup shares.
589
+ */
590
+ function resolveSpanStyleAttributes(item) {
591
+ if (!isSpanStyleItem(item)) {
592
+ return undefined;
593
+ }
594
+ const className = resolveClassNames(item.classNames);
595
+ const declarations = parseStyleDeclarations(item.declarations);
596
+ if (className === undefined && declarations.length === 0) {
597
+ return undefined;
598
+ }
599
+ return {
600
+ ...(className !== undefined ? { className } : {}),
601
+ ...(declarations.length > 0 ? { style: formatStyleDeclarations(declarations) } : {}),
602
+ };
603
+ }
604
+
605
+ /**
606
+ * `{` + optional mark letters + optional link index + optional `.` and styling
607
+ * index + `|` + text + `}`.
355
608
  *
356
609
  * Sticky, so it can be tried at one position without slicing the string. The
357
610
  * text is everything up to the first `}`, which is what makes markers
358
611
  * non-nesting.
612
+ *
613
+ * The dot is what tells the two indexes apart, and it reads as the CSS class
614
+ * selector it usually stands for. It cannot be confused with a link index,
615
+ * because a link index is bare digits and always comes first.
359
616
  */
360
- const MARKER = /\{([a-z]*)(\d*)\|([^}]*)\}/y;
617
+ const MARKER = /\{([a-z]*)(\d*)(?:\.(\d+))?\|([^}]*)\}/y;
361
618
  /**
362
619
  * One letter per mark. Order is not significant (`{bi1|x}` and `{ib1|x}` are
363
620
  * the same span) and repeats are harmless, because the letters resolve to a
@@ -399,9 +656,9 @@ const resolveMarks = (letters) => {
399
656
  * The grammar, and everything it deliberately leaves as literal text, is
400
657
  * specified in `rulecms/__docs__/__ARCHITECTURE__/INLINE_MARKUP_ARCHITECTURE.md`.
401
658
  * In short: a marker is `{`, then lowercase mark letters, then a link index,
402
- * then `|`, then its text, then `}`. At least one of the letters and the index
403
- * must be present, and everything that does not match that shape stays
404
- * verbatim — `{name}`, `{99}`, a lone `{`, JSON fragments, an unterminated
659
+ * then `.` and a styling index, then `|`, then its text, then `}`. At least one
660
+ * of those three must be present, and everything that does not match that shape
661
+ * stays verbatim — `{name}`, `{99}`, a lone `{`, JSON fragments, an unterminated
405
662
  * marker, an unknown mark letter, and empty span text.
406
663
  *
407
664
  * Indexes are 1-based and normalize through a base-10 parse, so `{01|x}` and
@@ -429,7 +686,7 @@ function parseInlineMarkers(value) {
429
686
  }
430
687
  MARKER.lastIndex = open;
431
688
  const match = MARKER.exec(value);
432
- const run = match ? resolveRun(match[1], match[2], match[3]) : undefined;
689
+ const run = match ? resolveRun(match[1], match[2], match[3], match[4]) : undefined;
433
690
  if (run === undefined) {
434
691
  // Not a marker: keep the brace as text and look for the next one.
435
692
  pendingText += value.slice(cursor, open + 1);
@@ -447,13 +704,19 @@ function parseInlineMarkers(value) {
447
704
  /**
448
705
  * One matched `{…|…}` to the run it means, or `undefined` when the match is
449
706
  * not a marker after all and its opening brace is literal text.
707
+ *
708
+ * `styleDigits` is `undefined` when the marker carried no dot at all, which is
709
+ * different from a dot with nothing after it — the regex does not match that,
710
+ * so it never reaches here.
450
711
  */
451
- const resolveRun = (letters, digits, text) => {
712
+ const resolveRun = (letters, digits, styleDigits, text) => {
452
713
  if (letters === BREAK_TOKEN) {
453
714
  // A break carries nothing. `{br|x}` is a mistake, not a break with text.
454
- return digits === '' && text === '' ? { kind: 'break' } : undefined;
715
+ return digits === '' && styleDigits === undefined && text === ''
716
+ ? { kind: 'break' }
717
+ : undefined;
455
718
  }
456
- if (letters === '' && digits === '') {
719
+ if (letters === '' && digits === '' && styleDigits === undefined) {
457
720
  return undefined;
458
721
  }
459
722
  // Empty text has no words to mark or link, and staying literal is what shows
@@ -469,6 +732,7 @@ const resolveRun = (letters, digits, text) => {
469
732
  kind: 'span',
470
733
  text,
471
734
  ...(digits === '' ? {} : { index: Number.parseInt(digits, 10) }),
735
+ ...(styleDigits === undefined ? {} : { styleIndex: Number.parseInt(styleDigits, 10) }),
472
736
  marks,
473
737
  };
474
738
  };
@@ -492,8 +756,14 @@ class InlineMarkupComponent {
492
756
  const anchors = runs.map((run) => run.kind === 'span' && run.index !== undefined
493
757
  ? resolveLinkAttributes(getLinkItemAt(this.links, run.index))
494
758
  : undefined);
759
+ const spanStyles = runs.map((run) => run.kind === 'span' && run.styleIndex !== undefined
760
+ ? resolveSpanStyleAttributes(getSpanStyleItemAt(this.spanStyles, run.styleIndex))
761
+ : undefined);
495
762
  const needsElement = runs.some((run, position) => run.kind === 'break' ||
496
- (run.kind === 'span' && (anchors[position] !== undefined || hasMark(run.marks))));
763
+ (run.kind === 'span' &&
764
+ (anchors[position] !== undefined ||
765
+ spanStyles[position] !== undefined ||
766
+ hasMark(run.marks))));
497
767
  if (!needsElement) {
498
768
  this.renderer.appendChild(this.host.nativeElement, this.renderer.createText(runs.map(plainTextOf).join('')));
499
769
  return;
@@ -507,10 +777,15 @@ class InlineMarkupComponent {
507
777
  this.renderer.appendChild(this.host.nativeElement, this.renderer.createElement('br'));
508
778
  return;
509
779
  }
510
- this.renderer.appendChild(this.host.nativeElement, this.renderSpan(run, anchors[position]));
780
+ this.renderer.appendChild(this.host.nativeElement, this.renderSpan(run, anchors[position], spanStyles[position]));
511
781
  });
512
782
  }
513
- renderSpan(run, anchor) {
783
+ /**
784
+ * The anchor stays innermost and a styling entry becomes the outermost
785
+ * element: the marks say what these words *are*, and the styling says how
786
+ * this run of words looks, which is the container of the other two.
787
+ */
788
+ renderSpan(run, anchor, spanStyle) {
514
789
  let node = anchor === undefined
515
790
  ? this.renderer.createText(run.text)
516
791
  : this.createAnchor(anchor, run.text);
@@ -522,7 +797,16 @@ class InlineMarkupComponent {
522
797
  node = wrapper;
523
798
  }
524
799
  }
525
- return node;
800
+ if (spanStyle === undefined) {
801
+ return node;
802
+ }
803
+ const styled = this.renderer.createElement('span');
804
+ if (spanStyle.className)
805
+ this.renderer.setAttribute(styled, 'class', spanStyle.className);
806
+ if (spanStyle.style)
807
+ this.renderer.setAttribute(styled, 'style', spanStyle.style);
808
+ this.renderer.appendChild(styled, node);
809
+ return styled;
526
810
  }
527
811
  createAnchor(anchor, text) {
528
812
  const el = this.renderer.createElement('a');
@@ -553,7 +837,7 @@ class InlineMarkupComponent {
553
837
  }
554
838
  }
555
839
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InlineMarkupComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
556
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: InlineMarkupComponent, isStandalone: true, selector: "rulecms-inline-markup", inputs: { text: "text", links: "links" }, host: { styleAttribute: "display: contents" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true }); }
840
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: InlineMarkupComponent, isStandalone: true, selector: "rulecms-inline-markup", inputs: { text: "text", links: "links", spanStyles: "spanStyles" }, host: { styleAttribute: "display: contents" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true }); }
557
841
  }
558
842
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InlineMarkupComponent, decorators: [{
559
843
  type: Component,
@@ -567,6 +851,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
567
851
  type: Input
568
852
  }], links: [{
569
853
  type: Input
854
+ }], spanStyles: [{
855
+ type: Input
570
856
  }] } });
571
857
 
572
858
  class HtmlTagComponent {
@@ -646,6 +932,13 @@ class RTextComponent {
646
932
  get links() {
647
933
  return this.componentAttributes?.['links-3-resolutions'];
648
934
  }
935
+ /**
936
+ * The two index markers — `{n|…}` into the link list and `{.n|…}` into the
937
+ * styling list — resolve by 1-based position against separate attributes.
938
+ */
939
+ get spanStyles() {
940
+ return this.componentAttributes?.['inline-styles-3-resolutions'];
941
+ }
649
942
  get tag() {
650
943
  return resolveEnclosingTag(this.componentAttributes);
651
944
  }
@@ -653,12 +946,12 @@ class RTextComponent {
653
946
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: RTextComponent, isStandalone: true, selector: "rulecms-r-text", inputs: { componentAttributes: "componentAttributes", styleProps: "styleProps", className: "className" }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
654
947
  @if (tag) {
655
948
  <rulecms-html-tag [tag]="tag" [styleProps]="styleProps" [className]="className">
656
- <rulecms-inline-markup [text]="content" [links]="links" />
949
+ <rulecms-inline-markup [text]="content" [links]="links" [spanStyles]="spanStyles" />
657
950
  </rulecms-html-tag>
658
951
  } @else {
659
- <rulecms-inline-markup [text]="content" [links]="links" />
952
+ <rulecms-inline-markup [text]="content" [links]="links" [spanStyles]="spanStyles" />
660
953
  }
661
- `, isInline: true, dependencies: [{ kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links"] }, { kind: "component", type: HtmlTagComponent, selector: "rulecms-html-tag", inputs: ["tag", "styleProps", "className"] }] }); }
954
+ `, isInline: true, dependencies: [{ kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links", "spanStyles"] }, { kind: "component", type: HtmlTagComponent, selector: "rulecms-html-tag", inputs: ["tag", "styleProps", "className"] }] }); }
662
955
  }
663
956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: RTextComponent, decorators: [{
664
957
  type: Component,
@@ -669,10 +962,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
669
962
  template: `
670
963
  @if (tag) {
671
964
  <rulecms-html-tag [tag]="tag" [styleProps]="styleProps" [className]="className">
672
- <rulecms-inline-markup [text]="content" [links]="links" />
965
+ <rulecms-inline-markup [text]="content" [links]="links" [spanStyles]="spanStyles" />
673
966
  </rulecms-html-tag>
674
967
  } @else {
675
- <rulecms-inline-markup [text]="content" [links]="links" />
968
+ <rulecms-inline-markup [text]="content" [links]="links" [spanStyles]="spanStyles" />
676
969
  }
677
970
  `,
678
971
  host: { style: 'display: contents' },
@@ -724,6 +1017,12 @@ const attributeEditModeConfigs$9 = [
724
1017
  subHeading: 'Links this text can point at. To link part of the text, wrap those words in {1|linked text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
725
1018
  defaultValues: {},
726
1019
  },
1020
+ {
1021
+ name: 'inline-styles-3-resolutions',
1022
+ heading: 'Class Names & Styles',
1023
+ subHeading: 'Class names and CSS this text can apply to part of itself, e.g. to give a few words their own colour. To apply an entry, wrap those words in {.1|styled text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
1024
+ defaultValues: {},
1025
+ },
727
1026
  ];
728
1027
 
729
1028
  const ComponentIdentifiers = {
@@ -3223,6 +3522,7 @@ const LIST_MARKER_ATTRIBUTE = 'list-marker-3-resolutions';
3223
3522
  const LIST_START_ATTRIBUTE = 'list-start-3-resolutions';
3224
3523
  const LIST_REVERSED_ATTRIBUTE = 'list-reversed-3-resolutions';
3225
3524
  const LIST_LINKS_ATTRIBUTE = 'list-links-3-resolutions';
3525
+ const LIST_INLINE_STYLES_ATTRIBUTE = 'list-inline-styles-3-resolutions';
3226
3526
  /** What a freshly dropped list shows before the author types anything. */
3227
3527
  const DEFAULT_LIST_ITEMS = 'First item\nSecond item\nThird item';
3228
3528
  const LIST_KINDS = ['unordered', 'ordered'];
@@ -3315,6 +3615,9 @@ class RListComponent {
3315
3615
  get links() {
3316
3616
  return this.componentAttributes?.[LIST_LINKS_ATTRIBUTE];
3317
3617
  }
3618
+ get spanStyles() {
3619
+ return this.componentAttributes?.[LIST_INLINE_STYLES_ATTRIBUTE];
3620
+ }
3318
3621
  get style() {
3319
3622
  return {
3320
3623
  ...(this.marker !== undefined ? { listStyleType: this.marker } : {}),
@@ -3339,7 +3642,7 @@ class RListComponent {
3339
3642
  <ol [ngStyle]="style" [class]="className || undefined" [attr.start]="start" [attr.reversed]="reversed ? true : null">
3340
3643
  @for (item of items; track $index) {
3341
3644
  <li>
3342
- <rulecms-inline-markup [text]="item" [links]="links" />
3645
+ <rulecms-inline-markup [text]="item" [links]="links" [spanStyles]="spanStyles" />
3343
3646
  </li>
3344
3647
  }
3345
3648
  </ol>
@@ -3347,12 +3650,12 @@ class RListComponent {
3347
3650
  <ul [ngStyle]="style" [class]="className || undefined">
3348
3651
  @for (item of items; track $index) {
3349
3652
  <li>
3350
- <rulecms-inline-markup [text]="item" [links]="links" />
3653
+ <rulecms-inline-markup [text]="item" [links]="links" [spanStyles]="spanStyles" />
3351
3654
  </li>
3352
3655
  }
3353
3656
  </ul>
3354
3657
  }
3355
- `, isInline: true, dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links"] }] }); }
3658
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links", "spanStyles"] }] }); }
3356
3659
  }
3357
3660
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: RListComponent, decorators: [{
3358
3661
  type: Component,
@@ -3365,7 +3668,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
3365
3668
  <ol [ngStyle]="style" [class]="className || undefined" [attr.start]="start" [attr.reversed]="reversed ? true : null">
3366
3669
  @for (item of items; track $index) {
3367
3670
  <li>
3368
- <rulecms-inline-markup [text]="item" [links]="links" />
3671
+ <rulecms-inline-markup [text]="item" [links]="links" [spanStyles]="spanStyles" />
3369
3672
  </li>
3370
3673
  }
3371
3674
  </ol>
@@ -3373,7 +3676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
3373
3676
  <ul [ngStyle]="style" [class]="className || undefined">
3374
3677
  @for (item of items; track $index) {
3375
3678
  <li>
3376
- <rulecms-inline-markup [text]="item" [links]="links" />
3679
+ <rulecms-inline-markup [text]="item" [links]="links" [spanStyles]="spanStyles" />
3377
3680
  </li>
3378
3681
  }
3379
3682
  </ul>
@@ -3436,6 +3739,10 @@ const defaultAttributeValues$2 = [
3436
3739
  name: LIST_LINKS_ATTRIBUTE,
3437
3740
  defaultValues: {},
3438
3741
  },
3742
+ {
3743
+ name: LIST_INLINE_STYLES_ATTRIBUTE,
3744
+ defaultValues: {},
3745
+ },
3439
3746
  ];
3440
3747
 
3441
3748
  /**
@@ -3482,6 +3789,12 @@ const attributeEditModeConfigs$2 = [
3482
3789
  subHeading: 'Links the items can point at. To link part of an item, wrap those words in {1|linked text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
3483
3790
  defaultValues: {},
3484
3791
  },
3792
+ {
3793
+ name: LIST_INLINE_STYLES_ATTRIBUTE,
3794
+ heading: 'Class Names & Styles',
3795
+ subHeading: 'Class names and CSS the items can apply to part of themselves, e.g. to give a few words their own colour. To apply an entry, wrap those words in {.1|styled text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
3796
+ defaultValues: {},
3797
+ },
3485
3798
  ];
3486
3799
 
3487
3800
  const displayCardEntry$2 = {
@@ -3528,6 +3841,7 @@ const displayCardEntry$2 = {
3528
3841
  const ACCORDION_SUMMARY_ATTRIBUTE = 'accordion-summary-3-resolutions';
3529
3842
  const ACCORDION_DETAIL_ATTRIBUTE = 'accordion-detail-3-resolutions';
3530
3843
  const ACCORDION_LINKS_ATTRIBUTE = 'accordion-links-3-resolutions';
3844
+ const ACCORDION_INLINE_STYLES_ATTRIBUTE = 'accordion-inline-styles-3-resolutions';
3531
3845
  const ACCORDION_OPEN_ATTRIBUTE = 'accordion-open-3-resolutions';
3532
3846
  const ACCORDION_HEADING_LEVEL_ATTRIBUTE = 'accordion-heading-level-3-resolutions';
3533
3847
  /** What a freshly dropped accordion shows before the author types anything. */
@@ -3655,6 +3969,9 @@ class RAccordionComponent {
3655
3969
  get links() {
3656
3970
  return this.componentAttributes?.[ACCORDION_LINKS_ATTRIBUTE];
3657
3971
  }
3972
+ get spanStyles() {
3973
+ return this.componentAttributes?.[ACCORDION_INLINE_STYLES_ATTRIBUTE];
3974
+ }
3658
3975
  get open() {
3659
3976
  return resolveAccordionOpen(this.componentAttributes);
3660
3977
  }
@@ -3675,12 +3992,12 @@ class RAccordionComponent {
3675
3992
  </summary>
3676
3993
  @for (paragraph of detail; track $index) {
3677
3994
  <p>
3678
- <rulecms-inline-markup [text]="paragraph" [links]="links" />
3995
+ <rulecms-inline-markup [text]="paragraph" [links]="links" [spanStyles]="spanStyles" />
3679
3996
  </p>
3680
3997
  }
3681
3998
  <ng-content />
3682
3999
  </details>
3683
- `, isInline: true, dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links"] }, { kind: "component", type: HtmlTagComponent, selector: "rulecms-html-tag", inputs: ["tag", "styleProps", "className"] }] }); }
4000
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineMarkupComponent, selector: "rulecms-inline-markup", inputs: ["text", "links", "spanStyles"] }, { kind: "component", type: HtmlTagComponent, selector: "rulecms-html-tag", inputs: ["tag", "styleProps", "className"] }] }); }
3684
4001
  }
3685
4002
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: RAccordionComponent, decorators: [{
3686
4003
  type: Component,
@@ -3701,7 +4018,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
3701
4018
  </summary>
3702
4019
  @for (paragraph of detail; track $index) {
3703
4020
  <p>
3704
- <rulecms-inline-markup [text]="paragraph" [links]="links" />
4021
+ <rulecms-inline-markup [text]="paragraph" [links]="links" [spanStyles]="spanStyles" />
3705
4022
  </p>
3706
4023
  }
3707
4024
  <ng-content />
@@ -3761,6 +4078,10 @@ const defaultAttributeValues$1 = [
3761
4078
  name: ACCORDION_LINKS_ATTRIBUTE,
3762
4079
  defaultValues: {},
3763
4080
  },
4081
+ {
4082
+ name: ACCORDION_INLINE_STYLES_ATTRIBUTE,
4083
+ defaultValues: {},
4084
+ },
3764
4085
  ];
3765
4086
 
3766
4087
  /**
@@ -3807,6 +4128,12 @@ const attributeEditModeConfigs$1 = [
3807
4128
  subHeading: 'Links the detail can point at. To link part of the detail, wrap those words in {1|linked text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
3808
4129
  defaultValues: {},
3809
4130
  },
4131
+ {
4132
+ name: ACCORDION_INLINE_STYLES_ATTRIBUTE,
4133
+ heading: 'Class Names & Styles',
4134
+ subHeading: 'Class names and CSS the detail can apply to part of itself, e.g. to give a few words their own colour. To apply an entry, wrap those words in {.1|styled text}, where the number is the position of the entry in this list counting from 1; clearing an entry leaves its position in place so the other numbers keep pointing where they did, for 3 different device resolutions',
4135
+ defaultValues: {},
4136
+ },
3810
4137
  ];
3811
4138
 
3812
4139
  const displayCardEntry$1 = {
@@ -4169,6 +4496,7 @@ const componentNameToModifyAttribute = {
4169
4496
  'embed-loading-3-resolutions': 'embed-loading-3-resolutions',
4170
4497
  'enclosing-tag-3-resolutions': 'enclosing-tag-3-resolutions',
4171
4498
  'links-3-resolutions': 'links-3-resolutions',
4499
+ 'inline-styles-3-resolutions': 'inline-styles-3-resolutions',
4172
4500
  // r-list owns its own attribute names — one component per documented
4173
4501
  // setting — but reuses existing editors wherever one fits: the multi-line
4174
4502
  // text field for its items, the numbered link list for its links, and the
@@ -4181,6 +4509,7 @@ const componentNameToModifyAttribute = {
4181
4509
  'list-start-3-resolutions': 'image-text-3-resolutions',
4182
4510
  'list-reversed-3-resolutions': 'list-reversed-3-resolutions',
4183
4511
  'list-links-3-resolutions': 'links-3-resolutions',
4512
+ 'list-inline-styles-3-resolutions': 'inline-styles-3-resolutions',
4184
4513
  // r-accordion owns its own attribute names — one component per documented
4185
4514
  // setting — and reuses existing editors for everything that fits: the
4186
4515
  // multi-line text field for the summary and the detail, and the numbered
@@ -4191,6 +4520,7 @@ const componentNameToModifyAttribute = {
4191
4520
  'accordion-open-3-resolutions': 'accordion-open-3-resolutions',
4192
4521
  'accordion-heading-level-3-resolutions': 'accordion-heading-level-3-resolutions',
4193
4522
  'accordion-links-3-resolutions': 'links-3-resolutions',
4523
+ 'accordion-inline-styles-3-resolutions': 'inline-styles-3-resolutions',
4194
4524
  // r-mount: the name is a constrained identifier and reuses the shared
4195
4525
  // plain-text editor; the props list is a repeatable key/type/value row
4196
4526
  // and needs its own editor.
@@ -4399,17 +4729,18 @@ const markLetters = (marks) => MARK_LETTERS.filter(([mark]) => marks[mark])
4399
4729
  .map(([, letter]) => letter)
4400
4730
  .join('');
4401
4731
  /**
4402
- * A span back to its marker. Text that carries neither marks nor an index is
4403
- * emitted bare: a marker with nothing to say would only be noise an author has
4404
- * to read past, and it round-trips to the same runs either way.
4732
+ * A span back to its marker. Text that carries no marks, no link and no styling
4733
+ * is emitted bare: a marker with nothing to say would only be noise an author
4734
+ * has to read past, and it round-trips to the same runs either way.
4405
4735
  */
4406
4736
  const formatSpan = (run) => {
4407
4737
  const letters = markLetters(run.marks);
4408
4738
  const index = run.index === undefined ? '' : `${run.index}`;
4409
- if (letters === '' && index === '') {
4739
+ const styleIndex = run.styleIndex === undefined ? '' : `.${run.styleIndex}`;
4740
+ if (letters === '' && index === '' && styleIndex === '') {
4410
4741
  return run.text;
4411
4742
  }
4412
- return `{${letters}${index}|${run.text}}`;
4743
+ return `{${letters}${index}${styleIndex}|${run.text}}`;
4413
4744
  };
4414
4745
  /** One run as authored text. */
4415
4746
  function formatInlineMarker(run) {
@@ -4439,5 +4770,5 @@ function serializeInlineMarkers(runs) {
4439
4770
  * Generated bundle index. Do not edit.
4440
4771
  */
4441
4772
 
4442
- export { ACCORDION_HEADING_LEVELS, BLOCK_ENCLOSING_TAGS, BUTTON_BORDER_VARIABLE, BUTTON_CLICK_HANDLER_PROP, BUTTON_CSS_VARIABLE_PREFIX, BUTTON_FILL_ACTIVE_VARIABLE, BUTTON_FILL_HOVER_VARIABLE, BUTTON_FILL_VARIABLE, BUTTON_INK_HOVER_VARIABLE, BUTTON_INK_VARIABLE, BUTTON_LIBRARY_STYLESHEET, BUTTON_RING_VARIABLE, BUTTON_SIZES, BUTTON_TONES, BUTTON_VARIANTS, ComponentIdentifiers, DEFAULT_ACCORDION_HEADING_LEVEL, DEFAULT_BUTTON_LABEL, DEFAULT_BUTTON_PRESET, DEFAULT_EMBED_LOADING, DEFAULT_ICON_POSITION, DEFAULT_LIST_KIND, EMBED_ASPECT_RATIOS, EMBED_LOADING_VALUES, EMBED_PROVIDERS, EMBED_UNSHAPED_HEIGHT, ENCLOSING_TAGS, ICON_POSITIONS, INLINE_ENCLOSING_TAGS, InlineMarkupComponent, LIST_KINDS, LIST_MARKERS, MOUNT_NAME_ATTRIBUTE, MOUNT_NAME_PATTERN, MOUNT_PROPS_ATTRIBUTE, MOUNT_PROP_TYPES, REJECTED_HREF_SCHEMES, SIZE_SCALES, TONE_PALETTES, applyButtonColumnStyles, buildButtonPresetStyles, buildButtonRenderStyles, disabledStyles as buttonDisabledStyles, cardGroups$1 as cardGroups, components, editor, formatInlineMarker, getComponentMetadataForEditing, getPreviewCardGroups, getPreviewComponents, getSourceComponentsPlugin, isAllowedEmbedUrl, isAllowedMountName, isBlockEnclosingTag, isButtonSize, isButtonTone, isButtonVariant, isEnclosingTag, isForbiddenMountPropKey, isSafeHref, manifest, normaliseHost, parseInlineLinkMarkers, parseInlineMarkers, resolveButtonPreset, resolveEmbedUrl, resolveLinkAttributes, resolveMountName, resolveMountProps, serializeInlineMarkers, stylesheet, toneCssVariable };
4773
+ export { ACCORDION_HEADING_LEVELS, BLOCK_ENCLOSING_TAGS, BUTTON_BORDER_VARIABLE, BUTTON_CLICK_HANDLER_PROP, BUTTON_CSS_VARIABLE_PREFIX, BUTTON_FILL_ACTIVE_VARIABLE, BUTTON_FILL_HOVER_VARIABLE, BUTTON_FILL_VARIABLE, BUTTON_INK_HOVER_VARIABLE, BUTTON_INK_VARIABLE, BUTTON_LIBRARY_STYLESHEET, BUTTON_RING_VARIABLE, BUTTON_SIZES, BUTTON_TONES, BUTTON_VARIANTS, ComponentIdentifiers, DEFAULT_ACCORDION_HEADING_LEVEL, DEFAULT_BUTTON_LABEL, DEFAULT_BUTTON_PRESET, DEFAULT_EMBED_LOADING, DEFAULT_ICON_POSITION, DEFAULT_LIST_KIND, EMBED_ASPECT_RATIOS, EMBED_LOADING_VALUES, EMBED_PROVIDERS, EMBED_UNSHAPED_HEIGHT, ENCLOSING_TAGS, ICON_POSITIONS, INLINE_ENCLOSING_TAGS, InlineMarkupComponent, LIST_KINDS, LIST_MARKERS, MOUNT_NAME_ATTRIBUTE, MOUNT_NAME_PATTERN, MOUNT_PROPS_ATTRIBUTE, MOUNT_PROP_TYPES, REJECTED_HREF_SCHEMES, SIZE_SCALES, TONE_PALETTES, applyButtonColumnStyles, buildButtonPresetStyles, buildButtonRenderStyles, disabledStyles as buttonDisabledStyles, cardGroups$1 as cardGroups, components, editor, formatInlineMarker, formatStyleDeclarations, getComponentMetadataForEditing, getPreviewCardGroups, getPreviewComponents, getSourceComponentsPlugin, getSpanStyleItemAt, isAllowedEmbedUrl, isAllowedMountName, isBlockEnclosingTag, isButtonSize, isButtonTone, isButtonVariant, isEmptySpanStyleItem, isEnclosingTag, isForbiddenMountPropKey, isSafeHref, isSpanStyleItem, manifest, normaliseHost, parseInlineLinkMarkers, parseInlineMarkers, parseStyleDeclarations, resolveButtonPreset, resolveClassNames, resolveEmbedUrl, resolveLinkAttributes, resolveMountName, resolveMountProps, resolveSpanStyleAttributes, serializeInlineMarkers, stylesheet, toneCssVariable };
4443
4774
  //# sourceMappingURL=rulecms-source-components-angular.mjs.map