@_linked/primitives 1.3.1 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @\_linked/primitives
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#39](https://github.com/linked-fw/primitives/pull/39) [`8130aa3`](https://github.com/linked-fw/primitives/commit/8130aa3f02682f1ced21d8767a6049d53a2744a6) Thanks [@flyon](https://github.com/flyon)! - `ConfirmDialog`'s `tone="danger"` was visually inert; `Button` now has a `danger` colour
8
+
9
+ `tone="danger"` applied a `.danger` class of ConfirmDialog's own stylesheet to the confirm
10
+ button. That class has specificity 0-1-0, while `Button` colours itself with compound
11
+ selectors — `.Root.primary.solid`, 0-3-0 — so the override never won. Measured computed
12
+ `background-color` on the danger button was `rgb(37, 99, 235)` (`--button-primary-bg`),
13
+ identical to a default confirm button. Every destructive confirmation therefore rendered in
14
+ the primary colour: the class was on the element, and the colour was not.
15
+
16
+ Colour is a `Button` prop, so that is now how tone travels: `Button`'s `color` union gains
17
+ `'danger'` with rules for all four variants, reading `--button-danger-*` and falling back to
18
+ the `--intent-danger-*` family, and `ConfirmDialog` passes `color="danger"` instead of a
19
+ class. Computed `background-color` is now `rgb(239, 68, 68)` (`--intent-danger-icon`), with
20
+ `--intent-danger-text` on hover.
21
+
22
+ `color="danger"` is new public API on `Button`. The `--confirm-dialog-danger-bg`,
23
+ `--confirm-dialog-danger-bg-hover`, `--confirm-dialog-danger-border` and
24
+ `--confirm-dialog-danger-text` custom properties are gone — they were read only by the rule
25
+ that never applied, so nothing that worked has been removed. Re-skin the destructive button
26
+ through `--button-danger-bg` / `--button-danger-bg-hover` / `--button-danger-border` /
27
+ `--button-danger-color`, or through `--intent-danger-*` to move every danger affordance at
28
+ once.
29
+
30
+ ## 1.3.2
31
+
32
+ ### Patch Changes
33
+
34
+ - [#37](https://github.com/linked-fw/primitives/pull/37) [`d7803e0`](https://github.com/linked-fw/primitives/commit/d7803e09f303015f6ba4f14ef3d610e7cda0c289) Thanks [@flyon](https://github.com/flyon)! - Give this package its own linked identity
35
+
36
+ `src/package.ts` re-exported core's decorators verbatim and declared
37
+ `packageName` as a bare literal that nothing was bound to, so everything
38
+ declared here registered under `@_linked/core`: the `radix` ontology landed in
39
+ core's entry in the package tree, and the next `@linkedShape` added here would
40
+ silently have been given a `.../shape/core/...` IRI. `Server.call` routes on
41
+ the package name a shape carries, so one naming the wrong package is simply
42
+ unreachable.
43
+
44
+ **No shape IRI changes**, because no shape is decorated here today — that is
45
+ why this is a patch and not a minor. The only visible change is where the
46
+ `radix` ontology is registered in the package tree.
47
+
48
+ Same defect and same fix as linked-fw/owl#26, where shapes did exist and the
49
+ IRIs did change.
50
+
3
51
  ## 1.3.1
4
52
 
5
53
  ### Patch Changes
@@ -2,7 +2,13 @@ import * as React from 'react';
2
2
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3
3
  asChild?: boolean;
4
4
  variant?: 'solid' | 'outline' | 'ghost' | 'link';
5
- color?: 'primary' | 'secondary' | 'tertiary';
5
+ /**
6
+ * Which colour family the button speaks in. `danger` is the destructive one — a
7
+ * statement about consequence, and a Button prop rather than a class a caller adds,
8
+ * because the colour classes here are compound (`.Root.primary.solid`) and a bare
9
+ * class handed in through `className` loses the cascade to them every time.
10
+ */
11
+ color?: 'primary' | 'secondary' | 'tertiary' | 'danger';
6
12
  size?: 'small' | 'medium' | 'large' | 'default';
7
13
  }
8
14
  declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/components/Button.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,4EAAwC;AACxC,gEAAqD;AACrD,qDAA4C;AAU5C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAC7B,CACE,EAOC,EACD,GAAG,EACH,EAAE;QATF,EACE,SAAS,EACT,OAAO,GAAG,OAAO,EACjB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,SAAS,EAChB,OAAO,GAAG,KAAK,OAEhB,EADI,KAAK,cANV,oDAOC,CADS;IAIV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC;IAElC,OAAO,CACL,oBAAC,IAAI,kBACH,SAAS,EAAE,IAAA,eAAE,EACX,2BAAK,CAAC,IAAI,EACV,2BAAK,CAAC,OAAO,CAAC,EACd,2BAAK,CAAC,KAAK,CAAC,EACZ,2BAAK,CAAC,IAAI,CAAC,EACX,UAAU,IAAI,2BAAK,CAAC,QAAQ,EAC5B,SAAS,CACV,EACD,GAAG,EAAE,GAAG,IACJ,KAAK,EACT,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AAGO,wBAAM;AAFf,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/components/Button.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,4EAAwC;AACxC,gEAAqD;AACrD,qDAA4C;AAgB5C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAC7B,CACE,EAOC,EACD,GAAG,EACH,EAAE;QATF,EACE,SAAS,EACT,OAAO,GAAG,OAAO,EACjB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,SAAS,EAChB,OAAO,GAAG,KAAK,OAEhB,EADI,KAAK,cANV,oDAOC,CADS;IAIV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC;IAElC,OAAO,CACL,oBAAC,IAAI,kBACH,SAAS,EAAE,IAAA,eAAE,EACX,2BAAK,CAAC,IAAI,EACV,2BAAK,CAAC,OAAO,CAAC,EACd,2BAAK,CAAC,KAAK,CAAC,EACZ,2BAAK,CAAC,IAAI,CAAC,EACX,UAAU,IAAI,2BAAK,CAAC,QAAQ,EAC5B,SAAS,CACV,EACD,GAAG,EAAE,GAAG,IACJ,KAAK,EACT,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AAGO,wBAAM;AAFf,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC"}
