@gjsify/adwaita-nativescript 0.64.12 → 0.64.13

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 (78) hide show
  1. package/lib/esm/builder/index.js +1 -1
  2. package/lib/esm/widgets/adw-about-dialog.js +3 -3
  3. package/lib/esm/widgets/adw-action-row.js +1 -1
  4. package/lib/esm/widgets/adw-avatar.js +1 -1
  5. package/lib/esm/widgets/adw-banner.js +1 -1
  6. package/lib/esm/widgets/adw-bottom-sheet.js +1 -1
  7. package/lib/esm/widgets/adw-carousel.js +1 -1
  8. package/lib/esm/widgets/adw-combo-row.js +1 -1
  9. package/lib/esm/widgets/adw-data-grid.js +1 -1
  10. package/lib/esm/widgets/adw-dialog.js +1 -1
  11. package/lib/esm/widgets/adw-image-button.js +1 -1
  12. package/lib/esm/widgets/adw-navigation-page.js +1 -1
  13. package/lib/esm/widgets/adw-navigation-split-view.js +1 -1
  14. package/lib/esm/widgets/adw-navigation-view.js +1 -1
  15. package/lib/esm/widgets/adw-overlay-split-view.js +1 -1
  16. package/lib/esm/widgets/adw-password-entry-row.js +1 -1
  17. package/lib/esm/widgets/adw-preferences-dialog.js +1 -1
  18. package/lib/esm/widgets/adw-preferences-page.js +1 -1
  19. package/lib/esm/widgets/adw-sidebar.js +1 -1
  20. package/lib/esm/widgets/adw-slider-row.js +1 -1
  21. package/lib/esm/widgets/adw-spin-row.js +1 -1
  22. package/lib/esm/widgets/adw-split-button.js +1 -1
  23. package/lib/esm/widgets/adw-tab-view.js +1 -1
  24. package/lib/esm/widgets/adw-toggle-group.js +1 -1
  25. package/lib/esm/widgets/adw-toolbar-view.js +1 -1
  26. package/lib/esm/widgets/adw-view-switcher-bar.js +1 -1
  27. package/lib/esm/widgets/adw-wrap-box.js +1 -1
  28. package/lib/esm/widgets/construct-props.js +1 -1
  29. package/lib/esm/widgets/gtk-action-bar.js +1 -1
  30. package/lib/esm/widgets/gtk-box.js +1 -1
  31. package/lib/esm/widgets/gtk-button.js +1 -1
  32. package/lib/esm/widgets/gtk-check-button.js +1 -1
  33. package/lib/esm/widgets/gtk-drawing-area.js +1 -1
  34. package/lib/esm/widgets/gtk-drop-down.js +1 -1
  35. package/lib/esm/widgets/gtk-entry.js +1 -1
  36. package/lib/esm/widgets/gtk-grid.js +1 -1
  37. package/lib/esm/widgets/gtk-icon-size.js +1 -1
  38. package/lib/esm/widgets/gtk-image.js +1 -1
  39. package/lib/esm/widgets/gtk-label.js +2 -2
  40. package/lib/esm/widgets/gtk-list-box.js +1 -1
  41. package/lib/esm/widgets/gtk-menu-button.js +1 -1
  42. package/lib/esm/widgets/gtk-revealer.js +1 -1
  43. package/lib/esm/widgets/gtk-scrolled-window.js +1 -1
  44. package/lib/esm/widgets/gtk-stack.js +1 -1
  45. package/lib/esm/widgets/gtk-text-view.js +1 -1
  46. package/lib/esm/widgets/gtk-toggle-button.js +1 -1
  47. package/lib/esm/widgets/gtk-window.js +1 -1
  48. package/lib/esm/widgets/label-markup.js +1 -0
  49. package/lib/esm/widgets/scrolled-window-policy.js +1 -1
  50. package/lib/esm/widgets/signals.js +1 -1
  51. package/lib/esm/widgets/single-child-base.js +1 -1
  52. package/lib/esm/widgets/single-child-cell.js +1 -0
  53. package/lib/esm/widgets/stack-page.js +1 -1
  54. package/lib/esm/widgets/transition-type.js +1 -1
  55. package/lib/esm/widgets/view-stack-page.js +1 -1
  56. package/lib/esm/widgets/widget-layout.js +1 -1
  57. package/lib/esm/widgets/xml-values.js +1 -1
  58. package/lib/types/builder/index.d.ts +4 -2
  59. package/lib/types/clamped-bin.spec.d.ts +2 -0
  60. package/lib/types/combo-row-properties.spec.d.ts +2 -0
  61. package/lib/types/default-translate.spec.d.ts +1 -0
  62. package/lib/types/drop-down-properties.spec.d.ts +2 -0
  63. package/lib/types/index.d.ts +1 -1
  64. package/lib/types/label-markup.spec.d.ts +1 -0
  65. package/lib/types/single-child-cell.spec.d.ts +2 -0
  66. package/lib/types/testing/ns-core.d.mts +115 -3
  67. package/lib/types/widgets/adw-banner.d.ts +2 -2
  68. package/lib/types/widgets/adw-combo-row.d.ts +42 -26
  69. package/lib/types/widgets/gtk-drop-down.d.ts +41 -33
  70. package/lib/types/widgets/gtk-label.d.ts +29 -8
  71. package/lib/types/widgets/index.d.ts +1 -2
  72. package/lib/types/widgets/label-markup.d.ts +43 -0
  73. package/lib/types/widgets/signals.d.ts +54 -0
  74. package/lib/types/widgets/single-child-base.d.ts +2 -0
  75. package/lib/types/widgets/single-child-cell.d.ts +8 -0
  76. package/lib/types/widgets/xml-values.d.ts +12 -0
  77. package/package.json +12 -12
  78. package/src/testing/ns-core.mts +231 -3
