@nexterajs/ui 0.0.1

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 (50) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +64 -0
  4. package/fesm2022/nexterajs-ui-animations.mjs +184 -0
  5. package/fesm2022/nexterajs-ui-animations.mjs.map +1 -0
  6. package/fesm2022/nexterajs-ui-core.mjs +326 -0
  7. package/fesm2022/nexterajs-ui-core.mjs.map +1 -0
  8. package/fesm2022/nexterajs-ui-dropdowns.mjs +953 -0
  9. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -0
  10. package/fesm2022/nexterajs-ui-excel.mjs +779 -0
  11. package/fesm2022/nexterajs-ui-excel.mjs.map +1 -0
  12. package/fesm2022/nexterajs-ui-file-uploader.mjs +125 -0
  13. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -0
  14. package/fesm2022/nexterajs-ui-forms.mjs +1113 -0
  15. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -0
  16. package/fesm2022/nexterajs-ui-grid.mjs +4145 -0
  17. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -0
  18. package/fesm2022/nexterajs-ui-intl.mjs +377 -0
  19. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -0
  20. package/fesm2022/nexterajs-ui-pivot.mjs +4739 -0
  21. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -0
  22. package/fesm2022/nexterajs-ui-styling.mjs +59 -0
  23. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -0
  24. package/fesm2022/nexterajs-ui-tree-view.mjs +202 -0
  25. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -0
  26. package/fesm2022/nexterajs-ui-validation.mjs +269 -0
  27. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -0
  28. package/fesm2022/nexterajs-ui.mjs +23 -0
  29. package/fesm2022/nexterajs-ui.mjs.map +1 -0
  30. package/package.json +100 -0
  31. package/styles/_button-directives.scss +39 -0
  32. package/styles/_dropdown.scss +113 -0
  33. package/styles/_editor.scss +146 -0
  34. package/styles/_mixins.scss +2 -0
  35. package/styles/_tokens.scss +77 -0
  36. package/styles/_widget.scss +58 -0
  37. package/styles/index.scss +83 -0
  38. package/types/nexterajs-ui-animations.d.ts +22 -0
  39. package/types/nexterajs-ui-core.d.ts +328 -0
  40. package/types/nexterajs-ui-dropdowns.d.ts +334 -0
  41. package/types/nexterajs-ui-excel.d.ts +159 -0
  42. package/types/nexterajs-ui-file-uploader.d.ts +47 -0
  43. package/types/nexterajs-ui-forms.d.ts +296 -0
  44. package/types/nexterajs-ui-grid.d.ts +1485 -0
  45. package/types/nexterajs-ui-intl.d.ts +125 -0
  46. package/types/nexterajs-ui-pivot.d.ts +1470 -0
  47. package/types/nexterajs-ui-styling.d.ts +40 -0
  48. package/types/nexterajs-ui-tree-view.d.ts +69 -0
  49. package/types/nexterajs-ui-validation.d.ts +102 -0
  50. package/types/nexterajs-ui.d.ts +12 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nexterajs-ui-validation.mjs","sources":["../../../projects/nextera-ui/validation/src/lib/validation/nxe-validation-rule.component.ts","../../../projects/nextera-ui/validation/src/lib/validation/nxe-validator.component.ts","../../../projects/nextera-ui/validation/src/lib/validation/nxe-validators.ts","../../../projects/nextera-ui/validation/src/public-api.ts","../../../projects/nextera-ui/validation/src/nexterajs-ui-validation.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';\r\n\r\n/** DevExtreme validation rule type (the codebase uses `required`, `compare`, and `range`). */\r\nexport type NxeValidationRuleType = 'required' | 'compare' | 'custom' | 'pattern' | 'email' | 'range';\r\n\r\n/**\r\n * The message shown when a rule fails and the caller gave none.\r\n *\r\n * These are DevExtreme's own defaults, verbatim from its `validation-*` message keys. Without\r\n * them a rule with no `message` blocks its group and displays nothing at all, because every\r\n * editor renders its error bubble under `@if (validationError())` — falsy for the empty string.\r\n */\r\nexport const NXE_DEFAULT_VALIDATION_MESSAGES: Readonly<Record<NxeValidationRuleType, string>> = {\r\n required: 'Required',\r\n email: 'Email is invalid',\r\n pattern: 'Value does not match pattern',\r\n range: 'Value is out of range',\r\n compare: 'Values do not match',\r\n custom: 'Value is invalid',\r\n};\r\n\r\n/** Payload passed to a `custom` rule's `validationCallback` (mirrors DevExtreme). */\r\nexport interface NxeCustomRuleData {\r\n value: unknown;\r\n rule: NxeValidationRuleComponent;\r\n}\r\n\r\n/**\r\n * `nxe-validation-rule` — drop-in for DevExtreme `dxi-validation-rule`.\r\n *\r\n * A no-template configuration child of `nxe-validator`. The codebase uses the universal element\r\n * with a `type` attribute (overwhelmingly `type=\"required\"`), so a single component with a\r\n * `type` input covers 100% of usage.\r\n */\r\n@Component({\r\n selector: 'nxe-validation-rule',\r\n standalone: true,\r\n template: '',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.None,\r\n})\r\nexport class NxeValidationRuleComponent {\r\n readonly type = input<NxeValidationRuleType>('required');\r\n readonly message = input<string>('');\r\n\r\n /** `compare` rule: returns the value to compare against. Mirrors `comparisonTarget`. */\r\n readonly comparisonTarget = input<(() => unknown) | undefined>(undefined);\r\n /** `compare` rule comparison operator (default `==`). */\r\n readonly comparisonType = input<'==' | '===' | '!=' | '!==' | '>' | '<' | '>=' | '<='>('==');\r\n\r\n /** `custom` rule: returns true when valid. Mirrors `validationCallback`. */\r\n readonly validationCallback = input<((data: NxeCustomRuleData) => boolean) | undefined>(undefined);\r\n\r\n /** `pattern` rule. */\r\n readonly pattern = input<string | RegExp | undefined>(undefined);\r\n\r\n /** `range` rule bounds (DevExtreme RangeRule: empty values pass, bounds are inclusive). */\r\n readonly min = input<number | undefined>(undefined);\r\n readonly max = input<number | undefined>(undefined);\r\n\r\n /**\r\n * The message to show when this rule fails: the caller's, or DevExtreme's default for the\r\n * rule type. Whitespace counts as absent — a message of spaces is still an empty bubble.\r\n */\r\n readonly resolvedMessage = computed(\r\n () => this.message().trim() || NXE_DEFAULT_VALIDATION_MESSAGES[this.type()] || '',\r\n );\r\n\r\n /** Whether `value` satisfies this rule. */\r\n evaluate(value: unknown): boolean {\r\n switch (this.type()) {\r\n case 'required':\r\n return !this.isEmpty(value);\r\n case 'compare':\r\n return this.compare(value);\r\n case 'range':\r\n return this.isEmpty(value) || this.inRange(value);\r\n case 'pattern':\r\n return this.isEmpty(value) || this.matchesPattern(value);\r\n case 'email':\r\n return this.isEmpty(value) || /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(String(value));\r\n case 'custom':\r\n return this.validationCallback()?.({ value, rule: this }) ?? true;\r\n default:\r\n return true;\r\n }\r\n }\r\n\r\n /** DevExtreme RequiredRule semantics: null/undefined/''/false are \"not specified\". */\r\n private isEmpty(value: unknown): boolean {\r\n return (\r\n value === null ||\r\n value === undefined ||\r\n value === '' ||\r\n value === false ||\r\n (Array.isArray(value) && value.length === 0)\r\n );\r\n }\r\n\r\n private compare(value: unknown): boolean {\r\n const target = this.comparisonTarget()?.();\r\n switch (this.comparisonType()) {\r\n case '===':\r\n return value === target;\r\n case '!=':\r\n // eslint-disable-next-line eqeqeq\r\n return value != target;\r\n case '!==':\r\n return value !== target;\r\n case '>':\r\n return (value as number) > (target as number);\r\n case '<':\r\n return (value as number) < (target as number);\r\n case '>=':\r\n return (value as number) >= (target as number);\r\n case '<=':\r\n return (value as number) <= (target as number);\r\n case '==':\r\n default:\r\n // eslint-disable-next-line eqeqeq\r\n return value == target;\r\n }\r\n }\r\n\r\n private inRange(value: unknown): boolean {\r\n const num = Number(value);\r\n if (Number.isNaN(num)) {\r\n return false;\r\n }\r\n const min = this.min();\r\n const max = this.max();\r\n return (min === undefined || num >= min) && (max === undefined || num <= max);\r\n }\r\n\r\n private matchesPattern(value: unknown): boolean {\r\n const pattern = this.pattern();\r\n if (!pattern) {\r\n return true;\r\n }\r\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\r\n return regex.test(String(value));\r\n }\r\n}\r\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n computed,\r\n contentChildren,\r\n DestroyRef,\r\n inject,\r\n Injector,\r\n input,\r\n signal,\r\n ViewEncapsulation,\r\n} from '@angular/core';\r\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\r\nimport { distinctUntilChanged, pairwise, startWith } from 'rxjs';\r\nimport { NxeValidationGroupRegistry } from '@nexterajs/ui/core';\r\nimport {\r\n NXE_VALIDATION_TARGET,\r\n NxeValidationResult,\r\n} from '@nexterajs/ui/core';\r\nimport { NxeValidationRuleComponent } from './nxe-validation-rule.component';\r\n\r\n/**\r\n * `nxe-validator` — drop-in for DevExtreme `dx-validator`.\r\n *\r\n * Placed inside an editor (`<nxe-text-box><nxe-validator><nxe-validation-rule .../></nxe-validator></nxe-text-box>`),\r\n * it adapts the editor via {@link NXE_VALIDATION_TARGET}, collects its `nxe-validation-rule`\r\n * children, and registers into the {@link NxeValidationGroupRegistry} under the editor's\r\n * `validationGroup`. Validation is on-demand (matching DevExtreme: not auto-run on change) —\r\n * `validate()` is invoked by `nxe-button[validationGroup]` (group-level) or directly.\r\n */\r\n@Component({\r\n selector: 'nxe-validator',\r\n standalone: true,\r\n template: '<ng-content />',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.None,\r\n})\r\nexport class NxeValidatorComponent {\r\n private readonly target = inject(NXE_VALIDATION_TARGET, { optional: true });\r\n private readonly registry = inject(NxeValidationGroupRegistry);\r\n\r\n readonly rules = contentChildren(NxeValidationRuleComponent);\r\n\r\n /** Explicit group; otherwise inherited from the wrapped editor's `validationGroup`. */\r\n readonly validationGroupInput = input<string | undefined>(undefined, { alias: 'validationGroup' });\r\n // Bare validators (no group) join the implicit default group `''` — mirroring DevExtreme's\r\n // global default group — so a groupless `nxe-button` validates them.\r\n private readonly group = computed(\r\n () => this.validationGroupInput() ?? this.target?.validationGroup() ?? '',\r\n );\r\n\r\n /** Last validation outcome (false once a rule has failed, until re-validated). */\r\n readonly isValid = signal(true);\r\n\r\n /** DevExtreme parity: existing code calls `validator.instance.validate()` via `@ViewChild`. */\r\n get instance(): NxeValidatorComponent {\r\n return this;\r\n }\r\n\r\n private readonly member = { validate: () => this.validate() };\r\n\r\n constructor() {\r\n // Register/unregister into the named group reactively (handles dynamic group changes).\r\n /* ⚠ NOT an effect - UI-48. The registration follows ONE signal, and the unregister that\r\n `onCleanup` used to run is the pairwise step below: a group change unregisters the old\r\n one before registering the new, and destruction unregisters the last. */\r\n toObservable(this.group, { injector: inject(Injector) })\r\n .pipe(\r\n distinctUntilChanged(),\r\n startWith(undefined as string | undefined),\r\n pairwise(),\r\n takeUntilDestroyed(),\r\n )\r\n .subscribe(([ previous, group ]) => {\r\n if (previous !== undefined) {\r\n this.registry.unregister(previous, this.member);\r\n }\r\n\r\n this.registry.register(group, this.member);\r\n });\r\n\r\n inject(DestroyRef).onDestroy(() => this.registry.unregister(this.group(), this.member));\r\n }\r\n\r\n /** Run all rules against the editor's current value; pushes the first failing message. */\r\n validate(): NxeValidationResult {\r\n const value = this.target?.getValue();\r\n for (const rule of this.rules()) {\r\n if (!rule.evaluate(value)) {\r\n // resolvedMessage(), not message(): a rule declared without one would otherwise\r\n // push the empty string, and every editor guards its bubble with\r\n // `@if (validationError())` — so the field would block the group in silence.\r\n const message = rule.resolvedMessage();\r\n this.isValid.set(false);\r\n this.target?.setValidationError(message);\r\n return {\r\n isValid: false,\r\n brokenRules: [{ type: rule.type(), message }],\r\n status: 'invalid',\r\n };\r\n }\r\n }\r\n this.isValid.set(true);\r\n this.target?.setValidationError(null);\r\n return { isValid: true, brokenRules: [], status: 'valid' };\r\n }\r\n}\r\n","import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\r\n\r\nexport class NxeValidators {\r\n /**\r\n * Check for empty (optional fields) values\r\n *\r\n * @param value\r\n */\r\n static isEmptyInputValue(value: any): boolean {\r\n return value == null || value.length === 0;\r\n }\r\n\r\n /**\r\n * Must match validator\r\n *\r\n * @param controlPath A dot-delimited string values that define the path to the control.\r\n * @param matchingControlPath A dot-delimited string values that define the path to the matching control.\r\n */\r\n static mustMatch(controlPath: string, matchingControlPath: string): ValidatorFn {\r\n return (formGroup: AbstractControl): ValidationErrors | null => {\r\n // Get the control and matching control\r\n const control = formGroup.get(controlPath);\r\n const matchingControl = formGroup.get(matchingControlPath);\r\n\r\n // Return if control or matching control doesn't exist\r\n if (!control || !matchingControl) {\r\n return null;\r\n }\r\n\r\n // Delete the mustMatch error to reset the error on the matching control\r\n if (matchingControl.hasError('mustMatch')) {\r\n delete matchingControl.errors.mustMatch;\r\n matchingControl.updateValueAndValidity();\r\n }\r\n\r\n // Don't validate empty values on the matching control\r\n // Don't validate if values are matching\r\n if (this.isEmptyInputValue(matchingControl.value) || control.value === matchingControl.value) {\r\n return null;\r\n }\r\n\r\n // Prepare the validation errors\r\n const errors = { mustMatch: true };\r\n\r\n // Set the validation error on the matching control\r\n matchingControl.setErrors(errors);\r\n\r\n // Return the errors\r\n return errors;\r\n };\r\n }\r\n}\r\n","// @nexterajs/ui/validation — public API.\r\nexport * from './lib/validation/nxe-validation-rule.component';\r\nexport * from './lib/validation/nxe-validator.component';\r\nexport * from './lib/validation/nxe-validators';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export * from '@nexterajs/ui/animations';
2
+ export * from '@nexterajs/ui/core';
3
+ export * from '@nexterajs/ui/dropdowns';
4
+ export * from '@nexterajs/ui/excel';
5
+ export * from '@nexterajs/ui/file-uploader';
6
+ export * from '@nexterajs/ui/forms';
7
+ export * from '@nexterajs/ui/grid';
8
+ export * from '@nexterajs/ui/intl';
9
+ export * from '@nexterajs/ui/pivot';
10
+ export * from '@nexterajs/ui/styling';
11
+ export * from '@nexterajs/ui/tree-view';
12
+ export * from '@nexterajs/ui/validation';
13
+
14
+ // @nexterajs/ui — compatibility barrel.
15
+ //
16
+ // Re-exports every secondary entry point so importing from the bare specifier keeps working.
17
+ // Prefer the specific entry point (@nexterajs/ui/grid) in new code: it keeps the pivot engine
18
+ // and the xlsx writer out of your bundle and out of your dev-server rebuilds.
19
+
20
+ /**
21
+ * Generated bundle index. Do not edit.
22
+ */
23
+ //# sourceMappingURL=nexterajs-ui.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nexterajs-ui.mjs","sources":["../../../projects/nextera-ui/src/public-api.ts","../../../projects/nextera-ui/src/nexterajs-ui.ts"],"sourcesContent":["// @nexterajs/ui — compatibility barrel.\r\n//\r\n// Re-exports every secondary entry point so importing from the bare specifier keeps working.\r\n// Prefer the specific entry point (@nexterajs/ui/grid) in new code: it keeps the pivot engine\r\n// and the xlsx writer out of your bundle and out of your dev-server rebuilds.\r\nexport * from '@nexterajs/ui/animations';\r\nexport * from '@nexterajs/ui/core';\r\nexport * from '@nexterajs/ui/dropdowns';\r\nexport * from '@nexterajs/ui/excel';\r\nexport * from '@nexterajs/ui/file-uploader';\r\nexport * from '@nexterajs/ui/forms';\r\nexport * from '@nexterajs/ui/grid';\r\nexport * from '@nexterajs/ui/intl';\r\nexport * from '@nexterajs/ui/pivot';\r\nexport * from '@nexterajs/ui/styling';\r\nexport * from '@nexterajs/ui/tree-view';\r\nexport * from '@nexterajs/ui/validation';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;;ACJA;;AAEG"}
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@nexterajs/ui",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/animations": "^22.0.0",
6
+ "@angular/cdk": "^22.0.0",
7
+ "@angular/common": "^22.0.0",
8
+ "@angular/core": "^22.0.0",
9
+ "@angular/forms": "^22.0.0",
10
+ "luxon": "^3.0.0",
11
+ "rxjs": "^7.8.0"
12
+ },
13
+ "dependencies": {
14
+ "tslib": "^2.3.0"
15
+ },
16
+ "sideEffects": false,
17
+ "exports": {
18
+ "./styles/*": {
19
+ "sass": "./styles/*",
20
+ "default": "./styles/*"
21
+ },
22
+ "./package.json": {
23
+ "default": "./package.json"
24
+ },
25
+ ".": {
26
+ "types": "./types/nexterajs-ui.d.ts",
27
+ "default": "./fesm2022/nexterajs-ui.mjs"
28
+ },
29
+ "./animations": {
30
+ "types": "./types/nexterajs-ui-animations.d.ts",
31
+ "default": "./fesm2022/nexterajs-ui-animations.mjs"
32
+ },
33
+ "./core": {
34
+ "types": "./types/nexterajs-ui-core.d.ts",
35
+ "default": "./fesm2022/nexterajs-ui-core.mjs"
36
+ },
37
+ "./dropdowns": {
38
+ "types": "./types/nexterajs-ui-dropdowns.d.ts",
39
+ "default": "./fesm2022/nexterajs-ui-dropdowns.mjs"
40
+ },
41
+ "./excel": {
42
+ "types": "./types/nexterajs-ui-excel.d.ts",
43
+ "default": "./fesm2022/nexterajs-ui-excel.mjs"
44
+ },
45
+ "./file-uploader": {
46
+ "types": "./types/nexterajs-ui-file-uploader.d.ts",
47
+ "default": "./fesm2022/nexterajs-ui-file-uploader.mjs"
48
+ },
49
+ "./forms": {
50
+ "types": "./types/nexterajs-ui-forms.d.ts",
51
+ "default": "./fesm2022/nexterajs-ui-forms.mjs"
52
+ },
53
+ "./grid": {
54
+ "types": "./types/nexterajs-ui-grid.d.ts",
55
+ "default": "./fesm2022/nexterajs-ui-grid.mjs"
56
+ },
57
+ "./intl": {
58
+ "types": "./types/nexterajs-ui-intl.d.ts",
59
+ "default": "./fesm2022/nexterajs-ui-intl.mjs"
60
+ },
61
+ "./pivot": {
62
+ "types": "./types/nexterajs-ui-pivot.d.ts",
63
+ "default": "./fesm2022/nexterajs-ui-pivot.mjs"
64
+ },
65
+ "./styling": {
66
+ "types": "./types/nexterajs-ui-styling.d.ts",
67
+ "default": "./fesm2022/nexterajs-ui-styling.mjs"
68
+ },
69
+ "./tree-view": {
70
+ "types": "./types/nexterajs-ui-tree-view.d.ts",
71
+ "default": "./fesm2022/nexterajs-ui-tree-view.mjs"
72
+ },
73
+ "./validation": {
74
+ "types": "./types/nexterajs-ui-validation.d.ts",
75
+ "default": "./fesm2022/nexterajs-ui-validation.mjs"
76
+ }
77
+ },
78
+ "license": "Apache-2.0",
79
+ "author": "Nextera",
80
+ "publishConfig": {
81
+ "access": "public"
82
+ },
83
+ "engines": {
84
+ "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
85
+ },
86
+ "description": "Angular 22 component library replacing DevExtreme: data grid, pivot grid, editors, pickers and a zero-dependency XLSX engine, in twelve tree-shakeable entry points.",
87
+ "keywords": [
88
+ "angular",
89
+ "angular-components",
90
+ "devextreme",
91
+ "data-grid",
92
+ "pivot-grid",
93
+ "xlsx",
94
+ "ui-library",
95
+ "signals"
96
+ ],
97
+ "module": "fesm2022/nexterajs-ui.mjs",
98
+ "typings": "types/nexterajs-ui.d.ts",
99
+ "type": "module"
100
+ }
@@ -0,0 +1,39 @@
1
+ // Hover/focus overlay for the nxe-mat-button directives. Material draws this via a
2
+ // `.mat-mdc-button-persistent-ripple` span, which an attribute directive can't inject — so we
3
+ // reproduce it with a `::after` overlay. The base sizing/color/radius still come from Material's
4
+ // loaded `.mat-mdc-*-button` theme CSS (we emit those classes).
5
+
6
+ @mixin nxe-button-directives {
7
+ .nxe-mat-button,
8
+ .nxe-mat-icon-button {
9
+ position: relative;
10
+
11
+ &::after {
12
+ content: '';
13
+ position: absolute;
14
+ inset: 0;
15
+ border-radius: inherit;
16
+ background: currentColor;
17
+ opacity: 0;
18
+ transition: opacity 80ms ease;
19
+ pointer-events: none;
20
+ }
21
+
22
+ &:hover::after {
23
+ opacity: 0.08;
24
+ }
25
+
26
+ &:active::after {
27
+ opacity: 0.16;
28
+ }
29
+
30
+ &:disabled,
31
+ &[disabled] {
32
+ cursor: default;
33
+
34
+ &::after {
35
+ opacity: 0;
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,113 @@
1
+ // Shared dropdown list panel for the nxe select family (select-box, tag-box, drop-down-box).
2
+ // Reproduces the DevExtreme `.dx-dropdownlist-popup`. Global (loaded via styling/index.scss) so it
3
+ // applies inside the CDK overlay regardless of which component opened it.
4
+ //
5
+ // Every value below is checked against the reference deployment's bundle
6
+ // (tools/visual-parity/reference/bright-styles.css); the source rule is named in a comment.
7
+ // Typography deliberately comes from `.nxe-widget` on the PANE (styling/_widget.scss) — the panel
8
+ // must NOT declare its own font-size. Declaring one here is what made options render 14px in an
9
+ // app whose every other text surface is 12.24px.
10
+
11
+ @mixin nxe-dropdown {
12
+ .nxe-select-panel {
13
+ box-sizing: border-box;
14
+ max-height: 320px;
15
+ // dx: `.dx-dropdownlist-popup-wrapper .dx-list:not(.dx-list-select-decorator-enabled)
16
+ // { min-height: 33.0001px }` — an empty panel never collapses below one row.
17
+ min-height: 33px;
18
+ // dx: `.dx-overlay-content { min-width: 400px !important }` in the dx-era app stylesheet.
19
+ // Library default is 0; the host app opts in via the token.
20
+ min-width: var(--nxe-ui-overlay-min-width);
21
+ overflow-y: auto;
22
+ // dx: `.dx-popup-wrapper > .dx-overlay-content { border:1px solid #ddd; background:#fff;
23
+ // box-shadow:0 6px 12px rgba(0,0,0,.175); border-radius:6px }`
24
+ background: var(--nxe-ui-surface);
25
+ border: 1px solid var(--nxe-ui-border);
26
+ border-radius: var(--nxe-ui-popup-radius);
27
+ box-shadow: var(--nxe-ui-popup-shadow);
28
+ color: var(--nxe-ui-text);
29
+ // dx: `.dx-dropdownlist-popup-wrapper.dx-popup-wrapper .dx-overlay-content
30
+ // { border-top-width: 0 }` — the panel sits flush against the editor it drops from.
31
+ border-top-width: 0;
32
+ // dx: `.dx-dropdownlist-popup-wrapper .dx-popup-content { padding: 1px }`
33
+ padding: 1px;
34
+ }
35
+
36
+ // dx flips the popup above the editor when there is no room below; the flush edge flips too.
37
+ // dx: `.dx-overlay-content.dx-dropdowneditor-overlay-flipped
38
+ // { border-top-width:1px; border-bottom-width:0 }`
39
+ // The marker class is applied by trackPaneFlip() in styling/nxe-overlay.ts (CDK has no hook).
40
+ .nxe-pane-flipped .nxe-select-panel {
41
+ border-top-width: 1px;
42
+ border-bottom-width: 0;
43
+ }
44
+
45
+ .nxe-list-item {
46
+ box-sizing: border-box;
47
+ // dx: `.dx-dropdownlist-popup-wrapper .dx-list… .dx-list-item-content { padding: 7px 9px }`
48
+ padding: var(--nxe-ui-item-padding);
49
+ cursor: pointer;
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ white-space: nowrap;
53
+
54
+ // NB: no `::before` spacer here. dx-compat.css carries one
55
+ // (`.dx-list-item-content::before{content:"_";width:0}`) but that rule comes from a NEWER
56
+ // DevExtreme than the reference runs — the reference bundle has no such rule, and adding it
57
+ // inflates the row from dx's 30.59px to 35.19px because an inline-block reserves descender
58
+ // space. Verified against tools/visual-parity/reference/bright-styles.css.
59
+
60
+ // dx: `.dx-list-item.dx-state-hover { background-color: rgba(0,0,0,.04); color: #333 }`
61
+ &:hover {
62
+ background-color: var(--nxe-ui-hover-bg);
63
+ color: var(--nxe-ui-text);
64
+ }
65
+
66
+ // dx: `.dx-list-item.dx-list-item-selected { background-color: rgba(0,0,0,.1); color:#333 }`
67
+ &.nxe-list-item-selected {
68
+ background-color: var(--nxe-ui-selected-bg);
69
+ color: var(--nxe-ui-text);
70
+ }
71
+
72
+ // dx: `.dx-list-item-selected.dx-state-hover:not(.dx-state-focused)
73
+ // { background-color: rgba(0,0,0,.07) }` — hovering an already-selected row must not
74
+ // fall back to the lighter plain-hover layer.
75
+ &.nxe-list-item-selected:hover:not(.nxe-list-item-active) {
76
+ background-color: var(--nxe-ui-selected-hover-bg);
77
+ }
78
+
79
+ // The keyboard cursor. dx calls it `dx-state-focused` (there is no `dx-list-item-focused`).
80
+ // dx: `.dx-list-item.dx-state-focused { background-color:#337ab7; color:#fff }`
81
+ &.nxe-list-item-active {
82
+ background-color: var(--nxe-ui-accent);
83
+ color: var(--nxe-ui-accent-contrast);
84
+ }
85
+
86
+ // dx: `.dx-state-focused.dx-list-item-selected
87
+ // { background-color: rgba(51,122,183,.7); color:#fff }`
88
+ &.nxe-list-item-active.nxe-list-item-selected {
89
+ background-color: var(--nxe-ui-focus-selected-bg);
90
+ }
91
+
92
+ // dx: `.dx-list-item.dx-state-active { background-color:#337ab7; color:#fff }` (mouse-down)
93
+ &:active {
94
+ background-color: var(--nxe-ui-accent);
95
+ color: var(--nxe-ui-accent-contrast);
96
+ }
97
+ }
98
+
99
+ // dx: `.dx-state-disabled .dx-list-item { background-color: transparent; opacity: .6 }`
100
+ .nxe-list-item[aria-disabled='true'] {
101
+ background-color: transparent;
102
+ opacity: 0.6;
103
+ cursor: default;
104
+ }
105
+
106
+ // dx: the empty message is NOT re-padded by the dropdown rule — it keeps the base
107
+ // `.dx-list .dx-empty-message { padding: 10px }` plus `min-height: 3em`.
108
+ .nxe-list-empty {
109
+ padding: var(--nxe-ui-list-padding);
110
+ min-height: 3em;
111
+ color: var(--nxe-ui-text-muted);
112
+ }
113
+ }
@@ -0,0 +1,146 @@
1
+ // Shared chrome for nxe text editors (text-box, text-area, number-box, date-box),
2
+ // reproducing DevExtreme `.dx-texteditor` (outlined stylingMode, light theme).
3
+ //
4
+ // Typography is NOT set here — it comes from `.nxe-widget` on the host (styling/_widget.scss), so
5
+ // it resolves per host app instead of assuming orbit's 14.4px root.
6
+
7
+ @mixin nxe-editor-chrome {
8
+ // Base box at ZERO specificity (`:where`) so per-instance Tailwind utilities applied on the
9
+ // host (`border-0`, `bg-transparent`, `text-right`, …) override it — dx.light.css got this for
10
+ // free by loading before Tailwind; this stylesheet loads after, so neutralise specificity.
11
+ :where(.nxe-editor) {
12
+ position: relative;
13
+ display: block;
14
+ box-sizing: border-box;
15
+ background: #fff;
16
+ border: 1px solid #ddd;
17
+ border-radius: 4px;
18
+ color: #333;
19
+ line-height: 1.35715;
20
+ }
21
+
22
+ .nxe-editor {
23
+ &:hover {
24
+ border-color: rgba(51, 122, 183, 0.4); // faded primary blue
25
+ }
26
+
27
+ &.nxe-focused {
28
+ border-color: #337ab7; // solid primary blue
29
+ }
30
+
31
+ &.nxe-readonly {
32
+ border-style: dashed;
33
+ }
34
+
35
+ &.nxe-disabled {
36
+ opacity: 0.5;
37
+ cursor: default;
38
+ }
39
+
40
+ // invalid: solid red, and thicker than the 1px resting box so a required field reads as
41
+ // required across a form of them. It used to rest at 40% opacity and only go solid on
42
+ // focus/hover, which put the strongest signal on the one field the user was already looking
43
+ // at and left the rest looking merely tinted (14-Aug review).
44
+ //
45
+ // The width is compensated with an inset shadow rather than a 2px border: growing the border
46
+ // would shift the input inside it by a pixel and jog the text as the field goes valid.
47
+ &.nxe-invalid {
48
+ border-color: rgba(217, 83, 79, 0.4);
49
+
50
+ &.nxe-focused,
51
+ &:hover {
52
+ border-color: #d9534f;
53
+ }
54
+
55
+ // Invalid badge — dx `.dx-invalid.dx-texteditor.dx-editor-outlined.dx-show-invalid-badge
56
+ // .dx-texteditor-input-container:after`: an 18px red disc with a bold white "!",
57
+ // vertically centred and inset 4px from the trailing edge. It flags the broken field at a
58
+ // glance, without waiting for the message bubble (which only shows while focused).
59
+ //
60
+ // Editors with a trailing button (chevron, calendar icon, clear ×) push the badge left of
61
+ // it with `--nxe-invalid-badge-inset`, mirroring dx's
62
+ // `.dx-invalid.dx-dropdowneditor .dx-texteditor-container:after { inset-inline-end: 38px }`.
63
+ > .nxe-editor-container::after {
64
+ content: '!';
65
+ pointer-events: none;
66
+ position: absolute;
67
+ top: 50%;
68
+ inset-inline-end: var(--nxe-invalid-badge-inset, 4px);
69
+ margin-top: -9px;
70
+ width: 18px;
71
+ height: 18px;
72
+ border-radius: 50%;
73
+ background-color: #d9534f;
74
+ color: #fff;
75
+ font-weight: 700;
76
+ font-size: 13px;
77
+ line-height: 18px;
78
+ text-align: center;
79
+ }
80
+
81
+ // dx `.dx-texteditor.dx-invalid .dx-texteditor-input { padding-inline-end: 34px }` — keeps
82
+ // a long value from running under the badge. Only for a container holding nothing but the
83
+ // input: where a trailing button exists the value already stops short of it, and dx leaves
84
+ // those inputs unpadded too (`.dx-dropdowneditor-button-visible` keeps padding-end 0).
85
+ > .nxe-editor-container:not(:has(> :not(.nxe-editor-input))) .nxe-editor-input {
86
+ padding-inline-end: 34px;
87
+ }
88
+ }
89
+ }
90
+
91
+ .nxe-editor-container {
92
+ display: flex;
93
+ position: relative;
94
+ align-items: center;
95
+ overflow: hidden;
96
+ width: 100%;
97
+ }
98
+
99
+ .nxe-editor-input {
100
+ -webkit-appearance: none;
101
+ appearance: none;
102
+ flex: 1 1 auto;
103
+ margin: 0;
104
+ width: 100%;
105
+ min-height: 34px;
106
+ padding: 7px 9px 8px;
107
+ border: 0;
108
+ outline: 0;
109
+ background: transparent;
110
+ color: #333;
111
+ // dx `.dx-texteditor-input { max-width: 100%; text-overflow: ellipsis }` — a value longer
112
+ // than the field is truncated with an ellipsis rather than clipped mid-glyph.
113
+ max-width: 100%;
114
+ text-overflow: ellipsis;
115
+ font: inherit;
116
+ border-radius: 4px;
117
+
118
+ &::placeholder {
119
+ color: #999;
120
+ }
121
+
122
+ &:disabled {
123
+ // dx keeps the input itself opaque; the host `.nxe-disabled` dims the whole editor
124
+ opacity: 1;
125
+ color: #333;
126
+ -webkit-text-fill-color: #333;
127
+ }
128
+ }
129
+
130
+ // Validation message bubble — solid red, white text (shown on focus, like dx).
131
+ .nxe-invalid-message {
132
+ position: absolute;
133
+ z-index: 10;
134
+ top: 100%;
135
+ inset-inline-start: 0;
136
+ margin-top: 1px;
137
+ max-width: 100%;
138
+ padding: 10px;
139
+ border-radius: 4px;
140
+ background-color: #d9534f;
141
+ color: #fff;
142
+ font-size: 0.85em;
143
+ line-height: normal;
144
+ word-wrap: break-word;
145
+ }
146
+ }
@@ -0,0 +1,2 @@
1
+ // @nexterajs/ui — shared SCSS mixins.
2
+ // Grows as components are added (e.g. form-control framing, grid theming). Empty for now.
@@ -0,0 +1,77 @@
1
+ // @nexterajs/ui — design tokens
2
+ //
3
+ // Self-contained CSS custom properties that reproduce the legacy theme/DevExtreme/Material visual
4
+ // values, so nxe components match without depending on Material/DevExtreme variables.
5
+ //
6
+ // PORTABILITY CONTRACT
7
+ // -------------------
8
+ // This library is consumed by more than one host app, and those apps do not share a root
9
+ // font-size (orbit sets `html { font-size: 14.4px }`; psak does not). Therefore:
10
+ //
11
+ // * The library NEVER uses `rem` — rem binds to the host app's root, so the same declaration
12
+ // renders at a different size in each app.
13
+ // * The library NEVER uses a bare `px` font-size — px binds to nothing at all, which is how
14
+ // `.nxe-select-panel { font-size: 14px }` ended up 14% larger than every other text surface
15
+ // in orbit (the CDK-portaled panel escapes the host app's editor font rule; see _widget.scss).
16
+ //
17
+ // Instead, absolute values live here as `--nxe-ui-*` custom properties whose DEFAULTS are
18
+ // DevExtreme dx.light's own. A host app overrides only the properties its own dx-era cascade
19
+ // changed. Sizes derived from the widget scale use `em`.
20
+ //
21
+ // Values are sourced from the reference deployment's real bundle
22
+ // (tools/visual-parity/reference/bright-styles.css), NOT from src/styles/dx-compat.css — that
23
+ // file was extracted from a newer DevExtreme release and disagrees with the reference.
24
+ //
25
+ // NAMESPACE: `--nxe-ui-*`, not `--nxe-*`. The host app's Tailwind theming plugin already emits
26
+ // `--nxe-border`, `--nxe-divider`, … dynamically; colliding with those would be silent and awful.
27
+
28
+ // `:where(:root)` — NOT `:root`. This sheet is the LAST entry in the host app's angular.json
29
+ // `styles` array, so a plain `:root` here would load after (and therefore beat) the host app's own
30
+ // `:root` overrides — defaults silently winning over the app that is trying to configure them.
31
+ // `:where()` pins these to zero specificity, so any host declaration always wins.
32
+ :where(:root) {
33
+ // Typography — reference `.dx-widget`
34
+ --nxe-ui-font-family: Helvetica Neue, Segoe UI, helvetica, verdana, sans-serif;
35
+ --nxe-ui-font-size: 14px; // dx default. orbit overrides -> 0.85rem (= 12.24px @ 14.4px root)
36
+ --nxe-ui-font-weight: 400;
37
+ --nxe-ui-line-height: 1.35715;
38
+
39
+ // Palette — reference dx.light
40
+ --nxe-ui-text: #333;
41
+ --nxe-ui-text-muted: #999;
42
+ --nxe-ui-border: #ddd;
43
+ --nxe-ui-surface: #fff;
44
+ --nxe-ui-accent: #337ab7;
45
+ --nxe-ui-accent-contrast: #fff;
46
+ --nxe-ui-danger: #d9534f;
47
+ --nxe-ui-success: #5cb85c;
48
+
49
+ // State layers — reference dx.light list/menu states
50
+ --nxe-ui-hover-bg: rgba(0, 0, 0, 0.04);
51
+ --nxe-ui-selected-bg: rgba(0, 0, 0, 0.1);
52
+ --nxe-ui-selected-hover-bg: rgba(0, 0, 0, 0.07);
53
+ --nxe-ui-focus-selected-bg: rgba(51, 122, 183, 0.7);
54
+ --nxe-ui-disabled-opacity: 0.5;
55
+
56
+ // Metrics
57
+ --nxe-ui-radius: 4px;
58
+ --nxe-ui-popup-radius: 6px;
59
+ --nxe-ui-popup-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
60
+ --nxe-ui-item-padding: 7px 9px;
61
+ --nxe-ui-list-padding: 10px;
62
+
63
+ // Overlay geometry — host apps that skinned dx popups override these.
64
+ // 0 keeps a dropdown panel exactly as wide as the editor it drops from (the components set
65
+ // `width` inline from the trigger's offsetWidth; a non-zero floor here overrides that). Orbit
66
+ // carried the dx-era `.dx-overlay-content { min-width: 400px !important }` here until
67
+ // 2026-07-29 and dropped it — see nxe-ui/DEFAULTS.md.
68
+ --nxe-ui-overlay-min-width: 0px;
69
+
70
+ // foreground.divider — slate.200
71
+ --nxe-divider-color: #e2e8f0;
72
+ }
73
+
74
+ :where(.dark) {
75
+ // slate.100 @ 12%
76
+ --nxe-divider-color: rgba(241, 245, 249, 0.12);
77
+ }
@@ -0,0 +1,58 @@
1
+ // @nexterajs/ui — the widget base, i.e. the nxe counterpart of dx.light's `.dx-widget`.
2
+ //
3
+ // WHY THIS EXISTS
4
+ // ---------------
5
+ // DevExtreme gave every widget its typography from one rule on `.dx-widget`, and orbit's dx-era
6
+ // stylesheet resized all of them with a single `.dx-widget { font-size: .85rem !important }`.
7
+ // The nxe components dropped that base class, so each one had to re-declare its own font — and
8
+ // the ones rendered through a CDK overlay had no way to inherit anything sensible at all.
9
+ //
10
+ // A CDK pane is appended to <body> (`@angular/cdk` overlay container), so a portaled dropdown
11
+ // inherits `html`/`body`, never the editor that opened it. Concretely, in orbit:
12
+ //
13
+ // `font: inherit` -> body's 0.8rem = 11.52px, and the page font stack (wrong)
14
+ // hard-coded `font-size: 14px` -> 14px while every sibling surface is 12.24px (wrong)
15
+ // `.nxe-widget` on the PANE -> the widget contract crosses the portal boundary (right)
16
+ //
17
+ // So: every component host AND every overlay pane the library opens carries `nxe-widget`. Use the
18
+ // `nxePane()` helper (styling/nxe-overlay.ts) when creating an overlay so this cannot be forgotten.
19
+ //
20
+ // `:where()` holds specificity at 0 so per-instance Tailwind utilities on a host still win —
21
+ // dx.light got that for free by loading before Tailwind; this sheet loads after.
22
+
23
+ @mixin nxe-widget {
24
+ :where(.nxe-widget) {
25
+ font-family: var(--nxe-ui-font-family);
26
+ font-size: var(--nxe-ui-font-size);
27
+ font-weight: var(--nxe-ui-font-weight);
28
+ line-height: var(--nxe-ui-line-height);
29
+ }
30
+
31
+ // Form controls do not inherit font by default (UA stylesheet wins), so they need it
32
+ // explicitly — as dx did with `.dx-widget input, .dx-widget textarea`.
33
+ .nxe-widget input,
34
+ .nxe-widget textarea,
35
+ .nxe-widget select,
36
+ .nxe-widget button {
37
+ font-family: var(--nxe-ui-font-family);
38
+ line-height: var(--nxe-ui-line-height);
39
+ }
40
+ }
41
+
42
+ // Deliberately NOT part of nxe-widget yet. dx also put `color` and `box-sizing` on `.dx-widget`,
43
+ // but adopting `color` here would newly repaint nxe-tree-view / nxe-list / nxe-file-uploader, and
44
+ // the host app renders its sidebar inside a `.dark` container. Land it only after a visual pass.
45
+ @mixin nxe-widget-chrome {
46
+ :where(.nxe-widget) {
47
+ color: var(--nxe-ui-text);
48
+ }
49
+
50
+ .nxe-widget,
51
+ .nxe-widget *,
52
+ .nxe-widget ::before,
53
+ .nxe-widget ::after,
54
+ .nxe-widget::before,
55
+ .nxe-widget::after {
56
+ box-sizing: border-box;
57
+ }
58
+ }