@@ -220,6 +220,56 @@
220
220
  }
221
221
  }
222
222
 
223
+ /* Destructive. Reads the intent family rather than a red primitive, so an app that
224
+ restyles what "danger" looks like restyles this too; the literal is the last-resort
225
+ fallback for a theme that defines neither, same contract as Badge. */
226
+ &.danger {
227
+ &.solid {
228
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
229
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
230
+ border-color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
231
+
232
+ &:hover {
233
+ background: var(
234
+ --button-danger-bg-hover,
235
+ var(--intent-danger-text, #b91c1c)
236
+ );
237
+ color: var(--button-danger-color-hover, var(--control-text-contrast, #fff));
238
+ }
239
+ }
240
+
241
+ &.outline {
242
+ background: transparent;
243
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
244
+ border: 1px solid
245
+ var(--button-danger-border, var(--intent-danger-border, #dc2626));
246
+
247
+ &:hover {
248
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
249
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
250
+ }
251
+ }
252
+
253
+ &.ghost {
254
+ background: transparent;
255
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
256
+
257
+ &:hover {
258
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
259
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
260
+ }
261
+ }
262
+
263
+ &.link {
264
+ background: transparent;
265
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
266
+
267
+ &:hover {
268
+ text-decoration: underline;
269
+ }
270
+ }
271
+ }
272
+
223
273
  /* size: 'small' | 'medium' | 'large' || 'default'*/
224
274
  &.small {
225
275
  padding: var(--button-padding-sm-y) var(--button-padding-sm-x);
@@ -9,7 +9,10 @@ export interface ConfirmDialogProps {
9
9
  cancelText?: string;
10
10
  /**
11
11
  * `danger` for a destructive action. Not a colour — a statement about consequence, which
12
- * is what `--intent-danger-*` exists to express.
12
+ * is what `--intent-danger-*` exists to express. It reaches the confirm button as
13
+ * `<Button color="danger">`, so an app re-skins it through `--button-danger-*` (or the
14
+ * `--intent-danger-*` family behind it) rather than through a `--confirm-dialog-danger-*`
15
+ * of its own.
13
16
  */
14
17
  tone?: 'default' | 'danger';
15
18
  /** The confirm action is in flight: both buttons lock. */
@@ -63,6 +63,6 @@ function ConfirmDialog({ open, onOpenChange, onConfirm, title, message, confirmT
63
63
  React.createElement(AlertDialog_js_1.AlertDialog.Footer, null,
64
64
  React.createElement(AlertDialog_js_1.AlertDialog.Cancel, { asChild: true },
65
65
  React.createElement(Button_js_1.Button, { variant: "ghost", disabled: isLoading }, cancelText)),
66
- React.createElement(Button_js_1.Button, { className: (0, ClassNames_1.cl)(tone === 'danger' && ConfirmDialog_module_css_1.default.danger), onClick: onConfirm, disabled: isLoading || confirmDisabled }, isLoading && loadingContent ? loadingContent : confirmText)))));
66
+ React.createElement(Button_js_1.Button, { color: tone === 'danger' ? 'danger' : 'primary', onClick: onConfirm, disabled: isLoading || confirmDisabled }, isLoading && loadingContent ? loadingContent : confirmText)))));
67
67
  }
68
68
  //# sourceMappingURL=ConfirmDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,sCAiDC;AA3FD,6CAA+B;AAC/B,gEAAmD;AACnD,qDAA6C;AAC7C,2CAAmC;AACnC,0FAA+C;AA0B/C;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,KAAK,EACL,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,UAAU,GAAG,QAAQ,EACrB,IAAI,GAAG,SAAS,EAChB,SAAS,GAAG,KAAK,EACjB,eAAe,GAAG,KAAK,EACvB,cAAc,EACd,IAAI,GAAG,KAAK,EACZ,SAAS,GACU;IACnB,OAAO,CACL,oBAAC,4BAAW,CAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY;QACtD,oBAAC,4BAAW,CAAC,OAAO,IAClB,SAAS,EAAE,IAAA,eAAE,EAAC,kCAAK,CAAC,OAAO,EAAE,IAAI,IAAI,kCAAK,CAAC,IAAI,EAAE,SAAS,CAAC;YAE3D,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,KAAK,QAAE,KAAK,CAAqB;gBAI9C,oBAAC,4BAAW,CAAC,WAAW,IAAC,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAC1D,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAM,OAAO,CAAO,CACrC,CACP;YACrB,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,MAAM,IAAC,OAAO;oBACzB,oBAAC,kBAAM,IAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,SAAS,IACxC,UAAU,CACJ,CACU;gBAIrB,oBAAC,kBAAM,IACL,SAAS,EAAE,IAAA,eAAE,EAAC,IAAI,KAAK,QAAQ,IAAI,kCAAK,CAAC,MAAM,CAAC,EAChD,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,SAAS,IAAI,eAAe,IAErC,SAAS,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACpD,CACU,CACD,CACL,CACpB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,sCAqDC;AAlGD,6CAA+B;AAC/B,gEAAmD;AACnD,qDAA6C;AAC7C,2CAAmC;AACnC,0FAA+C;AA6B/C;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,KAAK,EACL,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,UAAU,GAAG,QAAQ,EACrB,IAAI,GAAG,SAAS,EAChB,SAAS,GAAG,KAAK,EACjB,eAAe,GAAG,KAAK,EACvB,cAAc,EACd,IAAI,GAAG,KAAK,EACZ,SAAS,GACU;IACnB,OAAO,CACL,oBAAC,4BAAW,CAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY;QACtD,oBAAC,4BAAW,CAAC,OAAO,IAClB,SAAS,EAAE,IAAA,eAAE,EAAC,kCAAK,CAAC,OAAO,EAAE,IAAI,IAAI,kCAAK,CAAC,IAAI,EAAE,SAAS,CAAC;YAE3D,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,KAAK,QAAE,KAAK,CAAqB;gBAI9C,oBAAC,4BAAW,CAAC,WAAW,IAAC,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAC1D,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAM,OAAO,CAAO,CACrC,CACP;YACrB,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,MAAM,IAAC,OAAO;oBACzB,oBAAC,kBAAM,IAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,SAAS,IACxC,UAAU,CACJ,CACU;gBAQrB,oBAAC,kBAAM,IACL,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAC/C,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,SAAS,IAAI,eAAe,IAErC,SAAS,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACpD,CACU,CACD,CACL,CACpB,CAAC;AACJ,CAAC"}
@@ -8,15 +8,3 @@
8
8
  .wide {
9
9
  max-width: var(--confirm-dialog-width-wide, 56rem);
10
10
  }
11
-
12
- /* Destructive confirmation. Reads the intent family rather than a red primitive, so an app
13
- that restyles what "danger" looks like restyles this too. */
14
- .danger {
15
- background: var(--confirm-dialog-danger-bg, var(--intent-danger-icon));
16
- border-color: var(--confirm-dialog-danger-border, var(--intent-danger-border));
17
- color: var(--confirm-dialog-danger-text, var(--color-white));
18
- }
19
-
20
- .danger:hover:not(:disabled) {
21
- background: var(--confirm-dialog-danger-bg-hover, var(--intent-danger-text));
22
- }
@@ -1,3 +1,19 @@
1
- import { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule } from '@_linked/core/package';
2
- export declare const packageName: string;
3
- export { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule, };
1
+ /**
2
+ * This package's own linked identity.
3
+ *
4
+ * It used to re-export core's decorators verbatim and declare `packageName`
5
+ * as a bare literal that nothing was bound to. The decorators therefore came
6
+ * from `corePackage`, so everything declared here registered under
7
+ * `@_linked/core`: this package's ontology landed in core's entry in the
8
+ * package tree, and the next shape added here would silently have been given a
9
+ * `.../shape/core/...` IRI. That IRI is persisted data and `Server.call`
10
+ * routes on the package name inside it, so a shape naming the wrong package is
11
+ * simply unreachable.
12
+ *
13
+ * No shape is decorated here today, so nothing was mis-registered — the cost
14
+ * was waiting for the first one. Same defect, same fix, as linked-fw/owl#26.
15
+ */
16
+ export declare const linkedShape: {
17
+ <T extends typeof import("@_linked/core/shapes/Shape").Shape>(constructor: T): void;
18
+ <T extends typeof import("@_linked/core/shapes/Shape").Shape>(config?: import("@_linked/core/utils/Package").ShapeConfig): (constructor: T) => void;
19
+ }, linkedUtil: (constructor: any) => any, linkedOntology: (allFileExports: any, nameSpace: (term: string) => import("@_linked/core/utils/NodeReference").NodeReferenceValue, suggestedPrefixAndFileName: string, loadDataFunction?: () => Promise<any>, dataSource?: string | string[]) => void, registerPackageExport: (exportedObject: any) => void, registerPackageModule: (_module: any) => void, getPackageShape: (name: string) => import("@_linked/core/shapes/Shape").ShapeConstructor | undefined, packageExports: any, packageName: string;
@@ -1,13 +1,22 @@
1
1
  "use strict";
2
+ var _a;
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerPackageModule = exports.registerPackageExport = exports.packageExports = exports.linkedUtil = exports.linkedShape = exports.linkedOntology = exports.getPackageShape = exports.packageName = void 0;
4
- const package_1 = require("@_linked/core/package");
5
- Object.defineProperty(exports, "getPackageShape", { enumerable: true, get: function () { return package_1.getPackageShape; } });
6
- Object.defineProperty(exports, "linkedOntology", { enumerable: true, get: function () { return package_1.linkedOntology; } });
7
- Object.defineProperty(exports, "linkedShape", { enumerable: true, get: function () { return package_1.linkedShape; } });
8
- Object.defineProperty(exports, "linkedUtil", { enumerable: true, get: function () { return package_1.linkedUtil; } });
9
- Object.defineProperty(exports, "packageExports", { enumerable: true, get: function () { return package_1.packageExports; } });
10
- Object.defineProperty(exports, "registerPackageExport", { enumerable: true, get: function () { return package_1.registerPackageExport; } });
11
- Object.defineProperty(exports, "registerPackageModule", { enumerable: true, get: function () { return package_1.registerPackageModule; } });
12
- exports.packageName = { packageName: '@_linked/primitives' }.packageName;
4
+ exports.packageName = exports.packageExports = exports.getPackageShape = exports.registerPackageModule = exports.registerPackageExport = exports.linkedOntology = exports.linkedUtil = exports.linkedShape = void 0;
5
+ const Package_1 = require("@_linked/core/utils/Package");
6
+ /**
7
+ * This package's own linked identity.
8
+ *
9
+ * It used to re-export core's decorators verbatim and declare `packageName`
10
+ * as a bare literal that nothing was bound to. The decorators therefore came
11
+ * from `corePackage`, so everything declared here registered under
12
+ * `@_linked/core`: this package's ontology landed in core's entry in the
13
+ * package tree, and the next shape added here would silently have been given a
14
+ * `.../shape/core/...` IRI. That IRI is persisted data and `Server.call`
15
+ * routes on the package name inside it, so a shape naming the wrong package is
16
+ * simply unreachable.
17
+ *
18
+ * No shape is decorated here today, so nothing was mis-registered — the cost
19
+ * was waiting for the first one. Same defect, same fix, as linked-fw/owl#26.
20
+ */
21
+ _a = (0, Package_1.linkedPackage)('@_linked/primitives'), exports.linkedShape = _a.linkedShape, exports.linkedUtil = _a.linkedUtil, exports.linkedOntology = _a.linkedOntology, exports.registerPackageExport = _a.registerPackageExport, exports.registerPackageModule = _a.registerPackageModule, exports.getPackageShape = _a.getPackageShape, exports.packageExports = _a.packageExports, exports.packageName = _a.packageName;
13
22
  //# sourceMappingURL=package.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","sourceRoot":"","sources":["../../src/package.ts"],"names":[],"mappings":";;;AAAA,mDAQ+B;AAK7B,gGAZA,yBAAe,OAYA;AACf,+FAZA,wBAAc,OAYA;AACd,4FAZA,qBAAW,OAYA;AACX,2FAZA,oBAAU,OAYA;AACV,+FAZA,wBAAc,OAYA;AACd,sGAZA,+BAAqB,OAYA;AACrB,sGAZA,+BAAqB,OAYA;AATR,mBAAW,GAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,aAAC"}
1
+ {"version":3,"file":"package.js","sourceRoot":"","sources":["../../src/package.ts"],"names":[],"mappings":";;;;AAAA,yDAA0D;AAE1D;;;;;;;;;;;;;;GAcG;AACU,KAST,IAAA,uBAAa,EAAC,qBAAqB,CAAC,EARtC,mBAAW,mBACX,kBAAU,kBACV,sBAAc,sBACd,6BAAqB,6BACrB,6BAAqB,6BACrB,uBAAe,uBACf,sBAAc,sBACd,mBAAW,kBAC4B"}
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.3.1",
13
+ "version": "1.4.0",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "license": "ISC",
@@ -2,7 +2,13 @@ import * as React from 'react';
2
2
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3
3
  asChild?: boolean;
4
4
  variant?: 'solid' | 'outline' | 'ghost' | 'link';
5
- color?: 'primary' | 'secondary' | 'tertiary';
5
+ /**
6
+ * Which colour family the button speaks in. `danger` is the destructive one — a
7
+ * statement about consequence, and a Button prop rather than a class a caller adds,
8
+ * because the colour classes here are compound (`.Root.primary.solid`) and a bare
9
+ * class handed in through `className` loses the cascade to them every time.
10
+ */
11
+ color?: 'primary' | 'secondary' | 'tertiary' | 'danger';
6
12
  size?: 'small' | 'medium' | 'large' | 'default';
7
13
  }