@@ -21,6 +21,43 @@ type Declared<P> = P & {
21
21
  * here, since a write that resolves to the value already held is not a change.
22
22
  */
23
23
  readonly normalize?: (value: unknown, self: object) => unknown;
24
+ /**
25
+ * Replaces the equal-write check for this property: `written` is the value as the caller
26
+ * spelled it, `next` what {@link Declared.normalize} made of it, `held` what the store holds
27
+ * — and the answer decides whether the write notifies at all.
28
+ *
29
+ * WHY. A C setter guards on `g_strcmp0 (value, adw_…_get_…(self))`, against the GETTER
30
+ * READBACK rather than the stored field. Where storage normalises, the two differ and the
31
+ * guard can never fire: `GtkImage` turns an empty icon name into "no icon", so writing `''`
32
+ * to a button that already reads back `null` notifies EVERY time (measured on GJS 1.88.1 /
33
+ * libadwaita 1.9.4). Comparing the held value says the opposite, and only the property whose
34
+ * `normalize` collapses distinct writes onto one held value can tell the difference.
35
+ *
36
+ * RUNS ON BOTH LEGS. It used to run on the pure one alone, on the reasoning that "there the
37
+ * guard IS the C one" — which holds for a widget wrapping a real C object and not for a
38
+ * foreign-parent class, which has no C setter at all. On route D node-gi's generated setter
39
+ * is an identity guard and nothing more (`installForeignPropertyAccessors` in `gi.js`), so a
40
+ * property that left this to the real leg got the identity answer there and the measured one
41
+ * off-device — the per-leg difference this seam exists to close.
42
+ */
43
+ readonly changed?: (next: unknown, held: unknown, written: unknown, self: object) => boolean;
44
+ /**
45
+ * The rest of the C setter: runs once this property's own `notify::<name>` is out, so a
46
+ * setter that also drives OTHER properties emits in the order C does. For a property with
47
+ * {@link ForeignScalarProperty.explicitNotify} it runs INSTEAD of that notify, and owes every
48
+ * emission the write makes, its own included.
49
+ *
50
+ * WHY IT IS NOT `normalize`. A normalizer runs AHEAD of the store, so a cross-write made
51
+ * there notifies before the property that caused it. `Adw.SplitButton` measures the other way
52
+ * round — `label = 'Open'` on an icon button emits `label` first and the slots it clears
53
+ * after — and the two cannot both be one hook. The SHAPE of a value still belongs to
54
+ * `normalize`; what the write does to its siblings belongs here.
55
+ *
56
+ * RUNS ON BOTH LEGS, for the reason {@link Declared.changed} states: the foreign-parent class
57
+ * route D registers has no C setter to do this instead. `adw-split-button`'s label/icon-name
58
+ * mutual clear is the one that was silently missing on Android because of it.
59
+ */
60
+ readonly afterNotify?: (self: object) => void;
24
61
  };
25
62
  /** A declared property of any kind, with its optional {@link Declared.normalize}. */
26
63
  export type DeclaredProperty = Declared<ForeignScalarProperty> | Declared<ForeignObjectProperty>;
@@ -194,4 +231,21 @@ export declare function withProperties<TBase extends ObservableConstructor>(Base
194
231
  /** Only what this class ADDS; `emittedSignalsOf` merges it with the base's. */
195
232
  readonly emittedSignals: readonly string[];
196
233
  }) & TBase;
234
+ /**
235
+ * Write a declared property's held value WITHOUT notifying, and without running `normalize`,
236
+ * `changed` or `afterNotify` — the PUSH half of a property whose widget owns the emission.
237
+ *
238
+ * WHY A WIDGET NEEDS IT. A property whose value also moves from UNDERNEATH — the selection of an
239
+ * `Adw.ComboRow` when its model is replaced, or when the held model splices — cannot be kept in
240
+ * sync by writing it, because the write is the thing that emits and the emission order is
241
+ * measured and is not the order a write would produce. `model = null` is the sharpest case:
242
+ * `selected` goes to the sentinel and `selected-item` to null with NO notify at all, and `model`
243
+ * alone notifies. So the widget pushes the new value in here and emits what it owes with
244
+ * `notify('<name>')`, in the order it measured.
245
+ *
246
+ * Only a {@link isSeamOwned} property may be pushed, and a name that is not one THROWS rather
247
+ * than writing a store route D would never read back: there the value lives in the GObject, and
248
+ * a silent push would be correct off-device and invisible on the device.
249
+ */
250
+ export declare function pushDeclared(self: object, name: string, value: unknown): void;
197
251
  export {};