8
14
  declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/components/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAU5C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAC7B,CACE,EACE,SAAS,EACT,OAAO,GAAG,OAAO,EACjB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,SAAS,EAChB,OAAO,GAAG,KAAK,EACf,GAAG,KAAK,EACT,EACD,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC;IAElC,OAAO,CACL,oBAAC,IAAI,IACH,SAAS,EAAE,EAAE,CACX,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CAAC,EACd,KAAK,CAAC,KAAK,CAAC,EACZ,KAAK,CAAC,IAAI,CAAC,EACX,UAAU,IAAI,KAAK,CAAC,QAAQ,EAC5B,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AACF,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/components/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAgB5C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAC7B,CACE,EACE,SAAS,EACT,OAAO,GAAG,OAAO,EACjB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,SAAS,EAChB,OAAO,GAAG,KAAK,EACf,GAAG,KAAK,EACT,EACD,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC;IAElC,OAAO,CACL,oBAAC,IAAI,IACH,SAAS,EAAE,EAAE,CACX,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CAAC,EACd,KAAK,CAAC,KAAK,CAAC,EACZ,KAAK,CAAC,IAAI,CAAC,EACX,UAAU,IAAI,KAAK,CAAC,QAAQ,EAC5B,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AACF,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -220,6 +220,56 @@