@@ -5,6 +5,8 @@ export declare abstract class AdwSingleChildBase extends AdwStyledLayoutBase {
5
5
  constructor();
6
6
  private _columnIsStar;
7
7
  onLoaded(): void;
8
+ onUnloaded(): void;
9
+ private _onWidthChangeSyncColumn;
8
10
  private _syncColumnToAlignment;
9
11
  /** The child, or `null`. */
10
12
  get child(): View | null;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whether the single cell of a one-child container is `star` rather than `auto`.
3
+ *
4
+ * `star` when the container's width is settled — either because it stretches to its parent,
5
+ * or because a definite `width` was written on it. `auto` otherwise, so the container can
6
+ * still report its child's natural width and be placed by its alignment.
7
+ */
8
+ export declare function singleChildCellIsStar(horizontalAlignment: unknown, width: unknown): boolean;
@@ -6,6 +6,18 @@
6
6
  * caller can keep validating a RANGE without also having to re-check the type.
7
7
  */
8
8
  export declare function xmlNumber(value: unknown, fallback: number): number;
9
+ /**
10
+ * The integer GtkBuilder reads out of an enum property's value, or `undefined` when the
11
+ * value is a nick or name.
12
+ *
13
+ * `_gtk_builder_enum_from_string` (gtk/gtkbuilder.c) tries the number FIRST
14
+ * (`g_ascii_strtoull (string, &endptr, 0)`, so base 0: decimal, `0x` hex, leading-`0` octal)
15
+ * and only then the nick and the name — which is why `halign="1"` and `halign="start"` are
16
+ * the same attribute in a `.ui` file. A JS number is the same reading, already parsed.
17
+ * GTK also accepts a numeric PREFIX (`"2abc"` is 2); the whole string must be a number here,
18
+ * so a nick that merely starts with a digit stays a nick.
19
+ */
20
+ export declare function gtkBuilderInteger(value: unknown): number | undefined;
9
21
  /**
10
22
  * An attribute as a boolean.
11
23
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/adwaita-nativescript",
3
- "version": "0.64.12",
3
+ "version": "0.64.13",
4
4
  "description": "Native Adwaita widgets for the NativeScript runtime — real NativeScript components (StackLayout/GridLayout/Switch/Label) styled like Libadwaita, not a webview",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -73,15 +73,15 @@
73
73
  "design-system"
74
74
  ],
75
75
  "dependencies": {
76
- "@gjsify/adwaita-core": "^0.64.12",
77
- "@gjsify/adwaita-fonts": "^0.64.12",
78
- "@gjsify/adwaita-icons": "^0.64.12",
79
- "@gjsify/canvas2d-core": "^0.64.12",
80
- "@gjsify/native-platform": "^0.64.12"
76
+ "@gjsify/adwaita-core": "^0.64.13",
77
+ "@gjsify/adwaita-fonts": "^0.64.13",
78
+ "@gjsify/adwaita-icons": "^0.64.13",
79
+ "@gjsify/canvas2d-core": "^0.64.13",
80
+ "@gjsify/native-platform": "^0.64.13"
81
81
  },
82
82
  "peerDependencies": {
83
83
  "@nativescript/core": "*",
84
- "@gjsify/app-settings": "^0.64.12"
84
+ "@gjsify/app-settings": "^0.64.13"
85
85
  },
86
86
  "peerDependenciesMeta": {
87
87
  "@nativescript/core": {
@@ -92,11 +92,11 @@
92
92
  }
93
93
  },
94
94
  "devDependencies": {
95
- "@gjsify/app-settings": "^0.64.12",
96
- "@gjsify/blueprint": "^0.64.12",
97
- "@gjsify/cli": "^0.64.12",
98
- "@gjsify/unit": "^0.64.12",
99
- "@gjsify/vite-plugin-blueprint": "^0.64.12",
95
+ "@gjsify/app-settings": "^0.64.13",
96
+ "@gjsify/blueprint": "^0.64.13",
97
+ "@gjsify/cli": "^0.64.13",
98
+ "@gjsify/unit": "^0.64.13",
99
+ "@gjsify/vite-plugin-blueprint": "^0.64.13",
100
100
  "@types/node": "^25.9.2",
101
101
  "typescript": "^6.0.3"
102
102
  },
@@ -173,8 +173,12 @@ function unloadView(view: View): void {
173
173
  */
174
174
  export class Style extends Observable {
175
175
  direction?: 'ltr' | 'rtl' | null = null;
176
+ /** In DIPs, and unset until written: `fontSizeProperty` carries no `defaultValue`. */
177
+ fontSize?: number;
176
178
  readonly viewRef: WeakRef<View>;
177
179
  #visibility = 'visible';
180
+ #width: number | string = 'auto';
181
+ #height: number | string = 'auto';
178
182
 
179
183
  constructor(view: View) {
180
184
  super();
@@ -191,6 +195,33 @@ export class Style extends Observable {
191
195
  this.#visibility = value;
192
196
  this.notify({ eventName: 'visibilityChange', object: this, propertyName: 'visibility', value, oldValue });
193
197
  }
198
+
199
+ /**
200
+ * The inline width, in DIPs or as a percentage string, `'auto'` until written — and the
201
+ * `widthChange` a one-child container listens for. A CSS property notifies on the style
202
+ * and not on the view, which is the whole reason this pair lives here.
203
+ */
204
+ get width(): number | string {
205
+ return this.#width;
206
+ }
207
+
208
+ set width(value: number | string) {
209
+ const oldValue = this.#width;
210
+ if (value === oldValue) return;
211
+ this.#width = value;
212
+ this.notify({ eventName: 'widthChange', object: this, propertyName: 'width', value, oldValue });
213
+ }
214
+
215
+ get height(): number | string {
216
+ return this.#height;
217
+ }
218
+
219
+ set height(value: number | string) {
220
+ const oldValue = this.#height;
221
+ if (value === oldValue) return;
222
+ this.#height = value;
223
+ this.notify({ eventName: 'heightChange', object: this, propertyName: 'height', value, oldValue });
224
+ }
194
225
  }
195
226
 
196
227
  export class View extends Observable {
@@ -232,8 +263,32 @@ export class View extends Observable {
232
263
  * missing so the first `className` write died inside a constructor.
233
264
  */
234
265
  readonly cssClasses: Set<string> = new Set();
235
- width: number | string = 'auto';
236
- height: number | string = 'auto';
266
+ /**
267
+ * `'auto'` until written, and HELD ON THE {@link Style} as `widthProperty` /
268
+ * `heightProperty` are (`ui/styling/style-properties.ts` registers both on `Style`;
269
+ * `view-common.ts`'s `get width()` is a one-line forward to `this.style.width`).
270
+ *
271
+ * This double held them as plain fields on the view, which made it STRUCTURALLY BLIND to
272
+ * the one thing a reader needs from them: the notification. `AdwSingleChildBase` learns
273
+ * that a parent settled its width from `widthChange` on the style — on a device the only
274
+ * place it is emitted — and against a field there is no event at all, so a spec would
275
+ * have passed while the widget sat waiting for a message that never came.
276
+ */
277
+ get width(): number | string {
278
+ return this._style.width;
279
+ }
280
+
281
+ set width(value: number | string) {
282
+ this._style.width = value;
283
+ }
284
+
285
+ get height(): number | string {
286
+ return this._style.height;
287
+ }
288
+
289
+ set height(value: number | string) {
290
+ this._style.height = value;
291
+ }
237
292
  horizontalAlignment = 'stretch';
238
293
  verticalAlignment = 'stretch';
239
294
  /**
@@ -663,8 +718,181 @@ export class GridLayout extends LayoutBase {
663
718
  }
664
719
  }
665
720
 
721
+ /**
722
+ * A `Span`'s style — NOT a {@link View}'s {@link Style}.
723
+ *
724
+ * `Span extends ViewBase` upstream, so it has none of `View`'s shortcuts and carries only
725
+ * what a span can be given. Every member is unset until written, which is what the font
726
+ * properties answer on a device: `fontSizeProperty` and `fontFamilyProperty` are registered
727
+ * with no `defaultValue` (`ui/styling/style-properties.ts`).
728
+ */
729
+ export class SpanStyle extends Observable {
730
+ fontFamily: string | undefined;
731
+ fontSize: number | undefined;
732
+ fontStyle: string | undefined;
733
+ fontWeight: string | undefined;
734
+ textDecoration: string | undefined;
735
+ verticalAlignment: string | undefined;
736
+ }
737
+
738
+ /**
739
+ * One styled stretch of a {@link FormattedString}.
740
+ *
741
+ * The font and decoration members are ACCESSORS OVER `style`, as `ui/text-base/span.ts`
742
+ * has them — a double that made them own fields could not report a widget that wrote one
743
+ * and expected the other to see it.
744
+ */
745
+ export class Span extends Observable {
746
+ static readonly linkTapEvent = 'linkTap';
747
+
748
+ readonly _style: SpanStyle = new SpanStyle();
749
+
750
+ get style(): SpanStyle {
751
+ return this._style;
752
+ }
753
+
754
+ /** NativeScript unescapes the two whitespace spellings a markup file can carry. */
755
+ private _text = '';
756
+
757
+ get text(): string {
758
+ return this._text;
759
+ }
760
+
761
+ set text(value: string) {
762
+ this._text = String(value ?? '')
763
+ .replace(/\\n/g, '\n')
764
+ .replace(/\\t/g, '\t');
765
+ }
766
+
767
+ get fontFamily(): string | undefined {
768
+ return this._style.fontFamily;
769
+ }
770
+
771
+ set fontFamily(value: string | undefined) {
772
+ this._style.fontFamily = value;
773
+ }
774
+
775
+ get fontSize(): number | undefined {
776
+ return this._style.fontSize;
777
+ }
778
+
779
+ set fontSize(value: number | undefined) {
780
+ this._style.fontSize = value;
781
+ }
782
+
783
+ get fontStyle(): string | undefined {
784
+ return this._style.fontStyle;
785
+ }
786
+
787
+ set fontStyle(value: string | undefined) {
788
+ this._style.fontStyle = value;
789
+ }
790
+
791
+ get fontWeight(): string | undefined {
792
+ return this._style.fontWeight;
793
+ }
794
+
795
+ set fontWeight(value: string | undefined) {
796
+ this._style.fontWeight = value;
797
+ }
798
+
799
+ get textDecoration(): string | undefined {
800
+ return this._style.textDecoration;
801
+ }
802
+
803
+ set textDecoration(value: string | undefined) {
804
+ this._style.textDecoration = value;
805
+ }
806
+
807
+ /**
808
+ * DERIVED, never written — upstream flips it from `addEventListener` /
809
+ * `removeEventListener` through `_setTappable(this.hasListeners(Span.linkTapEvent))`,
810
+ * and `setSpanModifiers` attaches the native `ClickableSpan` only for a tappable span.
811
+ * So subscribing IS what makes a span a link, and this reads the listeners rather than
812
+ * a flag a caller could set without one.
813
+ */
814
+ get tappable(): boolean {
815
+ return (listenersOf(this).get(Span.linkTapEvent)?.length ?? 0) > 0;
816
+ }
817
+ }
818
+
819
+ /**
820
+ * The `ObservableArray<Span>` behind {@link FormattedString.spans}, as the four members
821
+ * that collection really offers.
822
+ *
823
+ * NOT A PLAIN ARRAY, and a double that used one would be the easier lie: `ObservableArray`
824
+ * reads by `getItem(pos)` and has no index access at all, so `spans[0]` is `undefined` on a
825
+ * device. A spec written against an array would pass here and read nothing there.
826
+ */
827
+ export class SpanList {
828
+ private readonly _items: Span[] = [];
829
+
830
+ get length(): number {
831
+ return this._items.length;
832
+ }
833
+
834
+ push(span: Span): number {
835
+ return this._items.push(span);
836
+ }
837
+
838
+ /** A negative `pos` counts from the end, as upstream's does. */
839
+ getItem(pos: number): Span {
840
+ return this._items[pos < 0 ? this._items.length + pos : pos]!;
841
+ }
842
+
843
+ *[Symbol.iterator](): Generator<Span, void, unknown> {
844
+ yield* this._items;
845
+ }
846
+ }
847
+
848
+ /** The rich-text value of a {@link Label} — a FLAT list of spans, no nesting. */
849
+ export class FormattedString extends Observable {
850
+ readonly spans: SpanList = new SpanList();
851
+
852
+ /** `formatted-string.ts`: the span texts concatenated, nothing between them. */
853
+ toString(): string {
854
+ let text = '';
855
+ for (const span of this.spans) text += span.text;
856
+ return text;
857
+ }
858
+ }
859
+
666
860
  export class Label extends View {
667
- text = '';
861
+ private _text = '';
862
+ private _formattedText: FormattedString | null | undefined;
863
+
864
+ /**
865
+ * A write is a NO-OP while {@link formattedText} is set, as on a device:
866
+ * `[textProperty.setNative]` returns early when the label carries rich text
867
+ * (`ui/text-base/index.android.ts:192-195`), so the spans are what shows and this
868
+ * property is whatever the spans last said.
869
+ */
870
+ get text(): string {
871
+ return this._text;
872
+ }
873
+
874
+ set text(value: string) {
875
+ if (this._formattedText === null || this._formattedText === undefined) this._text = value;
876
+ }
877
+
878
+ /**
879
+ * Unset until something writes it — `formattedTextProperty` carries no `defaultValue`
880
+ * (`ui/text-base/text-base-common.ts:194`), so a label that was never given rich text
881
+ * reads `undefined`, not `null`.
882
+ */
883
+ get formattedText(): FormattedString | null | undefined {
884
+ return this._formattedText;
885
+ }
886
+
887
+ set formattedText(value: FormattedString | null | undefined) {
888
+ this._formattedText = value;
889
+ // The device re-derives the plain text from the spans on BOTH legs:
890
+ // `[formattedTextProperty.setNative]` calls
891
+ // `textProperty.nativeValueChange(this, value?.toString() ?? '')`, which is why a
892
+ // `text` written BEFORE the clear would be wiped to the empty string.
893
+ this._text = value?.toString() ?? '';
894
+ }
895
+
668
896
  textWrap = false;
669
897
  textAlignment = 'initial';
670
898
  textOverflow = 'initial';