220
220
  }
221
221
  }
222
222
 
223
+ /* Destructive. Reads the intent family rather than a red primitive, so an app that
224
+ restyles what "danger" looks like restyles this too; the literal is the last-resort
225
+ fallback for a theme that defines neither, same contract as Badge. */
226
+ &.danger {
227
+ &.solid {
228
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
229
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
230
+ border-color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
231
+
232
+ &:hover {
233
+ background: var(
234
+ --button-danger-bg-hover,
235
+ var(--intent-danger-text, #b91c1c)
236
+ );
237
+ color: var(--button-danger-color-hover, var(--control-text-contrast, #fff));
238
+ }
239
+ }
240
+
241
+ &.outline {
242
+ background: transparent;
243
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
244
+ border: 1px solid
245
+ var(--button-danger-border, var(--intent-danger-border, #dc2626));
246
+
247
+ &:hover {
248
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
249
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
250
+ }
251
+ }
252
+
253
+ &.ghost {
254
+ background: transparent;
255
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
256
+
257
+ &:hover {
258
+ background: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
259
+ color: var(--button-danger-color, var(--control-text-contrast, #fff));
260
+ }
261
+ }
262
+
263
+ &.link {
264
+ background: transparent;
265
+ color: var(--button-danger-bg, var(--intent-danger-icon, #dc2626));
266
+
267
+ &:hover {
268
+ text-decoration: underline;
269
+ }
270
+ }
271
+ }
272
+
223
273
  /* size: 'small' | 'medium' | 'large' || 'default'*/
224
274
  &.small {
225
275
  padding: var(--button-padding-sm-y) var(--button-padding-sm-x);
@@ -9,7 +9,10 @@ export interface ConfirmDialogProps {
9
9
  cancelText?: string;
10
10
  /**
11
11
  * `danger` for a destructive action. Not a colour — a statement about consequence, which
12
- * is what `--intent-danger-*` exists to express.
12
+ * is what `--intent-danger-*` exists to express. It reaches the confirm button as
13
+ * `<Button color="danger">`, so an app re-skins it through `--button-danger-*` (or the
14
+ * `--intent-danger-*` family behind it) rather than through a `--confirm-dialog-danger-*`
15
+ * of its own.
13
16
  */
14
17
  tone?: 'default' | 'danger';
15
18
  /** The confirm action is in flight: both buttons lock. */
@@ -24,6 +24,6 @@ export function ConfirmDialog({ open, onOpenChange, onConfirm, title, message, c
24
24
  React.createElement(AlertDialog.Footer, null,
25
25
  React.createElement(AlertDialog.Cancel, { asChild: true },
26
26
  React.createElement(Button, { variant: "ghost", disabled: isLoading }, cancelText)),
27
- React.createElement(Button, { className: cl(tone === 'danger' && style.danger), onClick: onConfirm, disabled: isLoading || confirmDisabled }, isLoading && loadingContent ? loadingContent : confirmText)))));
27
+ React.createElement(Button, { color: tone === 'danger' ? 'danger' : 'primary', onClick: onConfirm, disabled: isLoading || confirmDisabled }, isLoading && loadingContent ? loadingContent : confirmText)))));
28
28
  }
29
29
  //# sourceMappingURL=ConfirmDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAC,EAAE,EAAC,MAAM,iCAAiC,CAAC;AACnD,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,MAAM,4BAA4B,CAAC;AA0B/C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,KAAK,EACL,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,UAAU,GAAG,QAAQ,EACrB,IAAI,GAAG,SAAS,EAChB,SAAS,GAAG,KAAK,EACjB,eAAe,GAAG,KAAK,EACvB,cAAc,EACd,IAAI,GAAG,KAAK,EACZ,SAAS,GACU;IACnB,OAAO,CACL,oBAAC,WAAW,CAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY;QACtD,oBAAC,WAAW,CAAC,OAAO,IAClB,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;YAE3D,oBAAC,WAAW,CAAC,MAAM;gBACjB,oBAAC,WAAW,CAAC,KAAK,QAAE,KAAK,CAAqB;gBAI9C,oBAAC,WAAW,CAAC,WAAW,IAAC,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAC1D,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAM,OAAO,CAAO,CACrC,CACP;YACrB,oBAAC,WAAW,CAAC,MAAM;gBACjB,oBAAC,WAAW,CAAC,MAAM,IAAC,OAAO;oBACzB,oBAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,SAAS,IACxC,UAAU,CACJ,CACU;gBAIrB,oBAAC,MAAM,IACL,SAAS,EAAE,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,EAChD,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,SAAS,IAAI,eAAe,IAErC,SAAS,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACpD,CACU,CACD,CACL,CACpB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAC,EAAE,EAAC,MAAM,iCAAiC,CAAC;AACnD,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,MAAM,4BAA4B,CAAC;AA6B/C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,KAAK,EACL,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,UAAU,GAAG,QAAQ,EACrB,IAAI,GAAG,SAAS,EAChB,SAAS,GAAG,KAAK,EACjB,eAAe,GAAG,KAAK,EACvB,cAAc,EACd,IAAI,GAAG,KAAK,EACZ,SAAS,GACU;IACnB,OAAO,CACL,oBAAC,WAAW,CAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY;QACtD,oBAAC,WAAW,CAAC,OAAO,IAClB,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;YAE3D,oBAAC,WAAW,CAAC,MAAM;gBACjB,oBAAC,WAAW,CAAC,KAAK,QAAE,KAAK,CAAqB;gBAI9C,oBAAC,WAAW,CAAC,WAAW,IAAC,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAC1D,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAM,OAAO,CAAO,CACrC,CACP;YACrB,oBAAC,WAAW,CAAC,MAAM;gBACjB,oBAAC,WAAW,CAAC,MAAM,IAAC,OAAO;oBACzB,oBAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,SAAS,IACxC,UAAU,CACJ,CACU;gBAQrB,oBAAC,MAAM,IACL,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAC/C,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,SAAS,IAAI,eAAe,IAErC,SAAS,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACpD,CACU,CACD,CACL,CACpB,CAAC;AACJ,CAAC"}
@@ -8,15 +8,3 @@
8
8
  .wide {
9
9
  max-width: var(--confirm-dialog-width-wide, 56rem);
10
10
  }
11
-
12
- /* Destructive confirmation. Reads the intent family rather than a red primitive, so an app
13
- that restyles what "danger" looks like restyles this too. */
14
- .danger {
15
- background: var(--confirm-dialog-danger-bg, var(--intent-danger-icon));
16
- border-color: var(--confirm-dialog-danger-border, var(--intent-danger-border));
17
- color: var(--confirm-dialog-danger-text, var(--color-white));
18
- }
19
-
20
- .danger:hover:not(:disabled) {
21
- background: var(--confirm-dialog-danger-bg-hover, var(--intent-danger-text));
22
- }
@@ -1,3 +1,19 @@
1
- import { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule } from '@_linked/core/package';
2
- export declare const packageName: string;
3
- export { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule, };
1
+ /**
2
+ * This package's own linked identity.
3
+ *
4
+ * It used to re-export core's decorators verbatim and declare `packageName`
5
+ * as a bare literal that nothing was bound to. The decorators therefore came
6
+ * from `corePackage`, so everything declared here registered under
7
+ * `@_linked/core`: this package's ontology landed in core's entry in the
8
+ * package tree, and the next shape added here would silently have been given a
9
+ * `.../shape/core/...` IRI. That IRI is persisted data and `Server.call`
10
+ * routes on the package name inside it, so a shape naming the wrong package is
11
+ * simply unreachable.
12
+ *
13
+ * No shape is decorated here today, so nothing was mis-registered — the cost
14
+ * was waiting for the first one. Same defect, same fix, as linked-fw/owl#26.
15
+ */
16
+ export declare const linkedShape: {
17
+ <T extends typeof import("@_linked/core/shapes/Shape").Shape>(constructor: T): void;
18
+ <T extends typeof import("@_linked/core/shapes/Shape").Shape>(config?: import("@_linked/core/utils/Package").ShapeConfig): (constructor: T) => void;
19
+ }, linkedUtil: (constructor: any) => any, linkedOntology: (allFileExports: any, nameSpace: (term: string) => import("@_linked/core/utils/NodeReference").NodeReferenceValue, suggestedPrefixAndFileName: string, loadDataFunction?: () => Promise<any>, dataSource?: string | string[]) => void, registerPackageExport: (exportedObject: any) => void, registerPackageModule: (_module: any) => void, getPackageShape: (name: string) => import("@_linked/core/shapes/Shape").ShapeConstructor | undefined, packageExports: any, packageName: string;
@@ -1,4 +1,18 @@
1
- import { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule, } from '@_linked/core/package';
2
- export const { packageName } = { packageName: '@_linked/primitives' };
3
- export { getPackageShape, linkedOntology, linkedShape, linkedUtil, packageExports, registerPackageExport, registerPackageModule, };
1
+ import { linkedPackage } from '@_linked/core/utils/Package';
2
+ /**
3
+ * This package's own linked identity.
4
+ *
5
+ * It used to re-export core's decorators verbatim and declare `packageName`
6
+ * as a bare literal that nothing was bound to. The decorators therefore came
7
+ * from `corePackage`, so everything declared here registered under
8
+ * `@_linked/core`: this package's ontology landed in core's entry in the
9
+ * package tree, and the next shape added here would silently have been given a
10
+ * `.../shape/core/...` IRI. That IRI is persisted data and `Server.call`
11
+ * routes on the package name inside it, so a shape naming the wrong package is
12
+ * simply unreachable.
13
+ *
14
+ * No shape is decorated here today, so nothing was mis-registered — the cost
15
+ * was waiting for the first one. Same defect, same fix, as linked-fw/owl#26.
16
+ */
17
+ export const { linkedShape, linkedUtil, linkedOntology, registerPackageExport, registerPackageModule, getPackageShape, packageExports, packageName, } = linkedPackage('@_linked/primitives');
4
18
  //# sourceMappingURL=package.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","sourceRoot":"","sources":["../../src/package.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACX,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;AAEtE,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACX,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,GACtB,CAAC"}
1
+ {"version":3,"file":"package.js","sourceRoot":"","sources":["../../src/package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,EACX,WAAW,EACX,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,WAAW,GACZ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC"}
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.3.1",
13
+ "version": "1.4.0",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "license": "ISC",
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.3.1",
13
+ "version": "1.4.0",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "main": "lib/cjs/index.js",
@@ -0,0 +1,183 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import test from 'node:test';
5
+
6
+ const dir = fileURLToPath(new URL('../src/components/', import.meta.url));
7
+ const read = (name) => readFileSync(dir + name, 'utf8');
8
+
9
+ /**
10
+ * Button colours itself with COMPOUND selectors — `.Root.primary.solid`, specificity
11
+ * 0-3-0. A sibling component that recolours a Button by handing it a bare class of its
12
+ * own module (0-1-0) therefore loses the cascade silently: the class is on the element,
13
+ * the DOM looks right, and the colour is Button's. That is exactly how `ConfirmDialog`'s
14
+ * `tone="danger"` came to be visually inert while looking correct in every review.
15
+ *
16
+ * These are STYLESHEET tests. They read the CSS as text and compare selector
17
+ * specificity; they do not render anything, so they prove that no competing
18
+ * lower-specificity declaration exists — NOT that the button comes out red. The rendered
19
+ * colour was measured in a browser (computed `background-color`) when this was fixed;
20
+ * this package has no DOM test runner to keep doing that.
21
+ */
22
+
23
+ /** Class selectors only, which is all a CSS module needs. Returns [a, b, c]. */
24
+ const specificity = (selector) => [
25
+ (selector.match(/#[\w-]+/g) ?? []).length,
26
+ (selector.match(/\.[\w-]+/g) ?? []).length +
27
+ (selector.match(/\[[^\]]+\]/g) ?? []).length +
28
+ (selector.match(/:(?!:)(?!hover|focus|active|disabled|not)[\w-]+/g) ?? [])
29
+ .length,
30
+ 0,
31
+ ];
32
+
33
+ const compare = (x, y) =>
34
+ x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
35
+
36
+ /**
37
+ * Strip comments and statement at-rules, then walk `{}` keeping a stack of `&`-expanded
38
+ * selectors. Each entry is one rule with the declarations written directly in it.
39
+ */
40
+ function rules(css) {
41
+ const source = css
42
+ .replace(/\/\*[\s\S]*?\*\//g, '')
43
+ .replace(/@(?:import|charset|mixin)[^;{]*;/g, '');
44
+ const out = [];
45
+ const stack = [];
46
+ let buffer = '';
47
+ for (const ch of source) {
48
+ if (ch === '{') {
49
+ const parents = stack.length ? stack[stack.length - 1].selectors : [''];
50
+ const selectors = buffer
51
+ .trim()
52
+ .split(',')
53
+ .map((s) => s.trim())
54
+ .flatMap((sel) =>
55
+ parents.map((parent) =>
56
+ sel.includes('&')
57
+ ? sel.replace(/&/g, parent)
58
+ : parent
59
+ ? `${parent} ${sel}`
60
+ : sel
61
+ )
62
+ );
63
+ const rule = { selectors, declarations: '' };
64
+ out.push(rule);
65
+ stack.push(rule);
66
+ buffer = '';
67
+ } else if (ch === '}') {
68
+ stack.pop();
69
+ buffer = '';
70
+ } else {
71
+ buffer += ch;
72
+ // A `;` at this depth closes a declaration of the innermost open rule.
73
+ if (ch === ';') {
74
+ if (stack.length) stack[stack.length - 1].declarations += buffer;
75
+ buffer = '';
76
+ }
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+
82
+ /** Highest specificity at which `prop` is set anywhere in this stylesheet. */
83
+ function maxSpecificityFor(css, prop) {
84
+ let best = null;
85
+ for (const rule of rules(css)) {
86
+ if (!new RegExp(`(^|;)\\s*${prop}\\s*:`).test(rule.declarations)) continue;
87
+ for (const selector of rule.selectors) {
88
+ const s = specificity(selector);
89
+ if (!best || compare(s, best.specificity) > 0)
90
+ best = { specificity: s, selector: selector.trim() };
91
+ }
92
+ }
93
+ return best;
94
+ }
95
+
96
+ test('every Button color in the union is actually styled', () => {
97
+ const source = read('Button.tsx');
98
+ const stylesheet = read('Button.module.css');
99
+
100
+ const union = source.match(/color\?:([^;]+);/);
101
+ assert.ok(union, 'color prop not found in Button.tsx');
102
+
103
+ const colors = [...union[1].matchAll(/'([a-z-]+)'/g)].map((m) => m[1]);
104
+ assert.ok(colors.includes('danger'), `expected a danger colour, got ${colors}`);
105
+
106
+ for (const color of colors) {
107
+ assert.match(
108
+ stylesheet,
109
+ new RegExp(`&\\.${color}\\s*\\{`),
110
+ `Button color "${color}" is accepted by the type but has no rules in Button.module.css`
111
+ );
112
+ }
113
+ });
114
+
115
+ test('ConfirmDialog expresses tone through Button, not through a class of its own', () => {
116
+ const source = read('ConfirmDialog.tsx');
117
+
118
+ assert.match(
119
+ source,
120
+ /color=\{tone === 'danger' \? 'danger' : 'primary'\}/,
121
+ 'ConfirmDialog no longer passes tone to Button as a colour prop'
122
+ );
123
+ assert.doesNotMatch(
124
+ source,
125
+ /className=\{cl\(tone === 'danger'/,
126
+ 'ConfirmDialog is back to overriding Button with a bare class, which loses the cascade'
127
+ );
128
+ assert.equal(
129
+ maxSpecificityFor(read('ConfirmDialog.module.css'), 'background'),
130
+ null,
131
+ 'ConfirmDialog.module.css sets a background again — it cannot outrank Button from here'
132
+ );
133
+ });
134
+
135
+ /**
136
+ * MultiSelect is a KNOWN, MEASURED instance of the same trap, left as-is deliberately:
137
+ * its `.Root .button` (0-2-0) sets `background-color` on a `<Button>` whose
138
+ * `.Root.primary.solid` (0-3-0) sets `background`, so `--multiselect-selected-bg` is
139
+ * inert and the chip renders in `--button-primary-bg`. Fixing it is a separate design
140
+ * decision (the chip also nests a `<button>` inside a `<button>`), so it is recorded
141
+ * here rather than silently swept into the ConfirmDialog fix.
142
+ */
143
+ const KNOWN_INERT = new Set(['MultiSelect.tsx']);
144
+
145
+ test('no component recolours a Button with a class that cannot outrank it', () => {
146
+ const buttonBackground = maxSpecificityFor(read('Button.module.css'), 'background');
147
+ assert.ok(buttonBackground, 'Button.module.css no longer sets a background');
148
+
149
+ const offenders = [];
150
+ for (const file of readdirSync(dir).filter((f) => f.endsWith('.tsx'))) {
151
+ if (file === 'Button.tsx') continue;
152
+ const source = read(file);
153
+ const stylesheet = (() => {
154
+ try {
155
+ return read(file.replace(/\.tsx$/, '.module.css'));
156
+ } catch {
157
+ return null;
158
+ }
159
+ })();
160
+ if (!stylesheet) continue;
161
+
162
+ for (const call of source.match(/<Button[\s\S]{0,400}?>/g) ?? []) {
163
+ for (const [, cls] of call.matchAll(/style\.([A-Za-z][\w]*)/g)) {
164
+ for (const prop of ['background', 'background-color', 'border-color']) {
165
+ const own = maxSpecificityFor(stylesheet, prop);
166
+ if (!own) continue;
167
+ if (!own.selector.includes(`.${cls}`)) continue;
168
+ if (compare(own.specificity, buttonBackground.specificity) < 0)
169
+ offenders.push(
170
+ `${file}: "${own.selector}" (${own.specificity.join('-')}) sets ${prop} on a ` +
171
+ `<Button> whose "${buttonBackground.selector}" ` +
172
+ `(${buttonBackground.specificity.join('-')}) wins — pass a Button prop instead`
173
+ );
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ const unexpected = offenders.filter(
180
+ (o) => !KNOWN_INERT.has(o.slice(0, o.indexOf(':')))
181
+ );
182
+ assert.deepEqual(unexpected, [], unexpected.join('\n'));
183
+ });
@@ -0,0 +1,28 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ /**
5
+ * Which `linkedPackage()` the decorators come from decides the name everything
6
+ * in this package registers under — the ontology here today, and the IRI of the
7
+ * first `@linkedShape` anyone adds tomorrow. `src/package.ts` used to re-export
8
+ * core's decorators, so all of it landed under `@_linked/core`.
9
+ *
10
+ * The test is on object identity rather than on a registered IRI or a key in
11
+ * the global package tree, because both of those are core's own shapes and have
12
+ * changed across its versions — and CI resolves a different `@_linked/core`
13
+ * from the workspace does. `packageExports` *being* core's registry object is
14
+ * the defect itself, stated in the one form that holds on every version.
15
+ */
16
+
17
+ test('this package does not share core\'s export registry', async () => {
18
+ const own = await import(new URL('../lib/esm/package.js', import.meta.url));
19
+ const core = await import('@_linked/core/package');
20
+
21
+ assert.notEqual(
22
+ own.packageExports,
23
+ core.packageExports,
24
+ 'packageExports is core\'s registry: the decorators here are core\'s, so ' +
25
+ 'everything declared in this package registers under @_linked/core',
26
+ );
27
+ assert.equal(own.packageName, '@_linked/primitives');
28
+ });